@sakoa/ui 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/cli/index.js +8 -0
  2. package/dist/components/ui/SAlert.d.ts +3 -2
  3. package/dist/components/ui/SButton.d.ts +6 -5
  4. package/dist/components/ui/SCheckbox.d.ts +4 -3
  5. package/dist/components/ui/SDatePicker.d.ts +3 -3
  6. package/dist/components/ui/SGlassButton.d.ts +6 -5
  7. package/dist/components/ui/SInput.d.ts +6 -5
  8. package/dist/components/ui/SSelect.d.ts +5 -4
  9. package/dist/components/ui/SSwitch.d.ts +6 -5
  10. package/dist/components/ui/accordion/SAccordionItem.d.ts +7 -6
  11. package/dist/components/ui/card/SCardHeader.d.ts +4 -3
  12. package/dist/components/ui/dropdown/SDropdownGroup.d.ts +3 -2
  13. package/dist/components/ui/dropdown/SDropdownItem.d.ts +7 -6
  14. package/dist/components/ui/option/SOption.d.ts +3 -2
  15. package/dist/components/ui/otp/SOTP.d.ts +1 -1
  16. package/dist/components/ui/pagination/SPagination.d.ts +1 -1
  17. package/dist/components/ui/progress/SProgress.d.ts +1 -1
  18. package/dist/components/ui/progress/SProgressRange.d.ts +1 -1
  19. package/dist/components/ui/radio/SRadio.d.ts +4 -3
  20. package/dist/components/ui/radio/SRadioGroup.d.ts +1 -1
  21. package/dist/components/ui/stepper/SStepper.d.ts +1 -1
  22. package/dist/index.d.ts +1 -0
  23. package/dist/lib/icon.d.ts +10 -0
  24. package/dist/saka-ui.css +1 -1
  25. package/dist/saka-ui.js +5355 -5252
  26. package/dist/saka-ui.umd.cjs +11 -11
  27. package/dist/views/docs/CLIView.d.ts +2 -0
  28. package/dist/views/docs/GettingStartedView.d.ts +2 -0
  29. package/dist/views/docs/IconsGuideView.d.ts +2 -0
  30. package/dist/views/docs/UseClickOutsideView.d.ts +9 -9
  31. package/dist/views/docs/UseHotkeyView.d.ts +10 -10
  32. package/dist/views/ui/OTPView.d.ts +3 -3
  33. package/package.json +1 -1
  34. package/registry/source/components/ui/SAlert.vue +9 -5
  35. package/registry/source/components/ui/SButton.vue +12 -10
  36. package/registry/source/components/ui/SCheckbox.vue +4 -2
  37. package/registry/source/components/ui/SGlassButton.vue +11 -10
  38. package/registry/source/components/ui/SInput.vue +12 -4
  39. package/registry/source/components/ui/SSelect.vue +30 -8
  40. package/registry/source/components/ui/SSwitch.vue +7 -4
  41. package/registry/source/components/ui/accordion/SAccordionItem.vue +19 -5
  42. package/registry/source/components/ui/card/SCardHeader.vue +8 -5
  43. package/registry/source/components/ui/drawer/SDrawerClose.vue +4 -2
  44. package/registry/source/components/ui/dropdown/SDropdown.vue +11 -7
  45. package/registry/source/components/ui/dropdown/SDropdownGroup.vue +4 -2
  46. package/registry/source/components/ui/dropdown/SDropdownItem.vue +10 -7
  47. package/registry/source/components/ui/option/SOption.vue +9 -2
  48. package/registry/source/components/ui/radio/SRadio.vue +11 -3
  49. package/registry/source/components/ui/tabs/STabs.vue +5 -2
@@ -0,0 +1,2 @@
1
+ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, HTMLDivElement>;
2
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, HTMLDivElement>;
2
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, HTMLDivElement>;
2
+ export default _default;
@@ -12,8 +12,8 @@ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import
12
12
  readonly preserveSize?: boolean | undefined;
13
13
  readonly block?: boolean | undefined;
14
14
  readonly rounded?: "none" | "sm" | "md" | "lg" | "full" | undefined;
15
- readonly iconLeft?: string | undefined;
16
- readonly iconRight?: string | undefined;
15
+ readonly iconLeft?: import('../../index').IconProp | undefined;
16
+ readonly iconRight?: import('../../index').IconProp | undefined;
17
17
  readonly iconOnly?: boolean | undefined;
18
18
  readonly tag?: string | undefined;
19
19
  readonly href?: string | undefined;
@@ -45,8 +45,8 @@ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import
45
45
  }>, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
46
46
  click: (event: MouseEvent) => any;
47
47
  }, string, {
48
- type: "button" | "submit" | "reset";
49
48
  size: "xs" | "small" | "medium" | "large" | "xl";
49
+ type: "button" | "submit" | "reset";
50
50
  disabled: boolean;
51
51
  color: string;
52
52
  variant: "filled" | "outlined" | "light" | "ghost" | "link";
@@ -55,8 +55,8 @@ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import
55
55
  rounded: "none" | "sm" | "md" | "lg" | "full";
56
56
  preserveSize: boolean;
57
57
  block: boolean;
58
- iconLeft: string;
59
- iconRight: string;
58
+ iconLeft: import('../../index').IconProp;
59
+ iconRight: import('../../index').IconProp;
60
60
  iconOnly: boolean;
61
61
  tag: string;
62
62
  href: string;
@@ -86,8 +86,8 @@ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import
86
86
  $nextTick: typeof import('vue').nextTick;
87
87
  $watch<T extends string | ((...args: any) => any)>(source: T, cb: T extends (...args: any) => infer R ? (...args: [R, R, import('@vue/reactivity').OnCleanup]) => any : (...args: [any, any, import('@vue/reactivity').OnCleanup]) => any, options?: import('vue').WatchOptions): import('vue').WatchStopHandle;
88
88
  } & Readonly<{
89
- type: "button" | "submit" | "reset";
90
89
  size: "xs" | "small" | "medium" | "large" | "xl";
90
+ type: "button" | "submit" | "reset";
91
91
  disabled: boolean;
92
92
  color: string;
93
93
  variant: "filled" | "outlined" | "light" | "ghost" | "link";
@@ -96,8 +96,8 @@ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import
96
96
  rounded: "none" | "sm" | "md" | "lg" | "full";
97
97
  preserveSize: boolean;
98
98
  block: boolean;
99
- iconLeft: string;
100
- iconRight: string;
99
+ iconLeft: import('../../index').IconProp;
100
+ iconRight: import('../../index').IconProp;
101
101
  iconOnly: boolean;
102
102
  tag: string;
103
103
  href: string;
@@ -108,7 +108,7 @@ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import
108
108
  iconClass: string;
109
109
  }> & Omit<Readonly<import('../../components/ui/SButton').Props> & Readonly<{
110
110
  onClick?: ((event: MouseEvent) => any) | undefined;
111
- }>, "type" | "size" | "disabled" | "color" | "variant" | "contentClass" | "loading" | "rounded" | "preserveSize" | "block" | "iconLeft" | "iconRight" | "iconOnly" | "tag" | "href" | "to" | "ripple" | "animationType" | "animateInactive" | "iconClass"> & import('vue').ShallowUnwrapRef<{}> & {} & import('vue').ComponentCustomProperties & {} & {
111
+ }>, "size" | "type" | "disabled" | "color" | "variant" | "contentClass" | "loading" | "rounded" | "preserveSize" | "block" | "iconLeft" | "iconRight" | "iconOnly" | "tag" | "href" | "to" | "ripple" | "animationType" | "animateInactive" | "iconClass"> & import('vue').ShallowUnwrapRef<{}> & {} & import('vue').ComponentCustomProperties & {} & {
112
112
  $slots: {
113
113
  'icon-left'?(_: {}): any;
114
114
  default?(_: {}): any;
@@ -13,8 +13,8 @@ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import
13
13
  readonly placeholder?: string | undefined;
14
14
  readonly labelPlacement?: "top" | "top-left" | "top-center" | "top-right" | "bottom" | "left" | "right" | "floating" | "inside" | undefined;
15
15
  readonly labelAnimation?: "morph" | "slide" | "fade" | "none" | undefined;
16
- readonly iconLeft?: string | undefined;
17
- readonly iconRight?: string | undefined;
16
+ readonly iconLeft?: import('../../index').IconProp | undefined;
17
+ readonly iconRight?: import('../../index').IconProp | undefined;
18
18
  readonly iconColor?: string | undefined;
19
19
  readonly disabled?: boolean | undefined;
20
20
  readonly readonly?: boolean | undefined;
@@ -54,8 +54,8 @@ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import
54
54
  readonly inputClass?: string | undefined;
55
55
  readonly labelClass?: string | undefined;
56
56
  readonly wrapperClass?: string | undefined;
57
- readonly "onUpdate:modelValue"?: ((value: string | number) => any) | undefined;
58
57
  readonly onClear?: (() => any) | undefined;
58
+ readonly "onUpdate:modelValue"?: ((value: string | number) => any) | undefined;
59
59
  readonly onInput?: ((event: Event) => any) | undefined;
60
60
  readonly onChange?: ((value: string | number, event: Event) => any) | undefined;
61
61
  readonly onBlur?: ((event: FocusEvent) => any) | undefined;
@@ -80,11 +80,11 @@ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import
80
80
  $root: import('vue').ComponentPublicInstance | null;
81
81
  $parent: import('vue').ComponentPublicInstance | null;
82
82
  $host: Element | null;
83
- $emit: ((event: "update:modelValue", value: string | number) => void) & ((event: "clear") => void) & ((event: "input", event: Event) => void) & ((event: "change", value: string | number, event: Event) => void) & ((event: "blur", event: FocusEvent) => void) & ((event: "focus", event: FocusEvent) => void) & ((event: "update:error", error: string | null) => void) & ((event: "enter", event: KeyboardEvent) => void) & ((event: "validate", isValid: boolean, error: string | null) => void) & ((event: "select-suggestion", suggestion: string) => void);
83
+ $emit: ((event: "clear") => void) & ((event: "update:modelValue", value: string | number) => void) & ((event: "input", event: Event) => void) & ((event: "change", value: string | number, event: Event) => void) & ((event: "blur", event: FocusEvent) => void) & ((event: "focus", event: FocusEvent) => void) & ((event: "update:error", error: string | null) => void) & ((event: "enter", event: KeyboardEvent) => void) & ((event: "validate", isValid: boolean, error: string | null) => void) & ((event: "select-suggestion", suggestion: string) => void);
84
84
  $el: any;
85
85
  $options: import('vue').ComponentOptionsBase<Readonly<import('../../components/ui/SInput').Props> & Readonly<{
86
- "onUpdate:modelValue"?: ((value: string | number) => any) | undefined;
87
86
  onClear?: (() => any) | undefined;
87
+ "onUpdate:modelValue"?: ((value: string | number) => any) | undefined;
88
88
  onInput?: ((event: Event) => any) | undefined;
89
89
  onChange?: ((value: string | number, event: Event) => any) | undefined;
90
90
  onBlur?: ((event: FocusEvent) => any) | undefined;
@@ -99,8 +99,8 @@ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import
99
99
  validate: () => Promise<void>;
100
100
  inputElement: import('vue').Ref<HTMLInputElement | HTMLTextAreaElement | null, HTMLInputElement | HTMLTextAreaElement | null>;
101
101
  }, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
102
- "update:modelValue": (value: string | number) => any;
103
102
  clear: () => any;
103
+ "update:modelValue": (value: string | number) => any;
104
104
  input: (event: Event) => any;
105
105
  change: (value: string | number, event: Event) => any;
106
106
  blur: (event: FocusEvent) => any;
@@ -110,9 +110,9 @@ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import
110
110
  validate: (isValid: boolean, error: string | null) => any;
111
111
  "select-suggestion": (suggestion: string) => any;
112
112
  }, string, {
113
+ size: "small" | "medium" | "large";
113
114
  modelValue: string | number;
114
115
  type: "text" | "email" | "password" | "number" | "tel" | "url" | "search" | "textarea";
115
- size: "small" | "medium" | "large";
116
116
  disabled: boolean;
117
117
  required: boolean;
118
118
  color: string;
@@ -155,9 +155,9 @@ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import
155
155
  $nextTick: typeof import('vue').nextTick;
156
156
  $watch<T extends string | ((...args: any) => any)>(source: T, cb: T extends (...args: any) => infer R ? (...args: [R, R, import('@vue/reactivity').OnCleanup]) => any : (...args: [any, any, import('@vue/reactivity').OnCleanup]) => any, options?: import('vue').WatchOptions): import('vue').WatchStopHandle;
157
157
  } & Readonly<{
158
+ size: "small" | "medium" | "large";
158
159
  modelValue: string | number;
159
160
  type: "text" | "email" | "password" | "number" | "tel" | "url" | "search" | "textarea";
160
- size: "small" | "medium" | "large";
161
161
  disabled: boolean;
162
162
  required: boolean;
163
163
  color: string;
@@ -180,8 +180,8 @@ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import
180
180
  allowOnly: "digits" | "letters" | "alphanumeric" | RegExp | ((char: string) => boolean);
181
181
  decimalPlaces: number;
182
182
  }> & Omit<Readonly<import('../../components/ui/SInput').Props> & Readonly<{
183
- "onUpdate:modelValue"?: ((value: string | number) => any) | undefined;
184
183
  onClear?: (() => any) | undefined;
184
+ "onUpdate:modelValue"?: ((value: string | number) => any) | undefined;
185
185
  onInput?: ((event: Event) => any) | undefined;
186
186
  onChange?: ((value: string | number, event: Event) => any) | undefined;
187
187
  onBlur?: ((event: FocusEvent) => any) | undefined;
@@ -190,7 +190,7 @@ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import
190
190
  onEnter?: ((event: KeyboardEvent) => any) | undefined;
191
191
  onValidate?: ((isValid: boolean, error: string | null) => any) | undefined;
192
192
  "onSelect-suggestion"?: ((suggestion: string) => any) | undefined;
193
- }>, "blur" | "focus" | "validate" | "inputElement" | ("modelValue" | "type" | "size" | "disabled" | "required" | "color" | "variant" | "loading" | "rounded" | "resize" | "clearable" | "labelPlacement" | "readonly" | "labelAnimation" | "validateOn" | "autofocus" | "spellcheck" | "showPasswordToggle" | "counter" | "rows" | "suggestions" | "showSuggestionsOnFocus" | "allowOnly" | "decimalPlaces")> & import('vue').ShallowUnwrapRef<{
193
+ }>, "blur" | "focus" | "validate" | "inputElement" | ("size" | "modelValue" | "type" | "disabled" | "required" | "color" | "variant" | "loading" | "rounded" | "resize" | "clearable" | "labelPlacement" | "readonly" | "labelAnimation" | "validateOn" | "autofocus" | "spellcheck" | "showPasswordToggle" | "counter" | "rows" | "suggestions" | "showSuggestionsOnFocus" | "allowOnly" | "decimalPlaces")> & import('vue').ShallowUnwrapRef<{
194
194
  focus: () => void;
195
195
  blur: () => void;
196
196
  validate: () => Promise<void>;
@@ -98,8 +98,8 @@ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import
98
98
  complete: (value: string) => any;
99
99
  resend: () => any;
100
100
  }, string, {
101
- modelValue: string;
102
101
  size: import('../../components/ui/otp').SOTPSize;
102
+ modelValue: string;
103
103
  disabled: boolean;
104
104
  mode: import('../../components/ui/otp').SOTPMode;
105
105
  animation: import('../../components/ui/otp').SOTPAnimation;
@@ -148,8 +148,8 @@ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import
148
148
  $nextTick: typeof import('vue').nextTick;
149
149
  $watch<T extends string | ((...args: any) => any)>(source: T, cb: T extends (...args: any) => infer R ? (...args: [R, R, import('@vue/reactivity').OnCleanup]) => any : (...args: [any, any, import('@vue/reactivity').OnCleanup]) => any, options?: import('vue').WatchOptions): import('vue').WatchStopHandle;
150
150
  } & Readonly<{
151
- modelValue: string;
152
151
  size: import('../../components/ui/otp').SOTPSize;
152
+ modelValue: string;
153
153
  disabled: boolean;
154
154
  mode: import('../../components/ui/otp').SOTPMode;
155
155
  animation: import('../../components/ui/otp').SOTPAnimation;
@@ -188,7 +188,7 @@ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import
188
188
  onSuccess?: (() => any) | undefined;
189
189
  onComplete?: ((value: string) => any) | undefined;
190
190
  onResend?: (() => any) | undefined;
191
- }>, "clear" | "blur" | "focus" | "focusInput" | "triggerError" | "getValue" | "isComplete" | ("modelValue" | "size" | "disabled" | "mode" | "animation" | "color" | "variant" | "loading" | "rounded" | "separator" | "gap" | "readonly" | "maxlength" | "autoFocus" | "entryAnimation" | "inputAnimation" | "successAnimation" | "errorAnimation" | "morphText" | "morphDuration" | "showPlaceholder" | "placeholderChar" | "maskChar" | "autoSubmit" | "masked" | "clearOnError" | "allowPaste" | "countdown" | "resendText")> & import('vue').ShallowUnwrapRef<{
191
+ }>, "clear" | "blur" | "focus" | "focusInput" | "triggerError" | "getValue" | "isComplete" | ("size" | "modelValue" | "disabled" | "mode" | "animation" | "color" | "variant" | "loading" | "rounded" | "separator" | "gap" | "readonly" | "maxlength" | "autoFocus" | "entryAnimation" | "inputAnimation" | "successAnimation" | "errorAnimation" | "morphText" | "morphDuration" | "showPlaceholder" | "placeholderChar" | "maskChar" | "autoSubmit" | "masked" | "clearOnError" | "allowPaste" | "countdown" | "resendText")> & import('vue').ShallowUnwrapRef<{
192
192
  clear: () => void;
193
193
  focusInput: (index: number) => void;
194
194
  focus: () => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sakoa/ui",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "main": "./dist/saka-ui.umd.cjs",
6
6
  "module": "./dist/saka-ui.js",
@@ -2,6 +2,7 @@
2
2
  import { ref, computed, onMounted, onBeforeUnmount, watch } from 'vue'
3
3
  import { cva, type VariantProps } from 'class-variance-authority'
4
4
  import { cn } from '../../lib/utils'
5
+ import { type IconProp, isIconComponent } from '../../lib/icon'
5
6
 
6
7
  defineOptions({ inheritAttrs: false })
7
8
 
@@ -66,7 +67,7 @@ export interface Props {
66
67
  size?: 'small' | 'medium' | 'large'
67
68
  title?: string
68
69
  description?: string
69
- icon?: string | boolean
70
+ icon?: IconProp | boolean
70
71
  closable?: boolean
71
72
  autoDismiss?: boolean
72
73
  duration?: number
@@ -125,8 +126,10 @@ const defaultIcons = computed(() => ({
125
126
 
126
127
  const displayIcon = computed(() => {
127
128
  if (props.icon === false) return null
128
- if (typeof props.icon === 'string') return props.icon
129
- return defaultIcons.value[props.variant as keyof typeof defaultIcons.value] || 'information'
129
+ if (props.icon === true || props.icon === undefined) {
130
+ return defaultIcons.value[props.variant as keyof typeof defaultIcons.value] || 'information'
131
+ }
132
+ return props.icon // could be string or Component
130
133
  })
131
134
 
132
135
  const sizes = computed(() => sizeConfig[props.size])
@@ -302,9 +305,10 @@ defineExpose({
302
305
  >
303
306
  <div class="flex items-start" :class="sizes.gap">
304
307
  <!-- Icon -->
305
- <div v-if="displayIcon" class="shrink-0" :class="[variantIconColors[variant], iconClass]">
308
+ <div v-if="displayIcon != null" class="shrink-0" :class="[variantIconColors[variant], iconClass]">
306
309
  <slot name="icon">
307
- <span class="mdi" :class="[`mdi-${displayIcon}`, sizes.iconSize]" />
310
+ <component v-if="isIconComponent(displayIcon)" :is="displayIcon" :class="[sizes.iconSize]" />
311
+ <span v-else class="mdi" :class="[`mdi-${displayIcon}`, sizes.iconSize]" />
308
312
  </slot>
309
313
  </div>
310
314
 
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { cva, type VariantProps } from 'class-variance-authority'
3
+ import type { IconProp } from '../../lib/icon'
3
4
 
4
5
  export const buttonVariants = cva(
5
6
  'relative inline-flex items-center justify-center font-medium transition-all duration-200 ease-out overflow-hidden select-none focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 border-[1.5px] border-transparent',
@@ -46,8 +47,8 @@ export interface Props {
46
47
  preserveSize?: boolean
47
48
  block?: boolean
48
49
  rounded?: 'none' | 'sm' | 'md' | 'lg' | 'full'
49
- iconLeft?: string
50
- iconRight?: string
50
+ iconLeft?: IconProp
51
+ iconRight?: IconProp
51
52
  iconOnly?: boolean
52
53
  tag?: string
53
54
  href?: string
@@ -64,6 +65,7 @@ export interface Props {
64
65
  <script setup lang="ts">
65
66
  import { computed, ref, useSlots, type CSSProperties } from 'vue'
66
67
  import { cn } from '../../lib/utils'
68
+ import { isIconComponent } from '../../lib/icon'
67
69
 
68
70
  const iconOnlySizes: Record<string, string> = {
69
71
  xs: 'w-6 h-6 text-xs',
@@ -257,10 +259,10 @@ const componentBindings = computed(() => {
257
259
  >
258
260
  <template v-if="!loading || preserveSize">
259
261
  <slot name="icon-left">
260
- <span
261
- v-if="iconLeft"
262
- :class="['mdi', `mdi-${iconLeft}`, iconSizes, iconClass]"
263
- />
262
+ <template v-if="iconLeft">
263
+ <component v-if="isIconComponent(iconLeft)" :is="iconLeft" :class="[iconSizes, iconClass]" />
264
+ <span v-else :class="['mdi', `mdi-${iconLeft}`, iconSizes, iconClass]" />
265
+ </template>
264
266
  </slot>
265
267
  </template>
266
268
 
@@ -274,10 +276,10 @@ const componentBindings = computed(() => {
274
276
 
275
277
  <template v-if="!loading || preserveSize">
276
278
  <slot name="icon-right">
277
- <span
278
- v-if="iconRight"
279
- :class="['mdi', `mdi-${iconRight}`, iconSizes, iconClass]"
280
- />
279
+ <template v-if="iconRight">
280
+ <component v-if="isIconComponent(iconRight)" :is="iconRight" :class="[iconSizes, iconClass]" />
281
+ <span v-else :class="['mdi', `mdi-${iconRight}`, iconSizes, iconClass]" />
282
+ </template>
281
283
  </slot>
282
284
  </template>
283
285
  </span>
@@ -2,6 +2,7 @@
2
2
  import { computed, ref, type CSSProperties } from 'vue'
3
3
  import { cva, type VariantProps } from 'class-variance-authority'
4
4
  import { cn } from '../../lib/utils'
5
+ import { type IconProp, isIconComponent } from '../../lib/icon'
5
6
 
6
7
  defineOptions({ inheritAttrs: false })
7
8
 
@@ -34,7 +35,7 @@ export interface Props {
34
35
  label?: string
35
36
  labelPosition?: 'left' | 'right'
36
37
  rounded?: boolean
37
- icon?: string
38
+ icon?: IconProp
38
39
  required?: boolean
39
40
  name?: string
40
41
  error?: string
@@ -246,7 +247,8 @@ const rounded = computed(() => props.rounded)
246
247
  :class="cn('absolute inset-0 flex items-center justify-center', color ? 'text-white' : 'text-primary-foreground', sizeConfig.icon)"
247
248
  >
248
249
  <slot name="icon">
249
- <span :class="['mdi', `mdi-${displayIcon}`]" />
250
+ <component v-if="isIconComponent(displayIcon)" :is="displayIcon" />
251
+ <span v-else :class="['mdi', `mdi-${displayIcon}`]" />
250
252
  </slot>
251
253
  </span>
252
254
  </Transition>
@@ -2,6 +2,7 @@
2
2
  import { computed, ref, useSlots, onMounted, onUnmounted } from 'vue'
3
3
  import { useTheme } from '../../composables/useTheme'
4
4
  import { cn } from '../../lib/utils'
5
+ import { type IconProp, isIconComponent } from '../../lib/icon'
5
6
 
6
7
  defineOptions({ inheritAttrs: false })
7
8
 
@@ -13,8 +14,8 @@ export interface Props {
13
14
  preserveSize?: boolean
14
15
  block?: boolean
15
16
  rounded?: 'none' | 'sm' | 'md' | 'lg' | 'full'
16
- iconLeft?: string
17
- iconRight?: string
17
+ iconLeft?: IconProp
18
+ iconRight?: IconProp
18
19
  iconOnly?: boolean
19
20
  tag?: string
20
21
  href?: string
@@ -356,10 +357,10 @@ const componentBindings = computed(() => {
356
357
  <!-- Left icon -->
357
358
  <template v-if="!loading || preserveSize">
358
359
  <slot name="icon-left">
359
- <span
360
- v-if="iconLeft"
361
- :class="['mdi', `mdi-${iconLeft}`, iconSizes]"
362
- />
360
+ <template v-if="iconLeft">
361
+ <component v-if="isIconComponent(iconLeft)" :is="iconLeft" :class="[iconSizes]" />
362
+ <span v-else :class="['mdi', `mdi-${iconLeft}`, iconSizes]" />
363
+ </template>
363
364
  </slot>
364
365
  </template>
365
366
 
@@ -376,10 +377,10 @@ const componentBindings = computed(() => {
376
377
  <!-- Right icon -->
377
378
  <template v-if="!loading || preserveSize">
378
379
  <slot name="icon-right">
379
- <span
380
- v-if="iconRight"
381
- :class="['mdi', `mdi-${iconRight}`, iconSizes]"
382
- />
380
+ <template v-if="iconRight">
381
+ <component v-if="isIconComponent(iconRight)" :is="iconRight" :class="[iconSizes]" />
382
+ <span v-else :class="['mdi', `mdi-${iconRight}`, iconSizes]" />
383
+ </template>
383
384
  </slot>
384
385
  </template>
385
386
  </span>
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import { ref, computed, watch, nextTick, onMounted, type CSSProperties } from 'vue'
3
3
  import { cn } from '../../lib/utils'
4
+ import { type IconProp, isIconComponent } from '../../lib/icon'
4
5
 
5
6
  defineOptions({ inheritAttrs: false })
6
7
 
@@ -23,8 +24,8 @@ export interface Props {
23
24
  labelAnimation?: 'morph' | 'slide' | 'fade' | 'none'
24
25
 
25
26
  // Icons
26
- iconLeft?: string
27
- iconRight?: string
27
+ iconLeft?: IconProp
28
+ iconRight?: IconProp
28
29
  iconColor?: string
29
30
 
30
31
  // States
@@ -743,7 +744,8 @@ watch(() => props.error, (newError) => {
743
744
  :style="iconColor ? { color: iconColor } : iconFocusStyle"
744
745
  >
745
746
  <slot name="prefix">
746
- <span v-if="iconLeft" :class="['mdi', `mdi-${iconLeft}`]" />
747
+ <component v-if="iconLeft && isIconComponent(iconLeft)" :is="iconLeft" />
748
+ <span v-else-if="iconLeft" :class="['mdi', `mdi-${iconLeft}`]" />
747
749
  <span v-if="prefix" class="text-muted-foreground text-sm">{{ prefix }}</span>
748
750
  </slot>
749
751
  </span>
@@ -905,8 +907,14 @@ watch(() => props.error, (newError) => {
905
907
 
906
908
  <!-- Custom suffix -->
907
909
  <slot name="suffix">
910
+ <component
911
+ v-if="iconRight && isIconComponent(iconRight)"
912
+ :is="iconRight"
913
+ :class="[iconColorClass]"
914
+ :style="iconColor ? { color: iconColor } : iconFocusStyle"
915
+ />
908
916
  <span
909
- v-if="iconRight"
917
+ v-else-if="iconRight"
910
918
  :class="['mdi', `mdi-${iconRight}`, iconColorClass]"
911
919
  :style="iconColor ? { color: iconColor } : iconFocusStyle"
912
920
  />
@@ -3,12 +3,13 @@ defineOptions({ inheritAttrs: false })
3
3
 
4
4
  import { ref, computed, watch, provide, onMounted, onBeforeUnmount, nextTick, type CSSProperties } from 'vue'
5
5
  import { cn } from '../../lib/utils'
6
+ import { type IconProp, isIconComponent } from '../../lib/icon'
6
7
 
7
8
  export interface SelectOption {
8
9
  value: any
9
10
  label?: string
10
11
  disabled?: boolean
11
- icon?: string
12
+ icon?: IconProp
12
13
  image?: string
13
14
  description?: string
14
15
  color?: string
@@ -40,7 +41,7 @@ export interface Props {
40
41
  teleport?: boolean | string
41
42
  placement?: 'bottom' | 'top' | 'auto'
42
43
  // New props
43
- arrowIcon?: string
44
+ arrowIcon?: IconProp
44
45
  menuWidth?: string | number
45
46
  menuAlign?: 'start' | 'end' | 'center'
46
47
  creatable?: boolean
@@ -872,6 +873,7 @@ const resolvedColor = computed(() => props.color ?? 'var(--s-primary)')
872
873
  :alt="selectedOptions[0].label"
873
874
  class="w-5 h-5 rounded-full object-cover shrink-0"
874
875
  />
876
+ <component v-else-if="selectedOptions[0]?.icon && isIconComponent(selectedOptions[0].icon)" :is="selectedOptions[0].icon" class="text-muted-foreground" />
875
877
  <span v-else-if="selectedOptions[0]?.icon" :class="['mdi', `mdi-${selectedOptions[0].icon}`, 'text-muted-foreground']" />
876
878
  <span class="truncate text-foreground">{{ displayValue }}</span>
877
879
  </slot>
@@ -907,8 +909,14 @@ const resolvedColor = computed(() => props.color ?? 'var(--s-primary)')
907
909
 
908
910
  <!-- Dropdown arrow -->
909
911
  <slot name="arrow" :is-open="isOpen">
910
- <span
911
- v-if="!loading"
912
+ <component
913
+ v-if="!loading && isIconComponent(arrowIcon)"
914
+ :is="arrowIcon"
915
+ class="text-muted-foreground transition-transform duration-200 shrink-0"
916
+ :class="[sizeConfig.icon, { 'rotate-180': isOpen }]"
917
+ />
918
+ <span
919
+ v-else-if="!loading"
912
920
  class="text-muted-foreground transition-transform duration-200 shrink-0"
913
921
  :class="['mdi', `mdi-${arrowIcon}`, sizeConfig.icon, { 'rotate-180': isOpen }]"
914
922
  />
@@ -1030,8 +1038,15 @@ const resolvedColor = computed(() => props.color ?? 'var(--s-primary)')
1030
1038
  class="relative z-10 w-6 h-6 rounded-full object-cover shrink-0 mr-2.5"
1031
1039
  />
1032
1040
  <!-- Icon -->
1033
- <span
1034
- v-else-if="option.icon"
1041
+ <component
1042
+ v-else-if="option.icon && isIconComponent(option.icon)"
1043
+ :is="option.icon"
1044
+ class="relative z-10 shrink-0 mr-2.5"
1045
+ :class="[sizeConfig.icon]"
1046
+ :style="isSelected(option.value) ? { color: option.color ?? resolvedColor } : {}"
1047
+ />
1048
+ <span
1049
+ v-else-if="option.icon"
1035
1050
  class="relative z-10 shrink-0 mr-2.5"
1036
1051
  :class="['mdi', `mdi-${option.icon}`, sizeConfig.icon]"
1037
1052
  :style="isSelected(option.value) ? { color: option.color ?? resolvedColor } : {}"
@@ -1098,8 +1113,15 @@ const resolvedColor = computed(() => props.color ?? 'var(--s-primary)')
1098
1113
  class="relative z-10 w-6 h-6 rounded-full object-cover shrink-0 mr-2.5"
1099
1114
  />
1100
1115
  <!-- Icon -->
1101
- <span
1102
- v-else-if="option.icon"
1116
+ <component
1117
+ v-else-if="option.icon && isIconComponent(option.icon)"
1118
+ :is="option.icon"
1119
+ class="relative z-10 shrink-0 mr-2.5"
1120
+ :class="[sizeConfig.icon]"
1121
+ :style="isSelected(option.value) ? { color: option.color ?? resolvedColor } : {}"
1122
+ />
1123
+ <span
1124
+ v-else-if="option.icon"
1103
1125
  class="relative z-10 shrink-0 mr-2.5"
1104
1126
  :class="['mdi', `mdi-${option.icon}`, sizeConfig.icon]"
1105
1127
  :style="isSelected(option.value) ? { color: option.color ?? resolvedColor } : {}"
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, useAttrs, type CSSProperties } from 'vue'
3
3
  import { cn } from '../../lib/utils'
4
+ import { type IconProp, isIconComponent } from '../../lib/icon'
4
5
 
5
6
  defineOptions({ inheritAttrs: false })
6
7
 
@@ -17,8 +18,8 @@ export interface Props {
17
18
  uncheckedValue?: boolean | string | number
18
19
  labelBefore?: string
19
20
  labelAfter?: string
20
- checkedIcon?: string
21
- uncheckedIcon?: string
21
+ checkedIcon?: IconProp
22
+ uncheckedIcon?: IconProp
22
23
  checkedText?: string
23
24
  uncheckedText?: string
24
25
  trackClass?: string
@@ -232,16 +233,18 @@ const thumbClasses = computed(() => {
232
233
  <!-- Custom Icons via Slots -->
233
234
  <template v-else>
234
235
  <slot v-if="isChecked" name="checked-icon">
236
+ <component v-if="checkedIcon && isIconComponent(checkedIcon)" :is="checkedIcon" :class="[sizeConfig.icon]" :style="checkedIconStyle" />
235
237
  <span
236
- v-if="checkedIcon"
238
+ v-else-if="checkedIcon"
237
239
  :class="['mdi', `mdi-${checkedIcon}`, sizeConfig.icon]"
238
240
  :style="checkedIconStyle"
239
241
  :data-no-color="!color ? '' : undefined"
240
242
  ></span>
241
243
  </slot>
242
244
  <slot v-else name="unchecked-icon">
245
+ <component v-if="uncheckedIcon && isIconComponent(uncheckedIcon)" :is="uncheckedIcon" :class="[sizeConfig.icon, 'text-muted-foreground']" />
243
246
  <span
244
- v-if="uncheckedIcon"
247
+ v-else-if="uncheckedIcon"
245
248
  :class="['mdi', `mdi-${uncheckedIcon}`, sizeConfig.icon, 'text-muted-foreground']"
246
249
  ></span>
247
250
  </slot>
@@ -3,6 +3,7 @@ defineOptions({ inheritAttrs: false })
3
3
 
4
4
  import { inject, ref, computed, onMounted, onBeforeUnmount, watch, nextTick, provide, useSlots } from 'vue'
5
5
  import { cn } from '../../../lib/utils'
6
+ import { type IconProp, isIconComponent } from '../../../lib/icon'
6
7
  import { SAccordionContextKey, SAccordionItemContextKey, type SAccordionContext } from './SAccordion.vue'
7
8
 
8
9
  export interface Props {
@@ -12,10 +13,10 @@ export interface Props {
12
13
  title?: string
13
14
  /** Optional subtitle text */
14
15
  subtitle?: string
15
- /** MDI icon name for header */
16
- icon?: string
17
- /** Custom expand icon (MDI icon name, replaces default chevron) */
18
- expandIcon?: string
16
+ /** MDI icon name or Vue component for header */
17
+ icon?: IconProp
18
+ /** Custom expand icon (MDI icon name or Vue component, replaces default chevron) */
19
+ expandIcon?: IconProp
19
20
  /** Disable this item */
20
21
  disabled?: boolean
21
22
  /** Lazy render content */
@@ -272,8 +273,14 @@ provide(SAccordionItemContextKey, {
272
273
  :disabled="disabled"
273
274
  >
274
275
  <!-- Icon -->
276
+ <component
277
+ v-if="icon && isIconComponent(icon)"
278
+ :is="icon"
279
+ :class="[sizeConfig.icon, 'shrink-0 text-muted-foreground']"
280
+ :style="isExpanded ? { color: context?.color } : {}"
281
+ />
275
282
  <span
276
- v-if="icon"
283
+ v-else-if="icon"
277
284
  :class="['mdi', `mdi-${icon}`, sizeConfig.icon, 'shrink-0 text-muted-foreground']"
278
285
  :style="isExpanded ? { color: context?.color } : {}"
279
286
  />
@@ -304,7 +311,14 @@ provide(SAccordionItemContextKey, {
304
311
 
305
312
  <!-- Expand Icon -->
306
313
  <slot name="icon" :expanded="isExpanded">
314
+ <component
315
+ v-if="expandIcon && isIconComponent(expandIcon)"
316
+ :is="expandIcon"
317
+ :class="[sizeConfig.expandIcon, 'text-muted-foreground shrink-0']"
318
+ :style="expandIconStyle"
319
+ />
307
320
  <span
321
+ v-else
308
322
  :class="['mdi shrink-0', `mdi-${expandIcon || 'chevron-down'}`, sizeConfig.expandIcon, 'text-muted-foreground']"
309
323
  :style="expandIconStyle"
310
324
  />
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, inject } from 'vue'
3
3
  import { cn } from '../../../lib/utils'
4
+ import { type IconProp, isIconComponent } from '../../../lib/icon'
4
5
  import { cardContextKey, type CardContext } from './index'
5
6
 
6
7
  defineOptions({ inheritAttrs: false })
@@ -14,8 +15,8 @@ export interface Props {
14
15
  avatar?: string
15
16
  /** Avatar fallback text (initials) */
16
17
  avatarFallback?: string
17
- /** Icon name (MDI) */
18
- icon?: string
18
+ /** Icon name (MDI) or Vue component */
19
+ icon?: IconProp
19
20
  /** Icon color */
20
21
  iconColor?: string
21
22
  /** Show divider below header */
@@ -109,11 +110,13 @@ const computedStyle = computed(() => {
109
110
  </div>
110
111
 
111
112
  <!-- Icon -->
112
- <div
113
- v-else-if="icon"
113
+ <div
114
+ v-else-if="icon"
114
115
  class="w-10 h-10 rounded-xl bg-primary/15 flex items-center justify-center"
115
116
  >
116
- <span
117
+ <component v-if="isIconComponent(icon)" :is="icon" :style="{ color: iconColor }" />
118
+ <span
119
+ v-else
117
120
  class="mdi text-xl"
118
121
  :class="`mdi-${icon}`"
119
122
  :style="{ color: iconColor }"