Skip to content

Commit ebd937d

Browse files
committed
feat(packages): ui: add stepper
1 parent 89afa9e commit ebd937d

File tree

17 files changed

+401
-4
lines changed

17 files changed

+401
-4
lines changed

packages/primitives/src/components/stepper/stepper-item.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { computed, toRefs } from 'vue';
33
import { useForwardExpose, useId } from '../../composables';
44
import { Primitive } from '../primitive';
55
import { injectStepperRootContext, provideStepperItemContext } from './context';
6-
import type { StepperItemPropsWithPrimitive } from './types';
6+
import type { StepperItemPropsWithPrimitive, StepperState } from './types';
77
88
defineOptions({
99
name: 'StepperItem'
@@ -23,7 +23,7 @@ const rootContext = injectStepperRootContext();
2323
const titleId = useId(undefined, 'soybean-stepper-item-title');
2424
const descriptionId = useId(undefined, 'soybean-stepper-item-description');
2525
26-
const itemState = computed(() => {
26+
const itemState = computed<StepperState>(() => {
2727
if (completed.value) return 'completed';
2828
if (rootContext.modelValue.value === step.value) return 'active';
2929
if (rootContext.modelValue.value! > step.value) return 'completed';

packages/primitives/src/composables/use-forward-expose.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ export function useForwardExpose<T extends ComponentPublicInstance>() {
1010
const currentElement = computed<HTMLElement>(() => {
1111
// $el could be text/comment for non-single root normal or text root, thus we retrieve the nextElementSibling
1212

13-
return ['#text', '#comment'].includes((currentRef.value as ComponentPublicInstance)?.$el.nodeName)
14-
? (currentRef.value as ComponentPublicInstance)?.$el.nextElementSibling
13+
return ['#text', '#comment'].includes((currentRef.value as ComponentPublicInstance)?.$el?.nodeName)
14+
? (currentRef.value as ComponentPublicInstance)?.$el?.nextElementSibling
1515
: unrefElement(currentRef as MaybeComputedElementRef<MaybeElement>);
1616
});
1717

packages/ui/src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export * from './separator';
3838
export * from './sheet';
3939
export * from './skeleton';
4040
export * from './slider';
41+
export * from './stepper';
4142
export * from './switch';
4243
export * from './tabs';
4344
export * from './tags-input';
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import SStepperRoot from './stepper-root.vue';
2+
import SStepperDescription from './stepper-description.vue';
3+
import SStepperIndicator from './stepper-indicator.vue';
4+
import SStepperItem from './stepper-item.vue';
5+
import SStepperSeparator from './stepper-separator.vue';
6+
import SStepperTitle from './stepper-title.vue';
7+
import SStepperTrigger from './stepper-trigger.vue';
8+
import SStepper from './stepper.vue';
9+
10+
export {
11+
SStepperRoot,
12+
SStepperDescription,
13+
SStepperIndicator,
14+
SStepperItem,
15+
SStepperSeparator,
16+
SStepperTitle,
17+
SStepperTrigger,
18+
SStepper
19+
};
20+
21+
export * from './types';
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<script setup lang="ts">
2+
import { computed } from 'vue';
3+
import { StepperDescription } from '@soybean-ui/primitives';
4+
import { cn, stepperVariants } from '@soybean-ui/variants';
5+
import type { StepperDescriptionProps } from './types';
6+
7+
defineOptions({
8+
name: 'SStepperDescription'
9+
});
10+
11+
const { class: cls } = defineProps<StepperDescriptionProps>();
12+
13+
const { description } = stepperVariants();
14+
15+
const mergedCls = computed(() => cn(description(), cls));
16+
</script>
17+
18+
<template>
19+
<StepperDescription :class="mergedCls">
20+
<slot />
21+
</StepperDescription>
22+
</template>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<script setup lang="ts">
2+
import { computed } from 'vue';
3+
import { StepperIndicator } from '@soybean-ui/primitives';
4+
import { cn, stepperVariants } from '@soybean-ui/variants';
5+
import type { StepperIndicatorProps } from './types';
6+
7+
defineOptions({
8+
name: 'SStepperIndicator'
9+
});
10+
11+
const { class: cls } = defineProps<StepperIndicatorProps>();
12+
13+
const { indicator } = stepperVariants();
14+
15+
const mergedCls = computed(() => cn(indicator(), cls));
16+
</script>
17+
18+
<template>
19+
<StepperIndicator :class="mergedCls">
20+
<slot />
21+
</StepperIndicator>
22+
</template>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<script setup lang="ts">
2+
import { computed } from 'vue';
3+
import { StepperItem, useForwardProps } from '@soybean-ui/primitives';
4+
import { cn, stepperVariants } from '@soybean-ui/variants';
5+
import type { StepperItemProps } from './types';
6+
7+
defineOptions({
8+
name: 'SStepperItem'
9+
});
10+
11+
const { class: cls, orientation, ...delegatedProps } = defineProps<StepperItemProps>();
12+
13+
const forwardedProps = useForwardProps(delegatedProps);
14+
15+
const mergedCls = computed(() => {
16+
const { item } = stepperVariants({ orientation });
17+
18+
return cn(item(), cls);
19+
});
20+
</script>
21+
22+
<template>
23+
<StepperItem v-slot="slotProps" v-bind="forwardedProps" :class="mergedCls">
24+
<slot v-bind="slotProps" />
25+
</StepperItem>
26+
</template>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<script setup lang="ts">
2+
import { computed } from 'vue';
3+
import { StepperRoot, useForwardPropsEmits } from '@soybean-ui/primitives';
4+
import { cn, stepperVariants } from '@soybean-ui/variants';
5+
import type { StepperRootEmits, StepperRootProps } from './types';
6+
7+
defineOptions({
8+
name: 'SStepperRoot'
9+
});
10+
11+
const { class: cls, orientation, ...delegatedProps } = defineProps<StepperRootProps>();
12+
13+
const emit = defineEmits<StepperRootEmits>();
14+
15+
const forwarded = useForwardPropsEmits(delegatedProps, emit);
16+
17+
const mergedCls = computed(() => {
18+
const { root } = stepperVariants({ orientation });
19+
20+
return cn(root(), cls);
21+
});
22+
</script>
23+
24+
<template>
25+
<StepperRoot v-slot="slotProps" v-bind="forwarded" :class="mergedCls">
26+
<slot v-bind="slotProps" />
27+
</StepperRoot>
28+
</template>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<script setup lang="ts">
2+
import { computed } from 'vue';
3+
import { StepperSeparator, useForwardProps } from '@soybean-ui/primitives';
4+
import { cn, stepperVariants } from '@soybean-ui/variants';
5+
import type { StepperSeparatorProps } from './types';
6+
7+
defineOptions({
8+
name: 'SStepperSeparator'
9+
});
10+
11+
const { class: cls, orientation, ...delegatedProps } = defineProps<StepperSeparatorProps>();
12+
13+
const forwardedProps = useForwardProps(delegatedProps);
14+
15+
const mergedCls = computed(() => {
16+
const { separator } = stepperVariants({ orientation });
17+
18+
return cn(separator(), cls);
19+
});
20+
</script>
21+
22+
<template>
23+
<StepperSeparator v-bind="forwardedProps" :class="mergedCls">
24+
<slot />
25+
</StepperSeparator>
26+
</template>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<script setup lang="ts">
2+
import { computed } from 'vue';
3+
import { StepperTitle } from '@soybean-ui/primitives';
4+
import { cn, stepperVariants } from '@soybean-ui/variants';
5+
import type { StepperTitleProps } from './types';
6+
7+
defineOptions({
8+
name: 'SStepperTitle'
9+
});
10+
11+
const { class: cls } = defineProps<StepperTitleProps>();
12+
13+
const { title } = stepperVariants();
14+
15+
const mergedCls = computed(() => cn(title(), cls));
16+
</script>
17+
18+
<template>
19+
<StepperTitle :class="mergedCls">
20+
<slot />
21+
</StepperTitle>
22+
</template>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<script setup lang="ts">
2+
import { computed } from 'vue';
3+
import { StepperTrigger } from '@soybean-ui/primitives';
4+
import { cn, stepperVariants } from '@soybean-ui/variants';
5+
import type { StepperTriggerProps } from './types';
6+
7+
defineOptions({
8+
name: 'SStepperTrigger'
9+
});
10+
11+
const { class: cls, as, asChild } = defineProps<StepperTriggerProps>();
12+
13+
const { trigger } = stepperVariants();
14+
15+
const mergedCls = computed(() => cn(trigger(), cls));
16+
</script>
17+
18+
<template>
19+
<StepperTrigger :class="mergedCls" :as="as" :as-child="asChild">
20+
<slot />
21+
</StepperTrigger>
22+
</template>
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<script setup lang="ts" generic="T extends StepperOptionData = StepperOptionData">
2+
import { computed } from 'vue';
3+
import { useForwardPropsEmits } from '@soybean-ui/primitives';
4+
import { cn, stepperVariants } from '@soybean-ui/variants';
5+
import { Check, Circle, Dot } from 'lucide-vue-next';
6+
import SButton from '../button/button.vue';
7+
import SStepperRoot from './stepper-root.vue';
8+
import SStepperDescription from './stepper-description.vue';
9+
import SStepperIndicator from './stepper-indicator.vue';
10+
import SStepperItem from './stepper-item.vue';
11+
import SStepperSeparator from './stepper-separator.vue';
12+
import SStepperTitle from './stepper-title.vue';
13+
import SStepperTrigger from './stepper-trigger.vue';
14+
import type { StepperEmits, StepperOptionData, StepperProps } from './types';
15+
16+
defineOptions({
17+
name: 'SStepper'
18+
});
19+
20+
const { class: cls, ui, items, ...delegatedRootProps } = defineProps<StepperProps<T>>();
21+
22+
const emit = defineEmits<StepperEmits>();
23+
24+
const forwardedRoot = useForwardPropsEmits(delegatedRootProps, emit);
25+
26+
const titleDescWrapperCls = computed(() => {
27+
const { titleDescWrapper } = stepperVariants({ orientation: delegatedRootProps.orientation });
28+
29+
return cn(titleDescWrapper(), ui?.titleDescWrapper);
30+
});
31+
</script>
32+
33+
<template>
34+
<SStepperRoot v-bind="forwardedRoot" :class="cls || ui?.root">
35+
<template v-for="(item, index) in items" :key="index">
36+
<slot name="item" :item="item" :index="index">
37+
<SStepperItem
38+
v-slot="{ state }"
39+
:class="ui?.item"
40+
:orientation="orientation"
41+
:step="item.step"
42+
:disabled="item.disabled"
43+
:completed="item.completed"
44+
>
45+
<SStepperSeparator
46+
v-if="item.step !== items[items.length - 1].step"
47+
:class="ui?.separator"
48+
:orientation="orientation"
49+
/>
50+
<SStepperIndicator v-if="item.indicatorLabel" :class="ui?.indicator">
51+
<component :is="item.icon" />
52+
<span v-if="item.indicatorLabel">{{ item.indicatorLabel }}</span>
53+
</SStepperIndicator>
54+
<slot name="trigger" :item="item" :state="state">
55+
<SStepperTrigger :class="ui?.trigger" as-child>
56+
<SButton
57+
:variant="state == 'completed' || state == 'active' ? 'solid' : 'outline'"
58+
shape="circle"
59+
class="z-10"
60+
:class="{ 'ring-2 ring-primary ring-offset-2 ring-offset-background': state === 'active' }"
61+
>
62+
<Check v-if="state == 'completed'" />
63+
<Circle v-if="state == 'active'" />
64+
<Dot v-if="state == 'inactive'" />
65+
</SButton>
66+
</SStepperTrigger>
67+
</slot>
68+
<div :class="titleDescWrapperCls">
69+
<SStepperTitle :class="ui?.title">{{ item.title }}</SStepperTitle>
70+
<SStepperDescription :class="ui?.description">{{ item.description }}</SStepperDescription>
71+
</div>
72+
</SStepperItem>
73+
</slot>
74+
</template>
75+
</SStepperRoot>
76+
</template>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import type { Component } from 'vue';
2+
import type {
3+
ClassValue,
4+
StepperRootEmits,
5+
StepperTriggerPropsWithPrimitive,
6+
StepperDescriptionProps as _StepperDescriptionProps,
7+
StepperIndicatorProps as _StepperIndicatorProps,
8+
StepperItemProps as _StepperItemProps,
9+
StepperRootProps as _StepperRootProps,
10+
StepperSeparatorProps as _StepperSeparatorProps,
11+
StepperTitleProps as _StepperTitleProps
12+
} from '@soybean-ui/primitives';
13+
import type { StepperSlots, ThemeOrientation } from '@soybean-ui/variants';
14+
15+
export interface StepperRootProps extends _StepperRootProps {
16+
orientation?: ThemeOrientation;
17+
}
18+
19+
export interface StepperDescriptionProps extends _StepperDescriptionProps {}
20+
21+
export interface StepperIndicatorProps extends _StepperIndicatorProps {}
22+
23+
export interface StepperItemProps extends _StepperItemProps {
24+
orientation?: ThemeOrientation;
25+
}
26+
27+
export interface StepperSeparatorProps extends _StepperSeparatorProps {
28+
orientation?: ThemeOrientation;
29+
}
30+
31+
export interface StepperTitleProps extends _StepperTitleProps {}
32+
33+
export interface StepperTriggerProps extends StepperTriggerPropsWithPrimitive {}
34+
35+
export interface StepperOptionData extends Pick<StepperItemProps, 'step' | 'disabled' | 'completed'> {
36+
title: string;
37+
description: string;
38+
indicatorLabel?: string;
39+
icon?: Component;
40+
}
41+
42+
export type StepperUi = Partial<Record<StepperSlots, ClassValue>>;
43+
44+
export interface StepperProps<T extends StepperOptionData = StepperOptionData> extends StepperRootProps {
45+
ui?: StepperUi;
46+
items: T[];
47+
}
48+
49+
export type StepperEmits = StepperRootEmits;
50+
51+
export type { StepperRootEmits };

packages/variants/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export * from './variants/separator';
3636
export * from './variants/sheet';
3737
export * from './variants/skeleton';
3838
export * from './variants/slider';
39+
export * from './variants/stepper';
3940
export * from './variants/switch';
4041
export * from './variants/tabs';
4142
export * from './variants/tags-input';

0 commit comments

Comments
 (0)