@skewedaspect/sleekspace-ui 0.9.0 → 0.10.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 (210) hide show
  1. package/dist/components/Accordion/context.d.ts +4 -0
  2. package/dist/components/Autocomplete/SkAutocomplete.vue.d.ts +87 -0
  3. package/dist/components/Autocomplete/SkAutocompleteEmpty.vue.d.ts +17 -0
  4. package/dist/components/Autocomplete/SkAutocompleteGroup.vue.d.ts +17 -0
  5. package/dist/components/Autocomplete/SkAutocompleteGroupLabel.vue.d.ts +17 -0
  6. package/dist/components/Autocomplete/SkAutocompleteItem.vue.d.ts +39 -0
  7. package/dist/components/Autocomplete/SkAutocompleteSeparator.vue.d.ts +2 -0
  8. package/dist/components/Autocomplete/index.d.ts +7 -0
  9. package/dist/components/Autocomplete/types.d.ts +3 -0
  10. package/dist/components/Breadcrumbs/context.d.ts +4 -0
  11. package/dist/components/Button/SkButton.vue.d.ts +8 -1
  12. package/dist/components/Button/types.d.ts +2 -0
  13. package/dist/components/Card/SkCard.vue.d.ts +1 -1
  14. package/dist/components/ContextMenu/context.d.ts +3 -0
  15. package/dist/components/Dropdown/SkDropdown.vue.d.ts +1 -1
  16. package/dist/components/Dropdown/context.d.ts +3 -0
  17. package/dist/components/Field/SkField.vue.d.ts +7 -6
  18. package/dist/components/Input/SkInput.vue.d.ts +9 -2
  19. package/dist/components/Input/types.d.ts +2 -0
  20. package/dist/components/InputGroup/SkInputGroup.vue.d.ts +23 -0
  21. package/dist/components/InputGroup/SkInputGroupAddon.vue.d.ts +33 -0
  22. package/dist/components/InputGroup/types.d.ts +13 -0
  23. package/dist/components/NumberInput/SkNumberInput.vue.d.ts +15 -1
  24. package/dist/components/NumberInput/types.d.ts +2 -0
  25. package/dist/components/Pagination/context.d.ts +5 -0
  26. package/dist/components/Panel/SkPanel.vue.d.ts +1 -1
  27. package/dist/components/Panel/types.d.ts +2 -1
  28. package/dist/components/Radio/context.d.ts +4 -0
  29. package/dist/components/Select/SkSelect.vue.d.ts +7 -1
  30. package/dist/components/Select/types.d.ts +2 -0
  31. package/dist/components/Sidebar/SkSidebar.vue.d.ts +1 -1
  32. package/dist/components/Tabs/context.d.ts +6 -0
  33. package/dist/components/Textarea/SkTextarea.vue.d.ts +1 -1
  34. package/dist/components/Tooltip/SkTooltip.vue.d.ts +1 -1
  35. package/dist/composables/injectionKeys.d.ts +9 -0
  36. package/dist/global.d.ts +4 -0
  37. package/dist/index.d.ts +18 -0
  38. package/dist/sleekspace-ui.css +836 -280
  39. package/dist/sleekspace-ui.es.js +3759 -2545
  40. package/dist/sleekspace-ui.umd.js +3765 -2543
  41. package/dist/static/components/alert.d.ts +2 -1
  42. package/dist/static/components/avatar.d.ts +2 -1
  43. package/dist/static/components/breadcrumbs.d.ts +2 -1
  44. package/dist/static/components/button.d.ts +4 -2
  45. package/dist/static/components/card.d.ts +2 -1
  46. package/dist/static/components/checkbox.d.ts +2 -1
  47. package/dist/static/components/colorPicker.d.ts +2 -1
  48. package/dist/static/components/divider.d.ts +2 -1
  49. package/dist/static/components/dropdown.d.ts +2 -1
  50. package/dist/static/components/field.d.ts +2 -1
  51. package/dist/static/components/group.d.ts +2 -1
  52. package/dist/static/components/input.d.ts +4 -2
  53. package/dist/static/components/inputGroup.d.ts +8 -0
  54. package/dist/static/components/inputGroupAddon.d.ts +7 -0
  55. package/dist/static/components/navBar.d.ts +2 -1
  56. package/dist/static/components/numberInput.d.ts +4 -2
  57. package/dist/static/components/page.d.ts +2 -1
  58. package/dist/static/components/pagination.d.ts +2 -1
  59. package/dist/static/components/panel.d.ts +2 -1
  60. package/dist/static/components/progress.d.ts +2 -1
  61. package/dist/static/components/radio.d.ts +2 -1
  62. package/dist/static/components/select.d.ts +4 -2
  63. package/dist/static/components/sidebar.d.ts +2 -1
  64. package/dist/static/components/skeleton.d.ts +2 -1
  65. package/dist/static/components/slider.d.ts +2 -1
  66. package/dist/static/components/spinner.d.ts +2 -1
  67. package/dist/static/components/switchInput.d.ts +2 -1
  68. package/dist/static/components/table.d.ts +2 -1
  69. package/dist/static/components/tag.d.ts +2 -1
  70. package/dist/static/components/tagsInput.d.ts +2 -1
  71. package/dist/static/components/textarea.d.ts +2 -1
  72. package/dist/static/components/toolbar.d.ts +2 -1
  73. package/dist/static/components/tooltip.d.ts +2 -1
  74. package/dist/static/h.d.ts +2 -0
  75. package/dist/static/index.cjs.js +1 -1
  76. package/dist/static/index.d.ts +6 -0
  77. package/dist/static/index.es.js +366 -216
  78. package/dist/static/render.d.ts +2 -1
  79. package/dist/static/stringH.d.ts +2 -0
  80. package/dist/static/types.d.ts +5 -0
  81. package/dist/tailwind.css +222 -0
  82. package/dist/tokens.css +0 -223
  83. package/dist/types/corners.d.ts +1 -0
  84. package/dist/utils/slots.d.ts +6 -0
  85. package/llms-full.txt +17 -9
  86. package/package.json +9 -3
  87. package/src/components/Accordion/SkAccordion.vue +5 -2
  88. package/src/components/Accordion/SkAccordionItem.vue +7 -4
  89. package/src/components/Accordion/context.ts +23 -0
  90. package/src/components/Alert/SkAlert.vue +4 -2
  91. package/src/components/Autocomplete/SkAutocomplete.test.ts +83 -0
  92. package/src/components/Autocomplete/SkAutocomplete.vue +305 -0
  93. package/src/components/Autocomplete/SkAutocompleteEmpty.vue +39 -0
  94. package/src/components/Autocomplete/SkAutocompleteGroup.vue +46 -0
  95. package/src/components/Autocomplete/SkAutocompleteGroupLabel.vue +39 -0
  96. package/src/components/Autocomplete/SkAutocompleteItem.vue +85 -0
  97. package/src/components/Autocomplete/SkAutocompleteSeparator.vue +39 -0
  98. package/src/components/Autocomplete/index.ts +13 -0
  99. package/src/components/Autocomplete/types.ts +10 -0
  100. package/src/components/Breadcrumbs/SkBreadcrumbItem.vue +8 -3
  101. package/src/components/Breadcrumbs/SkBreadcrumbSeparator.vue +8 -2
  102. package/src/components/Breadcrumbs/SkBreadcrumbs.vue +11 -14
  103. package/src/components/Breadcrumbs/context.ts +20 -0
  104. package/src/components/Button/SkButton.vue +54 -11
  105. package/src/components/Button/types.ts +6 -0
  106. package/src/components/Card/SkCard.vue +12 -5
  107. package/src/components/Checkbox/SkCheckbox.vue +9 -2
  108. package/src/components/ColorPicker/SkColorPicker.vue +27 -5
  109. package/src/components/ContextMenu/SkContextMenu.vue +4 -1
  110. package/src/components/ContextMenu/SkContextMenuSubmenu.vue +5 -2
  111. package/src/components/ContextMenu/context.ts +17 -0
  112. package/src/components/Dropdown/SkDropdown.vue +2 -1
  113. package/src/components/Dropdown/SkDropdownSubmenu.vue +4 -3
  114. package/src/components/Dropdown/context.ts +16 -0
  115. package/src/components/Field/SkField.test.ts +88 -0
  116. package/src/components/Field/SkField.vue +15 -7
  117. package/src/components/Input/SkInput.test.ts +61 -0
  118. package/src/components/Input/SkInput.vue +42 -7
  119. package/src/components/Input/types.ts +2 -0
  120. package/src/components/InputGroup/SkInputGroup.test.ts +171 -0
  121. package/src/components/InputGroup/SkInputGroup.vue +131 -0
  122. package/src/components/InputGroup/SkInputGroupAddon.test.ts +104 -0
  123. package/src/components/InputGroup/SkInputGroupAddon.vue +107 -0
  124. package/src/components/InputGroup/types.ts +27 -0
  125. package/src/components/Listbox/SkListbox.vue +27 -6
  126. package/src/components/Modal/SkModal.vue +11 -4
  127. package/src/components/NavBar/SkNavBar.vue +5 -4
  128. package/src/components/NumberInput/SkNumberInput.vue +49 -8
  129. package/src/components/NumberInput/types.ts +2 -0
  130. package/src/components/Page/SkPage.vue +18 -15
  131. package/src/components/Pagination/SkPagination.vue +6 -3
  132. package/src/components/Pagination/SkPaginationItem.vue +8 -5
  133. package/src/components/Pagination/context.ts +19 -0
  134. package/src/components/Panel/types.ts +3 -2
  135. package/src/components/Popover/SkPopover.vue +11 -4
  136. package/src/components/Radio/SkRadio.vue +14 -4
  137. package/src/components/Radio/SkRadioGroup.vue +4 -2
  138. package/src/components/Radio/context.ts +17 -0
  139. package/src/components/Select/SkSelect.vue +39 -7
  140. package/src/components/Select/types.ts +2 -0
  141. package/src/components/Switch/SkSwitch.vue +14 -13
  142. package/src/components/Tabs/SkTab.vue +10 -3
  143. package/src/components/Tabs/SkTabList.vue +4 -2
  144. package/src/components/Tabs/SkTabs.vue +5 -3
  145. package/src/components/Tabs/context.ts +19 -0
  146. package/src/components/TagsInput/SkTagsInput.vue +28 -7
  147. package/src/components/Textarea/SkTextarea.vue +27 -6
  148. package/src/components/TreeView/SkTreeItem.vue +10 -2
  149. package/src/composables/injectionKeys.ts +52 -0
  150. package/src/index.ts +28 -0
  151. package/src/static/__tests__/parity.test.ts +2 -1
  152. package/src/static/__tests__/parityHarness.ts +5 -2
  153. package/src/static/components/__tests__/helpers.test.ts +191 -99
  154. package/src/static/components/alert.ts +12 -11
  155. package/src/static/components/avatar.ts +15 -16
  156. package/src/static/components/breadcrumbs.ts +3 -2
  157. package/src/static/components/button.ts +23 -27
  158. package/src/static/components/card.ts +3 -2
  159. package/src/static/components/checkbox.ts +11 -14
  160. package/src/static/components/colorPicker.ts +7 -9
  161. package/src/static/components/divider.ts +4 -3
  162. package/src/static/components/dropdown.ts +15 -6
  163. package/src/static/components/field.ts +32 -15
  164. package/src/static/components/group.ts +3 -2
  165. package/src/static/components/input.ts +20 -15
  166. package/src/static/components/inputGroup.ts +30 -0
  167. package/src/static/components/inputGroupAddon.ts +29 -0
  168. package/src/static/components/navBar.ts +30 -17
  169. package/src/static/components/numberInput.ts +17 -17
  170. package/src/static/components/page.ts +3 -2
  171. package/src/static/components/pagination.ts +3 -2
  172. package/src/static/components/panel.ts +3 -2
  173. package/src/static/components/progress.ts +3 -2
  174. package/src/static/components/radio.ts +14 -20
  175. package/src/static/components/select.ts +18 -15
  176. package/src/static/components/sidebar.ts +9 -13
  177. package/src/static/components/skeleton.ts +7 -10
  178. package/src/static/components/slider.ts +7 -9
  179. package/src/static/components/spinner.ts +22 -22
  180. package/src/static/components/switchInput.ts +12 -14
  181. package/src/static/components/table.ts +8 -10
  182. package/src/static/components/tag.ts +17 -11
  183. package/src/static/components/tagsInput.ts +3 -3
  184. package/src/static/components/textarea.ts +8 -13
  185. package/src/static/components/toolbar.ts +7 -10
  186. package/src/static/components/tooltip.ts +3 -2
  187. package/src/static/generated/defaults.ts +25 -9
  188. package/src/static/generated/propTypes.ts +19 -2
  189. package/src/static/h.ts +16 -0
  190. package/src/static/index.ts +8 -0
  191. package/src/static/render.test.ts +14 -10
  192. package/src/static/render.ts +33 -18
  193. package/src/static/specs.test.ts +1 -0
  194. package/src/static/specs.ts +22 -2
  195. package/src/static/stringH.ts +104 -0
  196. package/src/static/types.ts +25 -0
  197. package/src/styles/components/_autocomplete.scss +498 -0
  198. package/src/styles/components/_button.scss +55 -6
  199. package/src/styles/components/_index.scss +2 -0
  200. package/src/styles/components/_input-group.scss +292 -0
  201. package/src/styles/components/_input.scss +57 -9
  202. package/src/styles/components/_number-input.scss +88 -14
  203. package/src/styles/components/_select.scss +56 -9
  204. package/src/styles/mixins/_cut-border.scss +83 -0
  205. package/src/styles/tailwind.scss +262 -0
  206. package/src/styles/tokens.scss +8 -255
  207. package/src/types/corners.ts +10 -0
  208. package/src/utils/slots.test.ts +89 -0
  209. package/src/utils/slots.ts +80 -0
  210. package/web-types.json +392 -12
@@ -52,6 +52,9 @@
52
52
  import type { ComponentCustomColors } from '@/types';
53
53
  import type { SkAccordionKind, SkAccordionType } from './types';
54
54
 
55
+ // Context
56
+ import { ACCORDION_KIND_KEY, ACCORDION_MODEL_VALUE_KEY } from './context';
57
+
55
58
  // Composables
56
59
  import { useCustomColors } from '@/composables/useCustomColors';
57
60
 
@@ -130,10 +133,10 @@
130
133
  );
131
134
 
132
135
  // Provide kind for child items (reactive computed so changes propagate)
133
- provide('accordion-kind', computed(() => props.kind));
136
+ provide(ACCORDION_KIND_KEY, computed(() => props.kind));
134
137
 
135
138
  // Provide modelValue for child items to check if they are open
136
- provide('accordion-modelValue', modelValue);
139
+ provide(ACCORDION_MODEL_VALUE_KEY, modelValue);
137
140
 
138
141
  //------------------------------------------------------------------------------------------------------------------
139
142
 
@@ -63,7 +63,7 @@
63
63
  * Use this for complex headers with icons, badges, or conditional styling.
64
64
  */
65
65
 
66
- import { type ComputedRef, type Ref, computed, inject, ref } from 'vue';
66
+ import { computed, inject } from 'vue';
67
67
  import {
68
68
  AccordionContent,
69
69
  AccordionHeader,
@@ -74,6 +74,9 @@
74
74
  // Types
75
75
  import type { SkAccordionKind } from './types';
76
76
 
77
+ // Context
78
+ import { ACCORDION_KIND_KEY, ACCORDION_MODEL_VALUE_KEY } from './context';
79
+
77
80
  //------------------------------------------------------------------------------------------------------------------
78
81
 
79
82
  export interface SkAccordionItemComponentProps
@@ -119,11 +122,11 @@
119
122
  //------------------------------------------------------------------------------------------------------------------
120
123
 
121
124
  // Inject parent kind and compute effective kind
122
- const parentKind = inject<ComputedRef<SkAccordionKind>>('accordion-kind', computed(() => 'neutral'));
123
- const effectiveKind = computed(() => props.kind ?? parentKind.value);
125
+ const parentKind = inject(ACCORDION_KIND_KEY, computed(() => undefined));
126
+ const effectiveKind = computed<SkAccordionKind>(() => props.kind ?? parentKind.value ?? 'neutral');
124
127
 
125
128
  // Inject parent modelValue to check if this item is open
126
- const parentModelValue = inject<Ref<string | string[] | undefined>>('accordion-modelValue', ref(undefined));
129
+ const parentModelValue = inject(ACCORDION_MODEL_VALUE_KEY, computed(() => undefined));
127
130
  const isOpen = computed(() =>
128
131
  {
129
132
  const modelValue = parentModelValue.value;
@@ -0,0 +1,23 @@
1
+ //----------------------------------------------------------------------------------------------------------------------
2
+ // Accordion Context
3
+ //
4
+ // Injection keys for SkAccordionItem descendants. The kind channel lets items pick up the parent
5
+ // SkAccordion's kind without manual wiring; the model-value channel lets each item observe the
6
+ // open-state shared with its siblings.
7
+ //----------------------------------------------------------------------------------------------------------------------
8
+
9
+ import type { ComputedRef, InjectionKey, Ref } from 'vue';
10
+
11
+ import type { SkAccordionKind } from './types';
12
+
13
+ //----------------------------------------------------------------------------------------------------------------------
14
+
15
+ export const ACCORDION_KIND_KEY : InjectionKey<ComputedRef<SkAccordionKind | undefined>>
16
+ = Symbol('sk-accordion-kind');
17
+
18
+ // SkAccordion provides a writable computed for two-way binding; consumers only read, so the
19
+ // ambient channel is typed as a plain Ref to accept both writable and readonly providers.
20
+ export const ACCORDION_MODEL_VALUE_KEY : InjectionKey<Ref<string | string[] | undefined>>
21
+ = Symbol('sk-accordion-model-value');
22
+
23
+ //----------------------------------------------------------------------------------------------------------------------
@@ -94,6 +94,9 @@
94
94
  // Composables
95
95
  import { useCustomColors } from '@/composables/useCustomColors';
96
96
 
97
+ // Utils
98
+ import { hasSlotContent } from '@/utils/slots';
99
+
97
100
  //------------------------------------------------------------------------------------------------------------------
98
101
 
99
102
  // Kinds that have default icons
@@ -162,9 +165,8 @@
162
165
 
163
166
  // Auto-show if kind has a default icon or if slot content is provided
164
167
  const hasDefaultIcon = FEEDBACK_KINDS.includes(props.kind as typeof FEEDBACK_KINDS[number]);
165
- const hasSlotContent = !!slots.icon;
166
168
 
167
- return hasDefaultIcon || hasSlotContent;
169
+ return hasDefaultIcon || hasSlotContent(slots.icon);
168
170
  });
169
171
 
170
172
  //------------------------------------------------------------------------------------------------------------------
@@ -0,0 +1,83 @@
1
+ //----------------------------------------------------------------------------------------------------------------------
2
+ // SkAutocomplete Component Tests
3
+ //----------------------------------------------------------------------------------------------------------------------
4
+
5
+ import { describe, expect, it } from 'vitest';
6
+ import { mount } from '@vue/test-utils';
7
+
8
+ import SkAutocomplete from './SkAutocomplete.vue';
9
+
10
+ //----------------------------------------------------------------------------------------------------------------------
11
+
12
+ describe('SkAutocomplete', () =>
13
+ {
14
+ it('renders the wrapper with the default kind class', () =>
15
+ {
16
+ const wrapper = mount(SkAutocomplete);
17
+ const root = wrapper.find('.sk-autocomplete');
18
+ expect(root.exists()).toBe(true);
19
+ expect(root.classes()).toContain('sk-neutral');
20
+ expect(root.classes()).toContain('sk-md');
21
+ });
22
+
23
+ it('applies the explicit kind and size as classes', () =>
24
+ {
25
+ const wrapper = mount(SkAutocomplete, {
26
+ props: { kind: 'primary', size: 'lg' },
27
+ });
28
+ const root = wrapper.find('.sk-autocomplete');
29
+ expect(root.classes()).toContain('sk-primary');
30
+ expect(root.classes()).toContain('sk-lg');
31
+ });
32
+
33
+ it('omits the clear button by default', () =>
34
+ {
35
+ const wrapper = mount(SkAutocomplete);
36
+ expect(wrapper.find('.sk-autocomplete-cancel').exists()).toBe(false);
37
+ });
38
+
39
+ it('renders the clear button when clearable is true', () =>
40
+ {
41
+ const wrapper = mount(SkAutocomplete, { props: { clearable: true } });
42
+ expect(wrapper.find('.sk-autocomplete-cancel').exists()).toBe(true);
43
+ });
44
+
45
+ it('passes the placeholder to the input', () =>
46
+ {
47
+ const wrapper = mount(SkAutocomplete, { props: { placeholder: 'Find things...' } });
48
+ const input = wrapper.find('input');
49
+ expect(input.attributes('placeholder')).toBe('Find things...');
50
+ });
51
+
52
+ it('disables the input when disabled is true', () =>
53
+ {
54
+ const wrapper = mount(SkAutocomplete, { props: { disabled: true } });
55
+ const input = wrapper.find('input');
56
+ expect(input.attributes('disabled')).toBeDefined();
57
+ });
58
+
59
+ it('emits update:search when the user types', async () =>
60
+ {
61
+ const wrapper = mount(SkAutocomplete);
62
+ const input = wrapper.find('input');
63
+ await input.setValue('hello');
64
+ const events = wrapper.emitted('update:search');
65
+ expect(events).toBeDefined();
66
+ expect(events?.at(-1)).toEqual([ 'hello' ]);
67
+ });
68
+
69
+ it('two-way binds the typed string via v-model', async () =>
70
+ {
71
+ const wrapper = mount(SkAutocomplete, {
72
+ props: {
73
+ 'modelValue': '',
74
+ 'onUpdate:modelValue': (value : string) => wrapper.setProps({ modelValue: value }),
75
+ },
76
+ });
77
+ const input = wrapper.find('input');
78
+ await input.setValue('typed');
79
+ expect(wrapper.props('modelValue')).toBe('typed');
80
+ });
81
+ });
82
+
83
+ //----------------------------------------------------------------------------------------------------------------------
@@ -0,0 +1,305 @@
1
+ <!----------------------------------------------------------------------------------------------------------------------
2
+ - Autocomplete Component
3
+ --------------------------------------------------------------------------------------------------------------------->
4
+
5
+ <template>
6
+ <AutocompleteRoot
7
+ v-model="modelValue"
8
+ :disabled="disabled"
9
+ :ignore-filter="ignoreFilter"
10
+ :open-on-focus="openOnFocus"
11
+ :open-on-click="openOnClick"
12
+ :class="wrapperClasses"
13
+ :style="customColorStyles"
14
+ @update:model-value="onUpdateModel"
15
+ >
16
+ <AutocompleteAnchor :class="anchorClasses">
17
+ <AutocompleteInput :class="inputClasses" :placeholder="placeholder" />
18
+ <AutocompleteCancel v-if="clearable" :class="cancelClasses" aria-label="Clear">
19
+ <svg
20
+ xmlns="http://www.w3.org/2000/svg"
21
+ viewBox="0 0 24 24"
22
+ fill="none"
23
+ stroke="currentColor"
24
+ stroke-width="2"
25
+ stroke-linecap="square"
26
+ stroke-linejoin="miter"
27
+ style="width: 0.875rem; height: 0.875rem;"
28
+ >
29
+ <line x1="18" y1="6" x2="6" y2="18" />
30
+ <line x1="6" y1="6" x2="18" y2="18" />
31
+ </svg>
32
+ </AutocompleteCancel>
33
+ <AutocompleteTrigger :class="triggerClasses">
34
+ <svg
35
+ xmlns="http://www.w3.org/2000/svg"
36
+ viewBox="0 0 24 24"
37
+ fill="none"
38
+ stroke="currentColor"
39
+ stroke-width="2"
40
+ stroke-linecap="square"
41
+ stroke-linejoin="miter"
42
+ style="width: 1rem; height: 1rem;"
43
+ >
44
+ <polyline points="6 9 12 15 18 9" />
45
+ </svg>
46
+ </AutocompleteTrigger>
47
+ </AutocompleteAnchor>
48
+
49
+ <AutocompletePortal>
50
+ <AutocompleteContent
51
+ :data-scheme="theme"
52
+ :class="contentClasses"
53
+ :style="customColorStyles"
54
+ position="popper"
55
+ side="bottom"
56
+ align="start"
57
+ :side-offset="4"
58
+ >
59
+ <AutocompleteViewport>
60
+ <slot />
61
+ </AutocompleteViewport>
62
+ </AutocompleteContent>
63
+ </AutocompletePortal>
64
+ </AutocompleteRoot>
65
+ </template>
66
+
67
+ <!--------------------------------------------------------------------------------------------------------------------->
68
+
69
+ <style lang="scss" scoped>
70
+ // Component styles are in /src/styles/components/_autocomplete.scss
71
+ </style>
72
+
73
+ <!--------------------------------------------------------------------------------------------------------------------->
74
+
75
+ <script setup lang="ts">
76
+ /**
77
+ * @component SkAutocomplete
78
+ * @description A text input with advisory suggestions. Unlike SkListbox (which restricts the user to a
79
+ * predefined list), SkAutocomplete accepts free-form text and surfaces matching suggestions as the user
80
+ * types. Built on RekaUI's Autocomplete primitive with full keyboard navigation, client-side filtering,
81
+ * and portal rendering.
82
+ *
83
+ * Use SkAutocomplete for search bars, free-form fields with helpful suggestions, and any case where the
84
+ * user may type a value that isn't in the suggestion list. Use SkListbox when the user must pick from
85
+ * the list. Use SkSelect for short, fixed lists.
86
+ *
87
+ * @example
88
+ * ```vue
89
+ * <SkAutocomplete v-model="query" kind="primary" placeholder="Search...">
90
+ * <SkAutocompleteItem value="apple">Apple</SkAutocompleteItem>
91
+ * <SkAutocompleteItem value="banana">Banana</SkAutocompleteItem>
92
+ * <SkAutocompleteEmpty>No matches</SkAutocompleteEmpty>
93
+ * </SkAutocomplete>
94
+ * ```
95
+ *
96
+ * @example Async suggestions
97
+ * ```vue
98
+ * <SkAutocomplete v-model="query" :ignore-filter="true" @update:search="loadSuggestions">
99
+ * <SkAutocompleteItem v-for="item in results" :key="item.id" :value="item.label">
100
+ * {{ item.label }}
101
+ * </SkAutocompleteItem>
102
+ * </SkAutocomplete>
103
+ * ```
104
+ *
105
+ * @slot default - SkAutocompleteItem, SkAutocompleteSeparator, SkAutocompleteGroup, and
106
+ * SkAutocompleteEmpty components representing the available suggestions.
107
+ */
108
+
109
+ import { computed, inject, toRef } from 'vue';
110
+ import {
111
+ AutocompleteAnchor,
112
+ AutocompleteCancel,
113
+ AutocompleteContent,
114
+ AutocompleteInput,
115
+ AutocompletePortal,
116
+ AutocompleteRoot,
117
+ AutocompleteTrigger,
118
+ AutocompleteViewport,
119
+ } from 'reka-ui';
120
+
121
+ // Types
122
+ import type { ComponentCustomColors } from '@/types';
123
+ import type { SkAutocompleteKind, SkAutocompleteSize } from './types';
124
+
125
+ // Composables
126
+ import {
127
+ NO_KIND,
128
+ NO_SIZE,
129
+ inheritedKindKey,
130
+ inputGroupSizeKey,
131
+ validationKindKey,
132
+ } from '@/composables/injectionKeys';
133
+ import { useCustomColors } from '@/composables/useCustomColors';
134
+ import { usePortalContext } from '@/composables/usePortalContext';
135
+
136
+ //------------------------------------------------------------------------------------------------------------------
137
+
138
+ export interface SkAutocompleteComponentProps extends ComponentCustomColors
139
+ {
140
+ /**
141
+ * Semantic color kind that controls the input border, focus ring, and highlighted
142
+ * suggestion appearance. When used inside SkField, inherits the field's kind if not
143
+ * explicitly set.
144
+ * @default 'neutral' (or inherited from parent SkField)
145
+ */
146
+ kind ?: SkAutocompleteKind;
147
+
148
+ /**
149
+ * Size of the input field and dropdown content. Controls input height, text size,
150
+ * and option dimensions. Available sizes: 'sm', 'md', 'lg'.
151
+ * @default 'md'
152
+ */
153
+ size ?: SkAutocompleteSize;
154
+
155
+ /**
156
+ * Placeholder text displayed in the input field when empty.
157
+ * @default 'Search...'
158
+ */
159
+ placeholder ?: string;
160
+
161
+ /**
162
+ * When true, the autocomplete is disabled and cannot be interacted with.
163
+ * @default false
164
+ */
165
+ disabled ?: boolean;
166
+
167
+ /**
168
+ * When true, opens the suggestion menu as soon as the input gains focus.
169
+ * Set to false to require typing before suggestions appear.
170
+ * @default true
171
+ */
172
+ openOnFocus ?: boolean;
173
+
174
+ /**
175
+ * When true, opens the suggestion menu on click. RekaUI's default is false; opt in
176
+ * if you want a click-to-open UX in addition to focus-based opening.
177
+ * @default false
178
+ */
179
+ openOnClick ?: boolean;
180
+
181
+ /**
182
+ * When true, displays a clear button inside the input that resets the typed value.
183
+ * @default false
184
+ */
185
+ clearable ?: boolean;
186
+
187
+ /**
188
+ * When true, disables the built-in client-side filtering. Use this for async or
189
+ * server-driven suggestions, where you'll listen to `@update:search` and provide
190
+ * pre-filtered items.
191
+ * @default false
192
+ */
193
+ ignoreFilter ?: boolean;
194
+ }
195
+
196
+ //------------------------------------------------------------------------------------------------------------------
197
+
198
+ const props = withDefaults(defineProps<SkAutocompleteComponentProps>(), {
199
+ kind: undefined,
200
+ size: undefined,
201
+ placeholder: 'Search...',
202
+ disabled: false,
203
+ openOnFocus: true,
204
+ openOnClick: false,
205
+ clearable: false,
206
+ ignoreFilter: false,
207
+ });
208
+
209
+ const emit = defineEmits<{
210
+ /**
211
+ * Fires whenever the input text changes. Useful for async suggestion loading
212
+ * (combine with `:ignore-filter="true"`).
213
+ */
214
+ 'update:search' : [ value : string ];
215
+ }>();
216
+
217
+ //------------------------------------------------------------------------------------------------------------------
218
+
219
+ /**
220
+ * The typed text. Use with `v-model` for two-way binding.
221
+ */
222
+ const modelValue = defineModel<string>({ default: '' });
223
+
224
+ //------------------------------------------------------------------------------------------------------------------
225
+
226
+ // Handle portal context (theme injection/re-provision for nested portal components)
227
+ const { theme } = usePortalContext();
228
+
229
+ const validationKind = inject(validationKindKey, NO_KIND);
230
+ const inheritedKind = inject(inheritedKindKey, NO_KIND);
231
+ const inputGroupSize = inject(inputGroupSizeKey, NO_SIZE);
232
+
233
+ //------------------------------------------------------------------------------------------------------------------
234
+
235
+ const effectiveKind = computed<SkAutocompleteKind>(() =>
236
+ {
237
+ if(validationKind.value !== undefined) { return validationKind.value; }
238
+ if(props.kind !== undefined) { return props.kind; }
239
+ if(inheritedKind.value !== undefined) { return inheritedKind.value; }
240
+ return 'neutral';
241
+ });
242
+
243
+ const effectiveSize = computed<SkAutocompleteSize>(() =>
244
+ {
245
+ if(props.size !== undefined) { return props.size; }
246
+ if(inputGroupSize.value !== undefined) { return inputGroupSize.value; }
247
+ return 'md';
248
+ });
249
+
250
+ //------------------------------------------------------------------------------------------------------------------
251
+
252
+ function onUpdateModel(value : string) : void
253
+ {
254
+ emit('update:search', value);
255
+ }
256
+
257
+ //------------------------------------------------------------------------------------------------------------------
258
+
259
+ const wrapperClasses = computed(() => ({
260
+ 'sk-autocomplete': true,
261
+ [`sk-${ effectiveKind.value }`]: true,
262
+ [`sk-${ effectiveSize.value }`]: true,
263
+ }));
264
+
265
+ //------------------------------------------------------------------------------------------------------------------
266
+
267
+ const anchorClasses = computed(() => ({
268
+ 'sk-autocomplete-anchor': true,
269
+ }));
270
+
271
+ //------------------------------------------------------------------------------------------------------------------
272
+
273
+ const inputClasses = computed(() => ({
274
+ 'sk-autocomplete-input': true,
275
+ }));
276
+
277
+ //------------------------------------------------------------------------------------------------------------------
278
+
279
+ const cancelClasses = computed(() => ({
280
+ 'sk-autocomplete-cancel': true,
281
+ }));
282
+
283
+ //------------------------------------------------------------------------------------------------------------------
284
+
285
+ const triggerClasses = computed(() => ({
286
+ 'sk-autocomplete-trigger': true,
287
+ }));
288
+
289
+ //------------------------------------------------------------------------------------------------------------------
290
+
291
+ const contentClasses = computed(() => ({
292
+ 'sk-autocomplete-content': true,
293
+ [`sk-${ effectiveKind.value }`]: true,
294
+ }));
295
+
296
+ //------------------------------------------------------------------------------------------------------------------
297
+
298
+ const customColorStyles = useCustomColors(
299
+ 'autocomplete',
300
+ toRef(() => props.baseColor),
301
+ toRef(() => props.textColor)
302
+ );
303
+ </script>
304
+
305
+ <!--------------------------------------------------------------------------------------------------------------------->
@@ -0,0 +1,39 @@
1
+ <!----------------------------------------------------------------------------------------------------------------------
2
+ - AutocompleteEmpty Component
3
+ --------------------------------------------------------------------------------------------------------------------->
4
+
5
+ <template>
6
+ <AutocompleteEmpty class="sk-autocomplete-empty">
7
+ <slot />
8
+ </AutocompleteEmpty>
9
+ </template>
10
+
11
+ <!--------------------------------------------------------------------------------------------------------------------->
12
+
13
+ <style lang="scss" scoped>
14
+ // Component styles are in /src/styles/components/_autocomplete.scss
15
+ </style>
16
+
17
+ <!--------------------------------------------------------------------------------------------------------------------->
18
+
19
+ <script setup lang="ts">
20
+ /**
21
+ * @component SkAutocompleteEmpty
22
+ * @description Content displayed inside an SkAutocomplete dropdown when no suggestions match the
23
+ * current input. RekaUI handles the show/hide logic automatically based on the filter result.
24
+ *
25
+ * @example
26
+ * ```vue
27
+ * <SkAutocomplete v-model="query">
28
+ * <SkAutocompleteItem value="apple">Apple</SkAutocompleteItem>
29
+ * <SkAutocompleteEmpty>No matches found</SkAutocompleteEmpty>
30
+ * </SkAutocomplete>
31
+ * ```
32
+ *
33
+ * @slot default - The empty-state message. Can be plain text or rich content.
34
+ */
35
+
36
+ import { AutocompleteEmpty } from 'reka-ui';
37
+ </script>
38
+
39
+ <!--------------------------------------------------------------------------------------------------------------------->
@@ -0,0 +1,46 @@
1
+ <!----------------------------------------------------------------------------------------------------------------------
2
+ - AutocompleteGroup Component
3
+ --------------------------------------------------------------------------------------------------------------------->
4
+
5
+ <template>
6
+ <AutocompleteGroup class="sk-autocomplete-group">
7
+ <slot />
8
+ </AutocompleteGroup>
9
+ </template>
10
+
11
+ <!--------------------------------------------------------------------------------------------------------------------->
12
+
13
+ <style lang="scss" scoped>
14
+ // Component styles are in /src/styles/components/_autocomplete.scss
15
+ </style>
16
+
17
+ <!--------------------------------------------------------------------------------------------------------------------->
18
+
19
+ <script setup lang="ts">
20
+ /**
21
+ * @component SkAutocompleteGroup
22
+ * @description Semantic grouping wrapper for related suggestions within an SkAutocomplete dropdown.
23
+ * Pair with SkAutocompleteGroupLabel to give the group a heading.
24
+ *
25
+ * @example
26
+ * ```vue
27
+ * <SkAutocomplete v-model="query">
28
+ * <SkAutocompleteGroup>
29
+ * <SkAutocompleteGroupLabel>Fruits</SkAutocompleteGroupLabel>
30
+ * <SkAutocompleteItem value="apple">Apple</SkAutocompleteItem>
31
+ * <SkAutocompleteItem value="banana">Banana</SkAutocompleteItem>
32
+ * </SkAutocompleteGroup>
33
+ * <SkAutocompleteGroup>
34
+ * <SkAutocompleteGroupLabel>Vegetables</SkAutocompleteGroupLabel>
35
+ * <SkAutocompleteItem value="carrot">Carrot</SkAutocompleteItem>
36
+ * </SkAutocompleteGroup>
37
+ * </SkAutocomplete>
38
+ * ```
39
+ *
40
+ * @slot default - SkAutocompleteGroupLabel and SkAutocompleteItem children.
41
+ */
42
+
43
+ import { AutocompleteGroup } from 'reka-ui';
44
+ </script>
45
+
46
+ <!--------------------------------------------------------------------------------------------------------------------->
@@ -0,0 +1,39 @@
1
+ <!----------------------------------------------------------------------------------------------------------------------
2
+ - AutocompleteGroupLabel Component
3
+ --------------------------------------------------------------------------------------------------------------------->
4
+
5
+ <template>
6
+ <AutocompleteLabel class="sk-autocomplete-group-label">
7
+ <slot />
8
+ </AutocompleteLabel>
9
+ </template>
10
+
11
+ <!--------------------------------------------------------------------------------------------------------------------->
12
+
13
+ <style lang="scss" scoped>
14
+ // Component styles are in /src/styles/components/_autocomplete.scss
15
+ </style>
16
+
17
+ <!--------------------------------------------------------------------------------------------------------------------->
18
+
19
+ <script setup lang="ts">
20
+ /**
21
+ * @component SkAutocompleteGroupLabel
22
+ * @description A non-selectable heading for a group of related suggestions inside an
23
+ * SkAutocompleteGroup. Use to label groups like "Fruits" or "Recent searches".
24
+ *
25
+ * @example
26
+ * ```vue
27
+ * <SkAutocompleteGroup>
28
+ * <SkAutocompleteGroupLabel>Recent</SkAutocompleteGroupLabel>
29
+ * <SkAutocompleteItem value="recent1">Recent search 1</SkAutocompleteItem>
30
+ * </SkAutocompleteGroup>
31
+ * ```
32
+ *
33
+ * @slot default - The heading text.
34
+ */
35
+
36
+ import { AutocompleteLabel } from 'reka-ui';
37
+ </script>
38
+
39
+ <!--------------------------------------------------------------------------------------------------------------------->