@sabrenski/spire-ui 0.0.6 → 0.0.7

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 (86) hide show
  1. package/dist/index.d.ts +167 -4
  2. package/dist/spire-ui.css +1 -1
  3. package/dist/spire-ui.es.js +7005 -6741
  4. package/dist/spire-ui.umd.js +10 -10
  5. package/package.json +83 -70
  6. package/src/components/Accordion/AccordionContent.vue +5 -2
  7. package/src/components/Accordion/AccordionItem.vue +4 -0
  8. package/src/components/Accordion/AccordionRoot.vue +4 -2
  9. package/src/components/Accordion/AccordionTrigger.vue +4 -1
  10. package/src/components/Avatar/Avatar.vue +4 -0
  11. package/src/components/Badge/Badge.vue +4 -0
  12. package/src/components/BadgeContainer/BadgeContainer.vue +4 -1
  13. package/src/components/Breadcrumb/BreadcrumbLink.vue +4 -1
  14. package/src/components/Breadcrumb/BreadcrumbRoot.vue +4 -1
  15. package/src/components/Button/Button.vue +5 -1
  16. package/src/components/Callout/Callout.vue +4 -0
  17. package/src/components/Card/Card.vue +5 -1
  18. package/src/components/Card/CardContent.vue +5 -1
  19. package/src/components/Card/CardFooter.vue +5 -1
  20. package/src/components/Card/CardHeader.vue +5 -1
  21. package/src/components/Card/CardImage.vue +4 -2
  22. package/src/components/Chart/BarChart.vue +4 -0
  23. package/src/components/Chart/BaseChart.vue +52 -47
  24. package/src/components/Chart/DonutChart.vue +4 -2
  25. package/src/components/Chart/LineChart.vue +4 -0
  26. package/src/components/Checkbox/Checkbox.test.ts +94 -0
  27. package/src/components/Checkbox/Checkbox.vue +170 -1
  28. package/src/components/ChoiceChip/ChoiceChip.vue +11 -5
  29. package/src/components/ChoiceChipGroup/ChoiceChipGroup.vue +4 -2
  30. package/src/components/ColorPicker/ColorArea.vue +4 -2
  31. package/src/components/ColorPicker/ColorPicker.vue +4 -2
  32. package/src/components/ColorPicker/ColorSlider.vue +5 -1
  33. package/src/components/Combobox/Combobox.vue +97 -91
  34. package/src/components/DataTable/DataTable.vue +5 -1
  35. package/src/components/DatePicker/DatePicker.vue +5 -1
  36. package/src/components/Drawer/Drawer.vue +4 -1
  37. package/src/components/Dropdown/Dropdown.vue +4 -2
  38. package/src/components/Dropdown/DropdownItem.vue +4 -0
  39. package/src/components/Dropdown/DropdownSubTrigger.vue +4 -2
  40. package/src/components/EmptyState/EmptyState.vue +5 -1
  41. package/src/components/FileUpload/FileUpload.vue +12 -6
  42. package/src/components/Heading/Heading.vue +4 -0
  43. package/src/components/Icon/Icon.vue +5 -2
  44. package/src/components/Input/Input.vue +5 -1
  45. package/src/components/Layout/Container.vue +4 -0
  46. package/src/components/Layout/Grid.vue +4 -1
  47. package/src/components/Layout/GridItem.vue +4 -1
  48. package/src/components/Layout/Stack.vue +4 -0
  49. package/src/components/Modal/Modal.test.ts +68 -13
  50. package/src/components/Modal/Modal.vue +94 -91
  51. package/src/components/Pagination/Pagination.vue +5 -1
  52. package/src/components/Popover/Popover.vue +4 -1
  53. package/src/components/Progress/Progress.vue +5 -0
  54. package/src/components/Radio/Radio.test.ts +88 -0
  55. package/src/components/Radio/Radio.vue +169 -1
  56. package/src/components/Rating/Rating.vue +5 -1
  57. package/src/components/SegmentedControl/SegmentedControl.vue +5 -1
  58. package/src/components/Select/Select.vue +61 -55
  59. package/src/components/Sidebar/SidebarGroup.vue +4 -0
  60. package/src/components/Sidebar/SidebarItem.vue +4 -0
  61. package/src/components/Sidebar/SidebarLayout.vue +5 -2
  62. package/src/components/Sidebar/SidebarRoot.vue +4 -2
  63. package/src/components/Skeleton/Skeleton.vue +5 -1
  64. package/src/components/Slider/Slider.vue +5 -1
  65. package/src/components/Spinner/Spinner.vue +4 -1
  66. package/src/components/SpireProvider/SpireProvider.vue +4 -1
  67. package/src/components/Stepper/StepperItem.vue +4 -0
  68. package/src/components/Stepper/StepperRoot.vue +4 -2
  69. package/src/components/Stepper/StepperTrigger.vue +6 -2
  70. package/src/components/Switch/Switch.vue +5 -1
  71. package/src/components/Tabs/Tabs.vue +4 -1
  72. package/src/components/Text/Text.vue +4 -0
  73. package/src/components/Textarea/Textarea.vue +13 -7
  74. package/src/components/TimePicker/TimePicker.vue +5 -1
  75. package/src/components/Timeline/Timeline.vue +4 -0
  76. package/src/components/Timeline/TimelineItem.vue +4 -0
  77. package/src/components/Toast/ToastItem.vue +5 -1
  78. package/src/components/Toast/ToastProvider.vue +5 -3
  79. package/src/components/ToggleButton/ToggleButton.vue +5 -1
  80. package/src/components/ToggleGroup/ToggleGroup.vue +5 -1
  81. package/src/components/Tooltip/Tooltip.vue +9 -1
  82. package/src/components/TreeView/TreeView.vue +4 -1
  83. package/src/components/TreeView/TreeViewItem.vue +4 -0
  84. package/src/index.ts +3 -0
  85. package/src/styles/main.css +21 -21
  86. package/src/types/common.ts +4 -0
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import { ref, computed, watch, nextTick, onBeforeUnmount } from 'vue'
3
3
  import { useId } from '../../composables'
4
+ import type { ClassValue } from '../../types/common'
4
5
 
5
6
  export interface ComboboxOption {
6
7
  /** Display text shown to user */
@@ -12,6 +13,8 @@ export interface ComboboxOption {
12
13
  }
13
14
 
14
15
  export interface ComboboxProps {
16
+ /** Additional CSS classes */
17
+ class?: ClassValue
15
18
  /** Selected value(s) - single value or array for multiple */
16
19
  modelValue?: string | number | (string | number)[] | null
17
20
  /** Available options */
@@ -411,8 +414,9 @@ onBeforeUnmount(() => {
411
414
 
412
415
  <template>
413
416
  <div
414
- class="ui-combobox"
415
417
  :class="[
418
+ props.class,
419
+ 'ui-combobox',
416
420
  `ui-combobox--${size}`,
417
421
  {
418
422
  'ui-combobox--block': block,
@@ -827,108 +831,110 @@ onBeforeUnmount(() => {
827
831
  </style>
828
832
 
829
833
  <style>
830
- .ui-combobox__listbox {
831
- z-index: 9999;
832
- margin: 0;
833
- padding: var(--space-1);
834
- list-style: none;
835
- background-color: var(--select-menu-bg);
836
- border: 1px solid var(--select-menu-border);
837
- border-radius: var(--radius-lg);
838
- box-shadow: var(--shadow-lg);
839
- max-height: 256px;
840
- overflow-y: auto;
841
- overscroll-behavior: contain;
842
- }
843
-
844
- .ui-combobox__option {
845
- display: flex;
846
- align-items: center;
847
- gap: var(--space-2);
848
- padding: var(--space-2) var(--space-3);
849
- border-radius: var(--radius-md);
850
- font-family: var(--font-sans);
851
- font-size: var(--text-sm);
852
- color: var(--select-option-text);
853
- cursor: pointer;
854
- transition: background-color var(--duration-fast) var(--ease-default);
855
- }
834
+ @layer spire-components {
835
+ .ui-combobox__listbox {
836
+ z-index: 9999;
837
+ margin: 0;
838
+ padding: var(--space-1);
839
+ list-style: none;
840
+ background-color: var(--select-menu-bg);
841
+ border: 1px solid var(--select-menu-border);
842
+ border-radius: var(--radius-lg);
843
+ box-shadow: var(--shadow-lg);
844
+ max-height: 256px;
845
+ overflow-y: auto;
846
+ overscroll-behavior: contain;
847
+ }
856
848
 
857
- .ui-combobox__option--highlighted {
858
- background-color: var(--select-option-hover);
859
- }
849
+ .ui-combobox__option {
850
+ display: flex;
851
+ align-items: center;
852
+ gap: var(--space-2);
853
+ padding: var(--space-2) var(--space-3);
854
+ border-radius: var(--radius-md);
855
+ font-family: var(--font-sans);
856
+ font-size: var(--text-sm);
857
+ color: var(--select-option-text);
858
+ cursor: pointer;
859
+ transition: background-color var(--duration-fast) var(--ease-default);
860
+ }
860
861
 
861
- .ui-combobox__option--selected {
862
- color: var(--select-option-selected);
863
- font-weight: var(--font-medium);
864
- }
862
+ .ui-combobox__option--highlighted {
863
+ background-color: var(--select-option-hover);
864
+ }
865
865
 
866
- .ui-combobox__option--disabled {
867
- opacity: 0.5;
868
- cursor: not-allowed;
869
- }
866
+ .ui-combobox__option--selected {
867
+ color: var(--select-option-selected);
868
+ font-weight: var(--font-medium);
869
+ }
870
870
 
871
- .ui-combobox__option--create {
872
- color: var(--action-primary);
873
- }
871
+ .ui-combobox__option--disabled {
872
+ opacity: 0.5;
873
+ cursor: not-allowed;
874
+ }
874
875
 
875
- .ui-combobox__checkbox {
876
- display: flex;
877
- align-items: center;
878
- justify-content: center;
879
- width: 1rem;
880
- height: 1rem;
881
- border: 1.5px solid var(--checkbox-border);
882
- border-radius: var(--radius-sm);
883
- background-color: var(--checkbox-bg);
884
- flex-shrink: 0;
885
- }
876
+ .ui-combobox__option--create {
877
+ color: var(--action-primary);
878
+ }
886
879
 
887
- .ui-combobox__option--selected .ui-combobox__checkbox {
888
- background-color: var(--checkbox-checked-bg);
889
- border-color: var(--checkbox-checked-bg);
890
- color: var(--checkbox-check);
891
- }
880
+ .ui-combobox__checkbox {
881
+ display: flex;
882
+ align-items: center;
883
+ justify-content: center;
884
+ width: 1rem;
885
+ height: 1rem;
886
+ border: 1.5px solid var(--checkbox-border);
887
+ border-radius: var(--radius-sm);
888
+ background-color: var(--checkbox-bg);
889
+ flex-shrink: 0;
890
+ }
892
891
 
893
- .ui-combobox__checkbox svg {
894
- width: 0.75rem;
895
- height: 0.75rem;
896
- }
892
+ .ui-combobox__option--selected .ui-combobox__checkbox {
893
+ background-color: var(--checkbox-checked-bg);
894
+ border-color: var(--checkbox-checked-bg);
895
+ color: var(--checkbox-check);
896
+ }
897
897
 
898
- .ui-combobox__option-label {
899
- flex: 1;
900
- }
898
+ .ui-combobox__checkbox svg {
899
+ width: 0.75rem;
900
+ height: 0.75rem;
901
+ }
901
902
 
902
- .ui-combobox__option-label mark {
903
- background-color: transparent;
904
- color: var(--action-primary);
905
- font-weight: var(--font-semibold);
906
- }
903
+ .ui-combobox__option-label {
904
+ flex: 1;
905
+ }
907
906
 
908
- .ui-combobox__check {
909
- flex-shrink: 0;
910
- width: 1rem;
911
- height: 1rem;
912
- color: var(--select-option-selected);
913
- }
907
+ .ui-combobox__option-label mark {
908
+ background-color: transparent;
909
+ color: var(--action-primary);
910
+ font-weight: var(--font-semibold);
911
+ }
914
912
 
915
- .ui-combobox__empty {
916
- padding: var(--space-3);
917
- text-align: center;
918
- font-size: var(--text-sm);
919
- color: var(--input-placeholder);
920
- }
913
+ .ui-combobox__check {
914
+ flex-shrink: 0;
915
+ width: 1rem;
916
+ height: 1rem;
917
+ color: var(--select-option-selected);
918
+ }
921
919
 
922
- .ui-combobox-menu-enter-active,
923
- .ui-combobox-menu-leave-active {
924
- transition:
925
- opacity var(--duration-fast) var(--ease-default),
926
- transform var(--duration-fast) var(--ease-default);
927
- }
920
+ .ui-combobox__empty {
921
+ padding: var(--space-3);
922
+ text-align: center;
923
+ font-size: var(--text-sm);
924
+ color: var(--input-placeholder);
925
+ }
928
926
 
929
- .ui-combobox-menu-enter-from,
930
- .ui-combobox-menu-leave-to {
931
- opacity: 0;
932
- transform: translateY(-4px);
927
+ .ui-combobox-menu-enter-active,
928
+ .ui-combobox-menu-leave-active {
929
+ transition:
930
+ opacity var(--duration-fast) var(--ease-default),
931
+ transform var(--duration-fast) var(--ease-default);
932
+ }
933
+
934
+ .ui-combobox-menu-enter-from,
935
+ .ui-combobox-menu-leave-to {
936
+ opacity: 0;
937
+ transform: translateY(-4px);
938
+ }
933
939
  }
934
940
  </style>
@@ -10,6 +10,7 @@ import Input from '../Input/Input.vue'
10
10
  import { getNestedValue } from '../../utils/object'
11
11
  import { useInternalIcon } from '../../config/icons'
12
12
  import { useId } from '../../composables'
13
+ import type { ClassValue } from '../../types/common'
13
14
 
14
15
  const skeletonWidths = ['60%', '75%', '90%', '70%', '85%', '65%', '80%', '55%']
15
16
  function getSkeletonWidth(rowIndex: number, colIndex: number): string {
@@ -66,6 +67,8 @@ export interface PaginationConfig {
66
67
  }
67
68
 
68
69
  export interface DataTableProps {
70
+ /** Additional CSS classes */
71
+ class?: ClassValue
69
72
  /** Data array */
70
73
  data: Record<string, unknown>[]
71
74
  /** Column definitions */
@@ -548,8 +551,9 @@ const skeletonArray = computed(() => Array.from({ length: props.skeletonRows }))
548
551
  <template>
549
552
  <div
550
553
  ref="tableRef"
551
- class="ui-table"
552
554
  :class="[
555
+ props.class,
556
+ 'ui-table',
553
557
  `ui-table--${size}`,
554
558
  {
555
559
  'ui-table--striped': striped,
@@ -20,10 +20,13 @@ import {
20
20
  type CalendarDay,
21
21
  type RangeState
22
22
  } from '../../utils'
23
+ import type { ClassValue } from '../../types/common'
23
24
 
24
25
  export type ViewMode = 'day' | 'month' | 'year'
25
26
 
26
27
  export interface DatePickerProps {
28
+ /** Additional CSS classes */
29
+ class?: ClassValue
27
30
  /** Selected date in ISO format (single mode) or [start, end] tuple (range mode) */
28
31
  modelValue?: string | [string, string]
29
32
  /** Selection mode */
@@ -602,8 +605,9 @@ defineExpose({
602
605
 
603
606
  <template>
604
607
  <div
605
- class="ui-datepicker-field"
606
608
  :class="[
609
+ props.class,
610
+ 'ui-datepicker-field',
607
611
  `ui-datepicker-field--${size}`,
608
612
  {
609
613
  'ui-datepicker-field--block': block,
@@ -8,12 +8,15 @@ import {
8
8
  nextTick
9
9
  } from 'vue'
10
10
  import { useId, useScrollLock } from '../../composables'
11
+ import type { ClassValue } from '../../types/common'
11
12
 
12
13
  export type DrawerPlacement = 'right' | 'left' | 'bottom'
13
14
  export type DrawerVariant = 'default' | 'floating'
14
15
  export type DrawerSize = 'sm' | 'md' | 'lg' | 'xl' | 'full'
15
16
 
16
17
  export interface DrawerProps {
18
+ /** Additional CSS classes */
19
+ class?: ClassValue
17
20
  /** Controls visibility (v-model) */
18
21
  modelValue?: boolean
19
22
  /** Drawer title */
@@ -184,7 +187,7 @@ defineExpose({
184
187
  <Transition :name="transitionName">
185
188
  <div
186
189
  v-if="isOpen"
187
- :class="['ui-drawer', { 'ui-drawer--force-placement': forcePlacement }]"
190
+ :class="[props.class, 'ui-drawer', { 'ui-drawer--force-placement': forcePlacement }]"
188
191
  @keydown="handleKeydown"
189
192
  >
190
193
  <div
@@ -1,11 +1,14 @@
1
1
  <script lang="ts">
2
2
  import type { InjectionKey, Ref } from 'vue'
3
+ import type { ClassValue } from '../../types/common'
3
4
 
4
5
  export type DropdownPlacement =
5
6
  | 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end'
6
7
  | 'right-start' | 'right-end' | 'left-start' | 'left-end'
7
8
 
8
9
  export interface DropdownProps {
10
+ /** Additional CSS classes */
11
+ class?: ClassValue
9
12
  /** Menu placement relative to trigger */
10
13
  placement?: DropdownPlacement
11
14
  /** Offset from trigger (px) */
@@ -585,8 +588,7 @@ const showMobileDrilldown = computed(() => {
585
588
 
586
589
  <template>
587
590
  <div
588
- class="ui-dropdown"
589
- :class="{ 'ui-dropdown--submenu': isSubmenu }"
591
+ :class="[props.class, 'ui-dropdown', { 'ui-dropdown--submenu': isSubmenu }]"
590
592
  >
591
593
  <div
592
594
  ref="triggerRef"
@@ -8,8 +8,11 @@ import {
8
8
  type Component
9
9
  } from 'vue'
10
10
  import { dropdownContextKey, type DropdownContext } from './Dropdown.vue'
11
+ import type { ClassValue } from '../../types/common'
11
12
 
12
13
  export interface DropdownItemProps {
14
+ /** Additional CSS classes */
15
+ class?: ClassValue
13
16
  /** Renders as RouterLink when provided */
14
17
  to?: string | Record<string, unknown>
15
18
  /** Renders as anchor when provided */
@@ -51,6 +54,7 @@ const componentType = computed(() => {
51
54
  const componentProps = computed(() => {
52
55
  const base: Record<string, unknown> = {
53
56
  class: [
57
+ props.class,
54
58
  'ui-dropdown-item',
55
59
  {
56
60
  'ui-dropdown-item--disabled': props.disabled,
@@ -8,8 +8,11 @@ import {
8
8
  } from 'vue'
9
9
  import { dropdownContextKey } from './Dropdown.vue'
10
10
  import { dropdownSubContextKey } from './DropdownSub.vue'
11
+ import type { ClassValue } from '../../types/common'
11
12
 
12
13
  export interface DropdownSubTriggerProps {
14
+ /** Additional CSS classes */
15
+ class?: ClassValue
13
16
  /** Disabled state */
14
17
  disabled?: boolean
15
18
  /** Leading icon */
@@ -60,8 +63,7 @@ onUnmounted(() => {
60
63
  <button
61
64
  ref="triggerRef"
62
65
  type="button"
63
- class="ui-dropdown-sub-trigger"
64
- :class="{ 'ui-dropdown-sub-trigger--disabled': disabled }"
66
+ :class="[props.class, 'ui-dropdown-sub-trigger', { 'ui-dropdown-sub-trigger--disabled': disabled }]"
65
67
  role="menuitem"
66
68
  :aria-haspopup="true"
67
69
  :aria-expanded="subContext?.isOpen.value"
@@ -3,10 +3,13 @@ import { computed, useSlots } from 'vue'
3
3
  import Icon from '../Icon/Icon.vue'
4
4
  import type { IconInput } from '../Icon/Icon.vue'
5
5
  import { useInternalIcon } from '../../config/icons'
6
+ import type { ClassValue } from '../../types/common'
6
7
 
7
8
  export type EmptyStateVariant = 'default' | 'search' | 'error'
8
9
 
9
10
  export interface EmptyStateProps {
11
+ /** Additional CSS classes */
12
+ class?: ClassValue
10
13
  /** Title text */
11
14
  title?: string
12
15
  /** Description text */
@@ -43,8 +46,9 @@ const hasAction = computed(() => !!slots.default)
43
46
 
44
47
  <template>
45
48
  <div
46
- class="ui-empty-state"
47
49
  :class="[
50
+ props.class,
51
+ 'ui-empty-state',
48
52
  `ui-empty-state--${variant}`,
49
53
  { 'ui-empty-state--compact': compact }
50
54
  ]"
@@ -2,6 +2,7 @@
2
2
  import { ref, computed, onMounted, onBeforeUnmount, watch } from 'vue'
3
3
  import Button from '../Button/Button.vue'
4
4
  import { useId } from '../../composables'
5
+ import type { ClassValue } from '../../types/common'
5
6
 
6
7
  export interface UploadFile {
7
8
  /** Unique identifier */
@@ -25,6 +26,8 @@ export interface UploadFile {
25
26
  }
26
27
 
27
28
  export interface FileUploadProps {
29
+ /** Additional CSS classes */
30
+ class?: ClassValue
28
31
  /** Currently selected files (v-model) */
29
32
  modelValue?: UploadFile[]
30
33
  /** Accepted file types (MIME types or extensions) */
@@ -479,12 +482,15 @@ function getFileIcon(type: string): string {
479
482
 
480
483
  <template>
481
484
  <div
482
- class="ui-file-upload"
483
- :class="{
484
- 'ui-file-upload--disabled': disabled,
485
- 'ui-file-upload--error': error,
486
- 'ui-file-upload--compact': compact
487
- }"
485
+ :class="[
486
+ props.class,
487
+ 'ui-file-upload',
488
+ {
489
+ 'ui-file-upload--disabled': disabled,
490
+ 'ui-file-upload--error': error,
491
+ 'ui-file-upload--compact': compact
492
+ }
493
+ ]"
488
494
  >
489
495
  <label
490
496
  v-if="label"
@@ -1,10 +1,13 @@
1
1
  <script setup lang="ts">
2
2
  import { computed } from 'vue'
3
+ import type { ClassValue } from '../../types/common'
3
4
 
4
5
  type HeadingTag = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'div' | 'span'
5
6
  type HeadingSize = '4xl' | '3xl' | '2xl' | 'xl' | 'lg' | 'md'
6
7
 
7
8
  export interface HeadingProps {
9
+ /** Additional CSS classes */
10
+ class?: ClassValue
8
11
  /** Semantic HTML tag */
9
12
  as?: HeadingTag
10
13
  /** Visual hierarchy size */
@@ -22,6 +25,7 @@ const props = withDefaults(defineProps<HeadingProps>(), {
22
25
  })
23
26
 
24
27
  const classes = computed(() => [
28
+ props.class,
25
29
  'ui-heading',
26
30
  `ui-heading--${props.size}`,
27
31
  `ui-heading--${props.align}`,
@@ -1,11 +1,14 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, h, type Component, type VNode } from 'vue'
3
+ import type { ClassValue } from '../../types/common'
3
4
 
4
5
  type HugeIconData = [string, Record<string, unknown>][]
5
6
 
6
7
  export type IconInput = Component | HugeIconData
7
8
 
8
9
  export interface IconProps {
10
+ /** Additional CSS classes */
11
+ class?: ClassValue
9
12
  /** The icon - Vue component OR HugeIcons data array */
10
13
  icon: IconInput
11
14
  /** Predefined size or custom value */
@@ -51,7 +54,7 @@ function renderHugeIcon(): VNode {
51
54
  xmlns: 'http://www.w3.org/2000/svg',
52
55
  viewBox: '0 0 24 24',
53
56
  fill: 'none',
54
- class: 'ui-icon',
57
+ class: [props.class, 'ui-icon'],
55
58
  style: { '--icon-size': resolvedSize.value },
56
59
  'aria-label': props.label,
57
60
  'aria-hidden': ariaHidden.value,
@@ -64,7 +67,7 @@ function renderHugeIcon(): VNode {
64
67
  <component
65
68
  v-if="!isHugeIconData"
66
69
  :is="icon"
67
- class="ui-icon"
70
+ :class="[props.class, 'ui-icon']"
68
71
  :style="{ '--icon-size': resolvedSize }"
69
72
  :stroke-width="strokeWidth"
70
73
  :aria-label="label"
@@ -4,8 +4,11 @@ import Icon from '../Icon/Icon.vue'
4
4
  import Spinner from '../Spinner/Spinner.vue'
5
5
  import type { IconInput } from '../Icon/Icon.vue'
6
6
  import { useId } from '../../composables'
7
+ import type { ClassValue } from '../../types/common'
7
8
 
8
9
  export interface InputProps {
10
+ /** Additional CSS classes */
11
+ class?: ClassValue
9
12
  /** Input value (v-model) */
10
13
  modelValue?: string | number
11
14
  /** Input type */
@@ -100,8 +103,9 @@ function handleBlur(event: FocusEvent) {
100
103
 
101
104
  <template>
102
105
  <div
103
- class="ui-input-field"
104
106
  :class="[
107
+ props.class,
108
+ 'ui-input-field',
105
109
  `ui-input-field--${size}`,
106
110
  {
107
111
  'ui-input-field--block': block,
@@ -1,7 +1,10 @@
1
1
  <script setup lang="ts">
2
2
  import { computed } from 'vue'
3
+ import type { ClassValue } from '../../types/common'
3
4
 
4
5
  export interface ContainerProps {
6
+ /** Additional CSS classes */
7
+ class?: ClassValue
5
8
  /** If true, width is 100%. If false, max-width snaps to breakpoints. */
6
9
  fluid?: boolean
7
10
  /** Auto margins (mx-auto). Default: true */
@@ -17,6 +20,7 @@ const props = withDefaults(defineProps<ContainerProps>(), {
17
20
  })
18
21
 
19
22
  const containerClasses = computed(() => [
23
+ props.class,
20
24
  'ui-container',
21
25
  {
22
26
  'ui-container--fluid': props.fluid,
@@ -2,6 +2,7 @@
2
2
  import { computed, provide, toRef } from 'vue'
3
3
  import { GridKey } from './keys'
4
4
  import type { SpacingToken } from './Stack.vue'
5
+ import type { ClassValue } from '../../types/common'
5
6
 
6
7
  export type ColumnCount = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12
7
8
 
@@ -14,6 +15,8 @@ export interface ResponsiveColumns {
14
15
  }
15
16
 
16
17
  export interface GridProps {
18
+ /** Additional CSS classes */
19
+ class?: ClassValue
17
20
  /** Number of columns (or responsive object) */
18
21
  columns?: ColumnCount | ResponsiveColumns
19
22
  /** Grid gap using spacing tokens */
@@ -35,7 +38,7 @@ const props = withDefaults(defineProps<GridProps>(), {
35
38
  const isResponsive = computed(() => typeof props.columns === 'object')
36
39
 
37
40
  const gridClasses = computed(() => {
38
- const classes = ['ui-grid']
41
+ const classes: (string | undefined | null | boolean | ClassValue)[] = [props.class, 'ui-grid']
39
42
 
40
43
  if (isResponsive.value) {
41
44
  const cols = props.columns as ResponsiveColumns
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { computed } from 'vue'
3
+ import type { ClassValue } from '../../types/common'
3
4
 
4
5
  export type SpanCount = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 'full'
5
6
 
@@ -12,6 +13,8 @@ export interface ResponsiveSpan {
12
13
  }
13
14
 
14
15
  export interface GridItemProps {
16
+ /** Additional CSS classes */
17
+ class?: ClassValue
15
18
  /** Number of columns to span (or responsive object) */
16
19
  span?: SpanCount | ResponsiveSpan
17
20
  /** Grid column start */
@@ -32,7 +35,7 @@ const props = withDefaults(defineProps<GridItemProps>(), {
32
35
  const isResponsive = computed(() => typeof props.span === 'object')
33
36
 
34
37
  const itemClasses = computed(() => {
35
- const classes = ['ui-grid-item']
38
+ const classes: (string | undefined | null | boolean | ClassValue)[] = [props.class, 'ui-grid-item']
36
39
 
37
40
  if (isResponsive.value) {
38
41
  const span = props.span as ResponsiveSpan
@@ -1,9 +1,12 @@
1
1
  <script setup lang="ts">
2
2
  import { computed } from 'vue'
3
+ import type { ClassValue } from '../../types/common'
3
4
 
4
5
  export type SpacingToken = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | 12
5
6
 
6
7
  export interface StackProps {
8
+ /** Additional CSS classes */
9
+ class?: ClassValue
7
10
  /** Flex direction */
8
11
  direction?: 'row' | 'column' | 'row-reverse' | 'column-reverse'
9
12
  /** Gap using spacing tokens (maps to var(--space-X)) */
@@ -31,6 +34,7 @@ const props = withDefaults(defineProps<StackProps>(), {
31
34
  })
32
35
 
33
36
  const stackClasses = computed(() => [
37
+ props.class,
34
38
  'ui-stack',
35
39
  `ui-stack--${props.direction}`,
36
40
  `ui-stack--align-${props.align}`,