@sabrenski/spire-ui 0.0.5 → 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 +170 -4
  2. package/dist/spire-ui.css +1 -1
  3. package/dist/spire-ui.es.js +7040 -6773
  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 +13 -3
  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
@@ -17,6 +17,7 @@ import {
17
17
  type TooltipModel
18
18
  } from 'chart.js'
19
19
  import { useChartTheme } from './useChartTheme'
20
+ import type { ClassValue } from '../../types/common'
20
21
 
21
22
  Chart.register(...registerables)
22
23
 
@@ -57,6 +58,8 @@ export interface PointClickData {
57
58
  }
58
59
 
59
60
  export interface BaseChartProps {
61
+ /** Additional CSS classes */
62
+ class?: ClassValue
60
63
  /** Chart type (line, bar, doughnut, pie, etc.) */
61
64
  type: ChartType
62
65
  /** Chart.js data configuration */
@@ -281,7 +284,7 @@ defineExpose({
281
284
  </script>
282
285
 
283
286
  <template>
284
- <div class="ui-chart" ref="containerRef">
287
+ <div :class="[props.class, 'ui-chart']" ref="containerRef">
285
288
  <!-- Custom Legend Slot -->
286
289
  <slot name="legend" :items="legendItems" :toggle="toggleDataset" />
287
290
 
@@ -387,58 +390,60 @@ defineExpose({
387
390
 
388
391
  <!-- Tooltip styles must be unscoped because content is teleported to body -->
389
392
  <style>
390
- .ui-chart__tooltip {
391
- position: fixed;
392
- z-index: var(--z-tooltip, 100);
393
- padding: var(--space-2, 0.5rem) var(--space-3, 0.75rem);
394
- background-color: var(--chart-tooltip-bg, var(--tooltip-bg, #1c1917));
395
- color: var(--chart-tooltip-text, var(--tooltip-text, #fafaf9));
396
- border-radius: var(--radius-md, 0.375rem);
397
- font-size: var(--text-sm, 0.875rem);
398
- font-family: var(--font-sans, system-ui, -apple-system, sans-serif);
399
- pointer-events: none;
400
- transform: translate(-50%, calc(-100% - 8px));
401
- box-shadow: var(--shadow-lg, 0 10px 15px -3px rgb(0 0 0 / 0.1));
402
- min-width: 120px;
403
- }
393
+ @layer spire-components {
394
+ .ui-chart__tooltip {
395
+ position: fixed;
396
+ z-index: var(--z-tooltip, 100);
397
+ padding: var(--space-2, 0.5rem) var(--space-3, 0.75rem);
398
+ background-color: var(--chart-tooltip-bg, var(--tooltip-bg, #1c1917));
399
+ color: var(--chart-tooltip-text, var(--tooltip-text, #fafaf9));
400
+ border-radius: var(--radius-md, 0.375rem);
401
+ font-size: var(--text-sm, 0.875rem);
402
+ font-family: var(--font-sans, system-ui, -apple-system, sans-serif);
403
+ pointer-events: none;
404
+ transform: translate(-50%, calc(-100% - 8px));
405
+ box-shadow: var(--shadow-lg, 0 10px 15px -3px rgb(0 0 0 / 0.1));
406
+ min-width: 120px;
407
+ }
404
408
 
405
- .ui-chart__tooltip-title {
406
- font-weight: var(--font-medium, 500);
407
- margin-bottom: var(--space-1, 0.25rem);
408
- padding-bottom: var(--space-1, 0.25rem);
409
- border-bottom: 1px solid var(--chart-tooltip-border, rgba(255, 255, 255, 0.1));
410
- }
409
+ .ui-chart__tooltip-title {
410
+ font-weight: var(--font-medium, 500);
411
+ margin-bottom: var(--space-1, 0.25rem);
412
+ padding-bottom: var(--space-1, 0.25rem);
413
+ border-bottom: 1px solid var(--chart-tooltip-border, rgba(255, 255, 255, 0.1));
414
+ }
411
415
 
412
- .ui-chart__tooltip-item {
413
- display: flex;
414
- align-items: center;
415
- gap: var(--space-2, 0.5rem);
416
- padding: var(--space-1, 0.25rem) 0;
417
- }
416
+ .ui-chart__tooltip-item {
417
+ display: flex;
418
+ align-items: center;
419
+ gap: var(--space-2, 0.5rem);
420
+ padding: var(--space-1, 0.25rem) 0;
421
+ }
418
422
 
419
- .ui-chart__tooltip-color {
420
- width: 10px;
421
- height: 10px;
422
- border-radius: var(--radius-sm, 0.25rem);
423
- flex-shrink: 0;
424
- }
423
+ .ui-chart__tooltip-color {
424
+ width: 10px;
425
+ height: 10px;
426
+ border-radius: var(--radius-sm, 0.25rem);
427
+ flex-shrink: 0;
428
+ }
425
429
 
426
- .ui-chart__tooltip-label {
427
- flex: 1;
428
- opacity: 0.8;
429
- }
430
+ .ui-chart__tooltip-label {
431
+ flex: 1;
432
+ opacity: 0.8;
433
+ }
430
434
 
431
- .ui-chart__tooltip-value {
432
- font-weight: var(--font-medium, 500);
433
- font-variant-numeric: tabular-nums;
434
- }
435
+ .ui-chart__tooltip-value {
436
+ font-weight: var(--font-medium, 500);
437
+ font-variant-numeric: tabular-nums;
438
+ }
435
439
 
436
- [data-theme='dark'] .ui-chart__tooltip {
437
- background-color: var(--chart-tooltip-bg, #fafaf9);
438
- color: var(--chart-tooltip-text, #1c1917);
439
- }
440
+ [data-theme='dark'] .ui-chart__tooltip {
441
+ background-color: var(--chart-tooltip-bg, #fafaf9);
442
+ color: var(--chart-tooltip-text, #1c1917);
443
+ }
440
444
 
441
- [data-theme='dark'] .ui-chart__tooltip-title {
442
- border-bottom-color: var(--chart-tooltip-border, rgba(0, 0, 0, 0.1));
445
+ [data-theme='dark'] .ui-chart__tooltip-title {
446
+ border-bottom-color: var(--chart-tooltip-border, rgba(0, 0, 0, 0.1));
447
+ }
443
448
  }
444
449
  </style>
@@ -4,6 +4,7 @@ import type { ChartData, ChartOptions } from 'chart.js'
4
4
  import BaseChart from './BaseChart.vue'
5
5
  import type { LegendItem, TooltipData, PointClickData } from './BaseChart.vue'
6
6
  import { useChartTheme } from './useChartTheme'
7
+ import type { ClassValue } from '../../types/common'
7
8
 
8
9
  type DonutChartData = ChartData<'doughnut'>
9
10
  type DonutChartOptions = ChartOptions<'doughnut'>
@@ -21,6 +22,8 @@ export interface SegmentClickData {
21
22
  }
22
23
 
23
24
  export interface DonutChartProps {
25
+ /** Additional CSS classes */
26
+ class?: ClassValue
24
27
  /** Chart segments */
25
28
  segments: DonutChartSegment[]
26
29
  /** Chart height */
@@ -115,8 +118,7 @@ function handlePointClick(data: PointClickData) {
115
118
 
116
119
  <template>
117
120
  <div
118
- class="ui-donut-chart"
119
- :class="[`ui-donut-chart--legend-${legendPosition}`]"
121
+ :class="[props.class, 'ui-donut-chart', `ui-donut-chart--legend-${legendPosition}`]"
120
122
  >
121
123
  <!-- Legend (conditional position) -->
122
124
  <slot v-if="showLegend && (legendPosition === 'top' || legendPosition === 'left')" name="legend" :items="legendItems" :toggle="() => {}">
@@ -4,6 +4,7 @@ import type { ChartData, ChartOptions } from 'chart.js'
4
4
  import BaseChart from './BaseChart.vue'
5
5
  import type { LegendItem, TooltipData, PointClickData } from './BaseChart.vue'
6
6
  import { useChartTheme } from './useChartTheme'
7
+ import type { ClassValue } from '../../types/common'
7
8
 
8
9
  type LineChartData = ChartData<'line'>
9
10
  type LineChartOptions = ChartOptions<'line'>
@@ -14,6 +15,8 @@ export interface LineChartSeries {
14
15
  }
15
16
 
16
17
  export interface LineChartProps {
18
+ /** Additional CSS classes */
19
+ class?: ClassValue
17
20
  /** X-axis labels */
18
21
  labels: string[]
19
22
  /** Data series */
@@ -126,6 +129,7 @@ const chartOptions = computed(() => ({
126
129
 
127
130
  <template>
128
131
  <BaseChart
132
+ :class="props.class"
129
133
  type="line"
130
134
  :data="chartData"
131
135
  :options="chartOptions"
@@ -206,4 +206,98 @@ describe('Checkbox', () => {
206
206
  expect(wrapper.find('input').attributes('value')).toBe('accepted')
207
207
  })
208
208
  })
209
+
210
+ describe('Pill variant', () => {
211
+ it('renders pill structure when variant is pill', () => {
212
+ const wrapper = mount(Checkbox, {
213
+ props: { variant: 'pill', label: 'Option A' }
214
+ })
215
+ expect(wrapper.find('.ui-checkbox-pill').exists()).toBe(true)
216
+ expect(wrapper.find('.ui-checkbox').exists()).toBe(false)
217
+ })
218
+
219
+ it('renders default structure when variant is default', () => {
220
+ const wrapper = mount(Checkbox, {
221
+ props: { variant: 'default' }
222
+ })
223
+ expect(wrapper.find('.ui-checkbox').exists()).toBe(true)
224
+ expect(wrapper.find('.ui-checkbox-pill').exists()).toBe(false)
225
+ })
226
+
227
+ it('defaults to default variant', () => {
228
+ const wrapper = mount(Checkbox)
229
+ expect(wrapper.find('.ui-checkbox').exists()).toBe(true)
230
+ expect(wrapper.find('.ui-checkbox-pill').exists()).toBe(false)
231
+ })
232
+
233
+ it('renders pill label', () => {
234
+ const wrapper = mount(Checkbox, {
235
+ props: { variant: 'pill', label: 'Option A' }
236
+ })
237
+ expect(wrapper.find('.ui-checkbox-pill__label').text()).toBe('Option A')
238
+ })
239
+
240
+ it('renders pill check indicator', () => {
241
+ const wrapper = mount(Checkbox, {
242
+ props: { variant: 'pill', label: 'Option A' }
243
+ })
244
+ expect(wrapper.find('.ui-checkbox-pill__indicator').exists()).toBe(true)
245
+ expect(wrapper.find('.ui-checkbox-pill__check').exists()).toBe(true)
246
+ })
247
+
248
+ it('applies checked class when checked', () => {
249
+ const wrapper = mount(Checkbox, {
250
+ props: { modelValue: true, variant: 'pill', label: 'Option A' }
251
+ })
252
+ expect(wrapper.find('.ui-checkbox-pill').classes()).toContain('ui-checkbox-pill--checked')
253
+ })
254
+
255
+ it('does not apply checked class when unchecked', () => {
256
+ const wrapper = mount(Checkbox, {
257
+ props: { modelValue: false, variant: 'pill', label: 'Option A' }
258
+ })
259
+ expect(wrapper.find('.ui-checkbox-pill').classes()).not.toContain('ui-checkbox-pill--checked')
260
+ })
261
+
262
+ it('applies disabled class when disabled', () => {
263
+ const wrapper = mount(Checkbox, {
264
+ props: { variant: 'pill', label: 'Option A', disabled: true }
265
+ })
266
+ expect(wrapper.find('.ui-checkbox-pill').classes()).toContain('ui-checkbox-pill--disabled')
267
+ })
268
+
269
+ it('emits events on pill change', async () => {
270
+ const wrapper = mount(Checkbox, {
271
+ props: { modelValue: false, variant: 'pill', label: 'Option A' }
272
+ })
273
+ await wrapper.find('input').setValue(true)
274
+ expect(wrapper.emitted('update:modelValue')?.[0]).toEqual([true])
275
+ expect(wrapper.emitted('change')?.[0]).toEqual([true])
276
+ })
277
+
278
+ it('toggles off when checked pill is clicked', async () => {
279
+ const wrapper = mount(Checkbox, {
280
+ props: { modelValue: true, variant: 'pill', label: 'Option A' }
281
+ })
282
+ await wrapper.find('input').setValue(false)
283
+ expect(wrapper.emitted('update:modelValue')?.[0]).toEqual([false])
284
+ })
285
+
286
+ const sizes = ['sm', 'md', 'lg'] as const
287
+ sizes.forEach(size => {
288
+ it(`applies ${size} size class to pill`, () => {
289
+ const wrapper = mount(Checkbox, {
290
+ props: { variant: 'pill', label: 'Option A', size }
291
+ })
292
+ expect(wrapper.find('.ui-checkbox-pill').classes()).toContain(`ui-checkbox-pill--${size}`)
293
+ })
294
+ })
295
+
296
+ it('pill indicator has aria-hidden', () => {
297
+ const wrapper = mount(Checkbox, {
298
+ props: { variant: 'pill', label: 'Option A' }
299
+ })
300
+ expect(wrapper.find('.ui-checkbox-pill__indicator').attributes('aria-hidden')).toBe('true')
301
+ })
302
+ })
209
303
  })
@@ -1,12 +1,17 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, ref, watch, onMounted } from 'vue'
3
3
  import { useId } from '../../composables'
4
+ import type { ClassValue } from '../../types/common'
4
5
 
5
6
  export interface CheckboxProps {
7
+ /** Additional CSS classes */
8
+ class?: ClassValue
6
9
  /** Checked state (v-model) */
7
10
  modelValue?: boolean
8
11
  /** Indeterminate state (partial selection) */
9
12
  indeterminate?: boolean
13
+ /** Visual variant */
14
+ variant?: 'default' | 'pill'
10
15
  /** Checkbox size */
11
16
  size?: 'sm' | 'md' | 'lg'
12
17
  /** Disabled state */
@@ -26,6 +31,7 @@ export interface CheckboxProps {
26
31
  const props = withDefaults(defineProps<CheckboxProps>(), {
27
32
  modelValue: false,
28
33
  indeterminate: false,
34
+ variant: 'default',
29
35
  size: 'md',
30
36
  disabled: false
31
37
  })
@@ -66,9 +72,54 @@ const indeterminatePathLength = 10
66
72
  </script>
67
73
 
68
74
  <template>
75
+ <!-- Pill variant -->
69
76
  <label
70
- class="ui-checkbox"
77
+ v-if="variant === 'pill'"
71
78
  :class="[
79
+ props.class,
80
+ 'ui-checkbox-pill',
81
+ `ui-checkbox-pill--${size}`,
82
+ {
83
+ 'ui-checkbox-pill--disabled': disabled,
84
+ 'ui-checkbox-pill--checked': modelValue
85
+ }
86
+ ]"
87
+ >
88
+ <input
89
+ ref="inputRef"
90
+ :id="checkboxId"
91
+ type="checkbox"
92
+ :checked="modelValue"
93
+ :disabled="disabled"
94
+ :name="name"
95
+ :value="value"
96
+ class="ui-checkbox-pill__input"
97
+ @change="handleChange"
98
+ />
99
+
100
+ <span class="ui-checkbox-pill__indicator" aria-hidden="true">
101
+ <svg
102
+ class="ui-checkbox-pill__check"
103
+ viewBox="0 0 16 16"
104
+ fill="none"
105
+ stroke="currentColor"
106
+ stroke-width="2.5"
107
+ stroke-linecap="round"
108
+ stroke-linejoin="round"
109
+ >
110
+ <path d="M3.5 8.5L6.5 11L12.5 5" />
111
+ </svg>
112
+ </span>
113
+
114
+ <span v-if="label" class="ui-checkbox-pill__label">{{ label }}</span>
115
+ </label>
116
+
117
+ <!-- Default variant -->
118
+ <label
119
+ v-else
120
+ :class="[
121
+ props.class,
122
+ 'ui-checkbox',
72
123
  `ui-checkbox--${size}`,
73
124
  {
74
125
  'ui-checkbox--disabled': disabled,
@@ -282,4 +333,122 @@ const indeterminatePathLength = 10
282
333
  .ui-checkbox--lg .ui-checkbox__description {
283
334
  font-size: var(--text-sm);
284
335
  }
336
+
337
+ /* Pill variant styles */
338
+ .ui-checkbox-pill {
339
+ display: inline-flex;
340
+ align-items: center;
341
+ gap: var(--space-2);
342
+ height: 36px;
343
+ padding: 0 var(--space-3);
344
+ background: var(--chip-bg);
345
+ border: 1px solid var(--chip-border);
346
+ border-radius: var(--radius-full);
347
+ font-family: var(--font-sans);
348
+ font-size: var(--text-sm);
349
+ font-weight: var(--font-medium);
350
+ color: var(--chip-text);
351
+ cursor: pointer;
352
+ transition:
353
+ background-color var(--duration-fast) var(--ease-default),
354
+ border-color var(--duration-fast) var(--ease-default),
355
+ color var(--duration-fast) var(--ease-default);
356
+ -webkit-tap-highlight-color: transparent;
357
+ }
358
+
359
+ .ui-checkbox-pill--sm {
360
+ height: 32px;
361
+ font-size: var(--text-xs);
362
+ }
363
+
364
+ .ui-checkbox-pill--lg {
365
+ height: 44px;
366
+ font-size: var(--text-md);
367
+ padding: 0 var(--space-4);
368
+ }
369
+
370
+ .ui-checkbox-pill__input {
371
+ position: absolute;
372
+ width: 1px;
373
+ height: 1px;
374
+ padding: 0;
375
+ margin: -1px;
376
+ overflow: hidden;
377
+ clip: rect(0, 0, 0, 0);
378
+ white-space: nowrap;
379
+ border: 0;
380
+ }
381
+
382
+ .ui-checkbox-pill:not(.ui-checkbox-pill--disabled):hover {
383
+ background: var(--chip-bg-hover);
384
+ border-color: var(--chip-border-hover);
385
+ }
386
+
387
+ .ui-checkbox-pill:not(.ui-checkbox-pill--disabled):active {
388
+ background: var(--chip-bg-active);
389
+ border-color: var(--chip-border-active);
390
+ }
391
+
392
+ .ui-checkbox-pill--checked {
393
+ background: var(--chip-bg-selected);
394
+ border-color: var(--chip-border-selected);
395
+ color: var(--chip-text-selected);
396
+ }
397
+
398
+ .ui-checkbox-pill--checked:not(.ui-checkbox-pill--disabled):hover {
399
+ background: var(--chip-bg-selected-hover);
400
+ border-color: var(--chip-border-selected-hover);
401
+ }
402
+
403
+ .ui-checkbox-pill--checked:not(.ui-checkbox-pill--disabled):active {
404
+ background: var(--chip-bg-selected-active);
405
+ border-color: var(--chip-border-selected-active);
406
+ }
407
+
408
+ .ui-checkbox-pill:has(.ui-checkbox-pill__input:focus-visible) {
409
+ outline: 2px solid var(--ring-color);
410
+ outline-offset: 2px;
411
+ }
412
+
413
+ .ui-checkbox-pill--disabled {
414
+ opacity: 0.5;
415
+ cursor: not-allowed;
416
+ }
417
+
418
+ .ui-checkbox-pill__indicator {
419
+ display: flex;
420
+ align-items: center;
421
+ justify-content: center;
422
+ width: 0;
423
+ margin-right: calc(-1 * var(--space-2));
424
+ overflow: hidden;
425
+ transition:
426
+ width var(--duration-fast) var(--ease-out-expo),
427
+ margin var(--duration-fast) var(--ease-out-expo);
428
+ }
429
+
430
+ .ui-checkbox-pill--checked .ui-checkbox-pill__indicator {
431
+ width: 1rem;
432
+ margin-right: 0;
433
+ }
434
+
435
+ .ui-checkbox-pill__check {
436
+ width: 0.875rem;
437
+ height: 0.875rem;
438
+ flex-shrink: 0;
439
+ opacity: 0;
440
+ transform: scale(0.5);
441
+ transition:
442
+ opacity var(--duration-fast) var(--ease-default),
443
+ transform var(--duration-fast) var(--ease-out-back);
444
+ }
445
+
446
+ .ui-checkbox-pill--checked .ui-checkbox-pill__check {
447
+ opacity: 1;
448
+ transform: scale(1);
449
+ }
450
+
451
+ .ui-checkbox-pill__label {
452
+ white-space: nowrap;
453
+ }
285
454
  </style>
@@ -3,8 +3,11 @@ import { computed, inject } from 'vue'
3
3
  import Icon from '../Icon/Icon.vue'
4
4
  import type { IconInput } from '../Icon/Icon.vue'
5
5
  import { useId } from '../../composables'
6
+ import type { ClassValue } from '../../types/common'
6
7
 
7
8
  export interface ChoiceChipProps {
9
+ /** Additional CSS classes */
10
+ class?: ClassValue
8
11
  /** Selected state for standalone usage (v-model) */
9
12
  modelValue?: boolean
10
13
  /** Value for group usage */
@@ -65,11 +68,14 @@ function handleChange() {
65
68
 
66
69
  <template>
67
70
  <label
68
- class="ui-choice-chip"
69
- :class="{
70
- 'ui-choice-chip--selected': isSelected,
71
- 'ui-choice-chip--disabled': effectiveDisabled
72
- }"
71
+ :class="[
72
+ props.class,
73
+ 'ui-choice-chip',
74
+ {
75
+ 'ui-choice-chip--selected': isSelected,
76
+ 'ui-choice-chip--disabled': effectiveDisabled
77
+ }
78
+ ]"
73
79
  >
74
80
  <input
75
81
  :id="chipId"
@@ -1,7 +1,10 @@
1
1
  <script setup lang="ts">
2
2
  import { provide, toRef } from 'vue'
3
+ import type { ClassValue } from '../../types/common'
3
4
 
4
5
  export interface ChoiceChipGroupProps {
6
+ /** Additional CSS classes */
7
+ class?: ClassValue
5
8
  /** Selected value(s) - single value for 'single' type, array for 'multiple' */
6
9
  modelValue?: string | number | (string | number)[]
7
10
  /** Selection type */
@@ -50,8 +53,7 @@ provide('choiceChipGroup', {
50
53
  role="group"
51
54
  :aria-label="label"
52
55
  :aria-disabled="disabled || undefined"
53
- class="ui-choice-chip-group"
54
- :class="{ 'ui-choice-chip-group--disabled': disabled }"
56
+ :class="[props.class, 'ui-choice-chip-group', { 'ui-choice-chip-group--disabled': disabled }]"
55
57
  >
56
58
  <slot />
57
59
  </div>
@@ -1,8 +1,11 @@
1
1
  <script setup lang="ts">
2
2
  import { ref, computed, onUnmounted } from 'vue'
3
3
  import { clamp } from '../../utils/color'
4
+ import type { ClassValue } from '../../types/common'
4
5
 
5
6
  export interface ColorAreaProps {
7
+ /** Additional CSS classes */
8
+ class?: ClassValue
6
9
  /** Hue value (0-360) */
7
10
  hue: number
8
11
  /** Saturation value (0-1) */
@@ -94,8 +97,7 @@ onUnmounted(() => {
94
97
  <template>
95
98
  <div
96
99
  ref="areaRef"
97
- class="ui-color-area"
98
- :class="{ 'ui-color-area--dragging': isDragging }"
100
+ :class="[props.class, 'ui-color-area', { 'ui-color-area--dragging': isDragging }]"
99
101
  :style="{ '--hue-color': hueColor }"
100
102
  @mousedown="handleMouseDown"
101
103
  @touchstart="handleTouchStart"
@@ -12,10 +12,13 @@ import {
12
12
  formatRgb,
13
13
  type HSV
14
14
  } from '../../utils/color'
15
+ import type { ClassValue } from '../../types/common'
15
16
 
16
17
  export type ColorFormat = 'hex' | 'rgb' | 'oklch'
17
18
 
18
19
  export interface ColorPickerProps {
20
+ /** Additional CSS classes */
21
+ class?: ClassValue
19
22
  /** Current color value */
20
23
  modelValue: string
21
24
  /** Output format */
@@ -144,8 +147,7 @@ const hasEyeDropper = computed(() => 'EyeDropper' in window)
144
147
  >
145
148
  <template #trigger>
146
149
  <div
147
- class="ui-color-picker__trigger"
148
- :class="{ 'ui-color-picker__trigger--disabled': disabled }"
150
+ :class="[props.class, 'ui-color-picker__trigger', { 'ui-color-picker__trigger--disabled': disabled }]"
149
151
  >
150
152
  <div
151
153
  class="ui-color-picker__swatch"
@@ -1,8 +1,11 @@
1
1
  <script setup lang="ts">
2
2
  import { ref, computed, onUnmounted } from 'vue'
3
3
  import { clamp } from '../../utils/color'
4
+ import type { ClassValue } from '../../types/common'
4
5
 
5
6
  export interface ColorSliderProps {
7
+ /** Additional CSS classes */
8
+ class?: ClassValue
6
9
  /** Current value (0-1 for alpha, 0-360 for hue) */
7
10
  modelValue: number
8
11
  /** Slider type */
@@ -100,8 +103,9 @@ onUnmounted(() => {
100
103
  <template>
101
104
  <div
102
105
  ref="trackRef"
103
- class="ui-color-slider"
104
106
  :class="[
107
+ props.class,
108
+ 'ui-color-slider',
105
109
  `ui-color-slider--${type}`,
106
110
  { 'ui-color-slider--dragging': isDragging }
107
111
  ]"