Skip to content

Commit

Permalink
feat(Stepper): implement slot utility functions (#1179)
Browse files Browse the repository at this point in the history
* chore(Stepper): remove unused v-model

* feat(Stepper): added slot utility functions

* chore(docs): run docs gen

* chore(Stepper): remove debug from test

* refactor(Stepper): remove redundant stepper item set

* fix(Stepper): proper compute utility slot functions

* fix(Stepper): fix computed in stepper trigger
  • Loading branch information
epr3 authored Aug 5, 2024
1 parent 995c149 commit ba571bd
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 21 deletions.
30 changes: 30 additions & 0 deletions docs/content/meta/StepperRoot.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,35 @@
'name': 'modelValue',
'description': '<p>Current step</p>\n',
'type': 'number | undefined'
},
{
'name': 'totalSteps',
'description': '<p>Total number of steps</p>\n',
'type': 'number'
},
{
'name': 'isNextDisabled',
'description': '<p>Whether or not the next step is disabled</p>\n',
'type': 'boolean'
},
{
'name': 'isPrevDisabled',
'description': '<p>Whether or not the previous step is disabled</p>\n',
'type': 'boolean'
},
{
'name': 'isFirstStep',
'description': '<p>Whether or not the first step is active</p>\n',
'type': 'boolean'
},
{
'name': 'isLastStep',
'description': '<p>Whether or not the last step is active</p>\n',
'type': 'boolean'
},
{
'name': 'goToStep',
'description': '<p>Go to a specific step</p>\n',
'type': ''
}
]" />
2 changes: 0 additions & 2 deletions packages/radix-vue/src/Stepper/Stepper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import userEvent from '@testing-library/user-event'
import type { StepperRootProps } from './StepperRoot.vue'
import { render } from '@testing-library/vue'
import { useTestKbd } from '@/shared'
import { screen } from '@testing-library/dom'

const steps = [{
step: 1,
Expand Down Expand Up @@ -46,7 +45,6 @@ function setup(props: { stepperProps?: StepperRootProps & { steps: { step: numbe

it('should pass axe accessibility tests', async () => {
const { stepper } = setup()
screen.debug()
expect(await axe(stepper)).toHaveNoViolations()
})

Expand Down
71 changes: 65 additions & 6 deletions packages/radix-vue/src/Stepper/StepperRoot.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<script lang="ts">
import type { Ref } from 'vue'
import { useVModel } from '@vueuse/core'
import { ref, toRefs } from 'vue'
import { type Ref, computed, nextTick, ref, toRefs, watch } from 'vue'
import type { DataOrientation, Direction } from '../shared/types'
import type { PrimitiveProps } from '@/Primitive'
import { createContext, useDirection, useForwardExpose } from '@/shared'
Expand All @@ -13,7 +12,6 @@ export interface StepperRootContext {
orientation: Ref<DataOrientation>
dir: Ref<Direction>
linear: Ref<boolean>
stepperItems: Ref<Set<HTMLElement>>
totalStepperItems: Ref<Set<HTMLElement>>
}
Expand Down Expand Up @@ -58,21 +56,74 @@ defineSlots<{
default: (props: {
/** Current step */
modelValue: number | undefined
/** Total number of steps */
totalSteps: number
/** Whether or not the next step is disabled */
isNextDisabled: boolean
/** Whether or not the previous step is disabled */
isPrevDisabled: boolean
/** Whether or not the first step is active */
isFirstStep: boolean
/** Whether or not the last step is active */
isLastStep: boolean
/** Go to a specific step */
goToStep: (step: number) => void
}) => any
}>()
const { dir: propDir, orientation: propOrientation, linear } = toRefs(props)
const dir = useDirection(propDir)
useForwardExpose()
const stepperItems = ref<Set<HTMLElement>>(new Set())
const totalStepperItems = ref<Set<HTMLElement>>(new Set())
const modelValue = useVModel(props, 'modelValue', emits, {
defaultValue: props.defaultValue,
passive: (props.modelValue === undefined) as false,
})
const totalStepperItemsArray = computed(() => Array.from(totalStepperItems.value))
const isFirstStep = computed(() => modelValue.value === 1)
const isLastStep = computed(() => modelValue.value === totalStepperItemsArray.value.length)
const totalSteps = computed(() => totalStepperItems.value.size)
function goToStep(step: number) {
if (step > totalSteps.value)
return
if (step < 1)
return
if (totalStepperItems.value.size && !!totalStepperItemsArray.value[step].getAttribute('disabled'))
return
if (linear.value) {
if (step > (modelValue.value ?? 1) + 1)
return
}
modelValue.value = step
}
const nextStepperItem = ref<HTMLElement | null>(null)
const prevStepperItem = ref<HTMLElement | null>(null)
const isNextDisabled = computed(() => nextStepperItem.value ? nextStepperItem.value.getAttribute('disabled') === '' : true)
const isPrevDisabled = computed(() => prevStepperItem.value ? prevStepperItem.value.getAttribute('disabled') === '' : true)
watch(modelValue, async () => {
await nextTick(() => {
nextStepperItem.value = totalStepperItemsArray.value.length && modelValue.value! < totalStepperItemsArray.value.length ? totalStepperItemsArray.value[modelValue.value!] : null
prevStepperItem.value = totalStepperItemsArray.value.length && modelValue.value! > 1 ? totalStepperItemsArray.value[modelValue.value! - 2] : null
})
})
watch(totalStepperItemsArray, async () => {
await nextTick(() => {
nextStepperItem.value = totalStepperItemsArray.value.length && modelValue.value! < totalStepperItemsArray.value.length ? totalStepperItemsArray.value[modelValue.value!] : null
prevStepperItem.value = totalStepperItemsArray.value.length && modelValue.value! > 1 ? totalStepperItemsArray.value[modelValue.value! - 2] : null
})
})
provideStepperRootContext({
modelValue,
changeModelValue: (value: number) => {
Expand All @@ -81,7 +132,6 @@ provideStepperRootContext({
orientation: propOrientation,
dir,
linear,
stepperItems,
totalStepperItems,
})
</script>
Expand All @@ -95,7 +145,16 @@ provideStepperRootContext({
:data-linear="linear ? '' : undefined"
:data-orientation="orientation"
>
<slot :model-value="modelValue" />
<slot
:model-value="modelValue"
:total-steps="totalStepperItems.size"
:is-next-disabled="isNextDisabled"
:is-prev-disabled="isPrevDisabled"
:is-first-step="isFirstStep"
:is-last-step="isLastStep"
:go-to-step="goToStep"
/>

<div
aria-live="polite"
aria-atomic="true"
Expand Down
14 changes: 2 additions & 12 deletions packages/radix-vue/src/Stepper/StepperTrigger.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script lang="ts">
import type { PrimitiveProps } from '@/Primitive'
import { useArrowNavigation, useForwardExpose, useKbd } from '@/shared'
import { computed, onMounted, onUnmounted, watch } from 'vue'
import { computed, onMounted, onUnmounted } from 'vue'
export interface StepperTriggerProps extends PrimitiveProps {
}
Expand All @@ -20,7 +20,7 @@ const rootContext = injectStepperRootContext()
const itemContext = injectStepperItemContext()
const kbd = useKbd()
const stepperItems = computed(() => Array.from(rootContext.stepperItems.value))
const stepperItems = computed(() => Array.from(rootContext.totalStepperItems.value))
function handleMouseDown(event: MouseEvent) {
if (itemContext.disabled.value)
Expand Down Expand Up @@ -67,22 +67,12 @@ function handleKeyDown(event: KeyboardEvent) {
const { forwardRef, currentElement } = useForwardExpose()
onMounted(() => {
if (itemContext.isFocusable.value)
rootContext.stepperItems.value.add(currentElement.value)
rootContext.totalStepperItems.value.add(currentElement.value)
})
onUnmounted(() => {
rootContext.stepperItems.value.delete(currentElement.value)
rootContext.totalStepperItems.value.delete(currentElement.value)
})
watch(itemContext.isFocusable, (newValue) => {
if (newValue)
rootContext.stepperItems.value.add(currentElement.value)
else
rootContext.stepperItems.value.delete(currentElement.value)
})
</script>

<template>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ const steps = [{
>
<Variant title="default">
<StepperRoot
v-model="step"
class="flex gap-2 p-1"
>
<StepperItem
Expand Down

0 comments on commit ba571bd

Please sign in to comment.