@skewedaspect/sleekspace-ui 0.9.1 → 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.
- package/dist/components/Accordion/context.d.ts +4 -0
- package/dist/components/Autocomplete/SkAutocomplete.vue.d.ts +87 -0
- package/dist/components/Autocomplete/SkAutocompleteEmpty.vue.d.ts +17 -0
- package/dist/components/Autocomplete/SkAutocompleteGroup.vue.d.ts +17 -0
- package/dist/components/Autocomplete/SkAutocompleteGroupLabel.vue.d.ts +17 -0
- package/dist/components/Autocomplete/SkAutocompleteItem.vue.d.ts +39 -0
- package/dist/components/Autocomplete/SkAutocompleteSeparator.vue.d.ts +2 -0
- package/dist/components/Autocomplete/index.d.ts +7 -0
- package/dist/components/Autocomplete/types.d.ts +3 -0
- package/dist/components/Breadcrumbs/context.d.ts +4 -0
- package/dist/components/Button/SkButton.vue.d.ts +8 -1
- package/dist/components/Button/types.d.ts +2 -0
- package/dist/components/Card/SkCard.vue.d.ts +1 -1
- package/dist/components/ContextMenu/context.d.ts +3 -0
- package/dist/components/Dropdown/SkDropdown.vue.d.ts +1 -1
- package/dist/components/Dropdown/context.d.ts +3 -0
- package/dist/components/Field/SkField.vue.d.ts +7 -6
- package/dist/components/Input/SkInput.vue.d.ts +9 -2
- package/dist/components/Input/types.d.ts +2 -0
- package/dist/components/InputGroup/SkInputGroup.vue.d.ts +23 -0
- package/dist/components/InputGroup/SkInputGroupAddon.vue.d.ts +33 -0
- package/dist/components/InputGroup/types.d.ts +13 -0
- package/dist/components/NumberInput/SkNumberInput.vue.d.ts +7 -1
- package/dist/components/NumberInput/types.d.ts +2 -0
- package/dist/components/Pagination/context.d.ts +5 -0
- package/dist/components/Panel/SkPanel.vue.d.ts +1 -1
- package/dist/components/Panel/types.d.ts +2 -1
- package/dist/components/Radio/context.d.ts +4 -0
- package/dist/components/Select/SkSelect.vue.d.ts +7 -1
- package/dist/components/Select/types.d.ts +2 -0
- package/dist/components/Sidebar/SkSidebar.vue.d.ts +1 -1
- package/dist/components/Tabs/context.d.ts +6 -0
- package/dist/components/Textarea/SkTextarea.vue.d.ts +1 -1
- package/dist/components/Tooltip/SkTooltip.vue.d.ts +1 -1
- package/dist/composables/injectionKeys.d.ts +9 -0
- package/dist/global.d.ts +4 -0
- package/dist/index.d.ts +18 -0
- package/dist/sleekspace-ui.css +831 -277
- package/dist/sleekspace-ui.es.js +3693 -2514
- package/dist/sleekspace-ui.umd.js +3700 -2513
- package/dist/static/components/alert.d.ts +2 -1
- package/dist/static/components/avatar.d.ts +2 -1
- package/dist/static/components/breadcrumbs.d.ts +2 -1
- package/dist/static/components/button.d.ts +4 -2
- package/dist/static/components/card.d.ts +2 -1
- package/dist/static/components/checkbox.d.ts +2 -1
- package/dist/static/components/colorPicker.d.ts +2 -1
- package/dist/static/components/divider.d.ts +2 -1
- package/dist/static/components/dropdown.d.ts +2 -1
- package/dist/static/components/field.d.ts +2 -1
- package/dist/static/components/group.d.ts +2 -1
- package/dist/static/components/input.d.ts +4 -2
- package/dist/static/components/inputGroup.d.ts +8 -0
- package/dist/static/components/inputGroupAddon.d.ts +7 -0
- package/dist/static/components/navBar.d.ts +2 -1
- package/dist/static/components/numberInput.d.ts +4 -2
- package/dist/static/components/page.d.ts +2 -1
- package/dist/static/components/pagination.d.ts +2 -1
- package/dist/static/components/panel.d.ts +2 -1
- package/dist/static/components/progress.d.ts +2 -1
- package/dist/static/components/radio.d.ts +2 -1
- package/dist/static/components/select.d.ts +4 -2
- package/dist/static/components/sidebar.d.ts +2 -1
- package/dist/static/components/skeleton.d.ts +2 -1
- package/dist/static/components/slider.d.ts +2 -1
- package/dist/static/components/spinner.d.ts +2 -1
- package/dist/static/components/switchInput.d.ts +2 -1
- package/dist/static/components/table.d.ts +2 -1
- package/dist/static/components/tag.d.ts +2 -1
- package/dist/static/components/tagsInput.d.ts +2 -1
- package/dist/static/components/textarea.d.ts +2 -1
- package/dist/static/components/toolbar.d.ts +2 -1
- package/dist/static/components/tooltip.d.ts +2 -1
- package/dist/static/h.d.ts +2 -0
- package/dist/static/index.cjs.js +1 -1
- package/dist/static/index.d.ts +6 -0
- package/dist/static/index.es.js +366 -216
- package/dist/static/render.d.ts +2 -1
- package/dist/static/stringH.d.ts +2 -0
- package/dist/static/types.d.ts +5 -0
- package/dist/tailwind.css +222 -0
- package/dist/tokens.css +0 -223
- package/dist/types/corners.d.ts +1 -0
- package/llms-full.txt +14 -9
- package/package.json +6 -3
- package/src/components/Accordion/SkAccordion.vue +5 -2
- package/src/components/Accordion/SkAccordionItem.vue +7 -4
- package/src/components/Accordion/context.ts +23 -0
- package/src/components/Autocomplete/SkAutocomplete.test.ts +83 -0
- package/src/components/Autocomplete/SkAutocomplete.vue +305 -0
- package/src/components/Autocomplete/SkAutocompleteEmpty.vue +39 -0
- package/src/components/Autocomplete/SkAutocompleteGroup.vue +46 -0
- package/src/components/Autocomplete/SkAutocompleteGroupLabel.vue +39 -0
- package/src/components/Autocomplete/SkAutocompleteItem.vue +85 -0
- package/src/components/Autocomplete/SkAutocompleteSeparator.vue +39 -0
- package/src/components/Autocomplete/index.ts +13 -0
- package/src/components/Autocomplete/types.ts +10 -0
- package/src/components/Breadcrumbs/SkBreadcrumbItem.vue +8 -3
- package/src/components/Breadcrumbs/SkBreadcrumbSeparator.vue +8 -2
- package/src/components/Breadcrumbs/SkBreadcrumbs.vue +5 -2
- package/src/components/Breadcrumbs/context.ts +20 -0
- package/src/components/Button/SkButton.vue +46 -6
- package/src/components/Button/types.ts +6 -0
- package/src/components/ColorPicker/SkColorPicker.vue +27 -5
- package/src/components/ContextMenu/SkContextMenu.vue +4 -1
- package/src/components/ContextMenu/SkContextMenuSubmenu.vue +5 -2
- package/src/components/ContextMenu/context.ts +17 -0
- package/src/components/Dropdown/SkDropdown.vue +2 -1
- package/src/components/Dropdown/SkDropdownSubmenu.vue +4 -3
- package/src/components/Dropdown/context.ts +16 -0
- package/src/components/Field/SkField.test.ts +88 -0
- package/src/components/Field/SkField.vue +15 -7
- package/src/components/Input/SkInput.test.ts +61 -0
- package/src/components/Input/SkInput.vue +42 -7
- package/src/components/Input/types.ts +2 -0
- package/src/components/InputGroup/SkInputGroup.test.ts +171 -0
- package/src/components/InputGroup/SkInputGroup.vue +131 -0
- package/src/components/InputGroup/SkInputGroupAddon.test.ts +104 -0
- package/src/components/InputGroup/SkInputGroupAddon.vue +107 -0
- package/src/components/InputGroup/types.ts +27 -0
- package/src/components/Listbox/SkListbox.vue +27 -6
- package/src/components/NumberInput/SkNumberInput.vue +39 -7
- package/src/components/NumberInput/types.ts +2 -0
- package/src/components/Pagination/SkPagination.vue +6 -3
- package/src/components/Pagination/SkPaginationItem.vue +8 -5
- package/src/components/Pagination/context.ts +19 -0
- package/src/components/Panel/types.ts +3 -2
- package/src/components/Radio/SkRadio.vue +6 -3
- package/src/components/Radio/SkRadioGroup.vue +4 -2
- package/src/components/Radio/context.ts +17 -0
- package/src/components/Select/SkSelect.vue +39 -7
- package/src/components/Select/types.ts +2 -0
- package/src/components/Tabs/SkTab.vue +4 -2
- package/src/components/Tabs/SkTabList.vue +4 -2
- package/src/components/Tabs/SkTabs.vue +5 -3
- package/src/components/Tabs/context.ts +19 -0
- package/src/components/TagsInput/SkTagsInput.vue +28 -7
- package/src/components/Textarea/SkTextarea.vue +27 -6
- package/src/composables/injectionKeys.ts +52 -0
- package/src/index.ts +28 -0
- package/src/static/__tests__/parity.test.ts +2 -1
- package/src/static/__tests__/parityHarness.ts +5 -2
- package/src/static/components/__tests__/helpers.test.ts +191 -99
- package/src/static/components/alert.ts +12 -11
- package/src/static/components/avatar.ts +15 -16
- package/src/static/components/breadcrumbs.ts +3 -2
- package/src/static/components/button.ts +23 -27
- package/src/static/components/card.ts +3 -2
- package/src/static/components/checkbox.ts +11 -14
- package/src/static/components/colorPicker.ts +7 -9
- package/src/static/components/divider.ts +4 -3
- package/src/static/components/dropdown.ts +15 -6
- package/src/static/components/field.ts +32 -15
- package/src/static/components/group.ts +3 -2
- package/src/static/components/input.ts +20 -15
- package/src/static/components/inputGroup.ts +30 -0
- package/src/static/components/inputGroupAddon.ts +29 -0
- package/src/static/components/navBar.ts +30 -17
- package/src/static/components/numberInput.ts +17 -17
- package/src/static/components/page.ts +3 -2
- package/src/static/components/pagination.ts +3 -2
- package/src/static/components/panel.ts +3 -2
- package/src/static/components/progress.ts +3 -2
- package/src/static/components/radio.ts +14 -20
- package/src/static/components/select.ts +18 -15
- package/src/static/components/sidebar.ts +9 -13
- package/src/static/components/skeleton.ts +7 -10
- package/src/static/components/slider.ts +7 -9
- package/src/static/components/spinner.ts +22 -22
- package/src/static/components/switchInput.ts +12 -14
- package/src/static/components/table.ts +8 -10
- package/src/static/components/tag.ts +17 -11
- package/src/static/components/tagsInput.ts +3 -3
- package/src/static/components/textarea.ts +8 -13
- package/src/static/components/toolbar.ts +7 -10
- package/src/static/components/tooltip.ts +3 -2
- package/src/static/generated/defaults.ts +24 -9
- package/src/static/generated/propTypes.ts +18 -2
- package/src/static/h.ts +16 -0
- package/src/static/index.ts +8 -0
- package/src/static/render.test.ts +14 -10
- package/src/static/render.ts +33 -18
- package/src/static/specs.test.ts +1 -0
- package/src/static/specs.ts +22 -2
- package/src/static/stringH.ts +104 -0
- package/src/static/types.ts +25 -0
- package/src/styles/components/_autocomplete.scss +498 -0
- package/src/styles/components/_button.scss +55 -6
- package/src/styles/components/_index.scss +2 -0
- package/src/styles/components/_input-group.scss +292 -0
- package/src/styles/components/_input.scss +57 -9
- package/src/styles/components/_number-input.scss +84 -18
- package/src/styles/components/_select.scss +56 -9
- package/src/styles/mixins/_cut-border.scss +83 -0
- package/src/styles/tailwind.scss +262 -0
- package/src/styles/tokens.scss +8 -255
- package/src/types/corners.ts +10 -0
- package/src/utils/slots.test.ts +89 -0
- package/src/utils/slots.ts +6 -1
- package/web-types.json +382 -12
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
* Use this for complex headers with icons, badges, or conditional styling.
|
|
64
64
|
*/
|
|
65
65
|
|
|
66
|
-
import {
|
|
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
|
|
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
|
|
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
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
@@ -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
|
+
<!--------------------------------------------------------------------------------------------------------------------->
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<!----------------------------------------------------------------------------------------------------------------------
|
|
2
|
+
- AutocompleteItem Component
|
|
3
|
+
--------------------------------------------------------------------------------------------------------------------->
|
|
4
|
+
|
|
5
|
+
<template>
|
|
6
|
+
<AutocompleteItem
|
|
7
|
+
:class="classes"
|
|
8
|
+
:value="value"
|
|
9
|
+
:disabled="disabled"
|
|
10
|
+
:text-value="textValue"
|
|
11
|
+
>
|
|
12
|
+
<slot />
|
|
13
|
+
</AutocompleteItem>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<!--------------------------------------------------------------------------------------------------------------------->
|
|
17
|
+
|
|
18
|
+
<style lang="scss" scoped>
|
|
19
|
+
// Component styles are in /src/styles/components/_autocomplete.scss
|
|
20
|
+
</style>
|
|
21
|
+
|
|
22
|
+
<!--------------------------------------------------------------------------------------------------------------------->
|
|
23
|
+
|
|
24
|
+
<script setup lang="ts">
|
|
25
|
+
/**
|
|
26
|
+
* @component SkAutocompleteItem
|
|
27
|
+
* @description A selectable suggestion within an SkAutocomplete dropdown. When chosen, the item's
|
|
28
|
+
* `value` is written into the input. Built on RekaUI's AutocompleteItem with keyboard navigation
|
|
29
|
+
* and type-ahead support.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```vue
|
|
33
|
+
* <SkAutocomplete v-model="query">
|
|
34
|
+
* <SkAutocompleteItem value="apple">Apple</SkAutocompleteItem>
|
|
35
|
+
* <SkAutocompleteItem value="banana">Banana</SkAutocompleteItem>
|
|
36
|
+
* <SkAutocompleteItem value="cherry" disabled>Cherry (out of stock)</SkAutocompleteItem>
|
|
37
|
+
* </SkAutocomplete>
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* @slot default - The display content for this suggestion. Can be plain text or rich content.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
import { computed } from 'vue';
|
|
44
|
+
import { AutocompleteItem } from 'reka-ui';
|
|
45
|
+
|
|
46
|
+
//------------------------------------------------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
export interface SkAutocompleteItemComponentProps
|
|
49
|
+
{
|
|
50
|
+
/**
|
|
51
|
+
* The string written into the input when this item is selected. Must be unique
|
|
52
|
+
* within the autocomplete to ensure proper highlight/match behavior.
|
|
53
|
+
*/
|
|
54
|
+
value : string;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* When true, this suggestion is disabled and cannot be selected. Skipped during
|
|
58
|
+
* keyboard navigation.
|
|
59
|
+
* @default false
|
|
60
|
+
*/
|
|
61
|
+
disabled ?: boolean;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Optional override for filter/typeahead matching. Use when the slot content is
|
|
65
|
+
* non-textual (icons, custom layouts) and you need to specify what text the
|
|
66
|
+
* client-side filter should match against.
|
|
67
|
+
*/
|
|
68
|
+
textValue ?: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
//------------------------------------------------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
withDefaults(defineProps<SkAutocompleteItemComponentProps>(), {
|
|
74
|
+
disabled: false,
|
|
75
|
+
textValue: undefined,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
//------------------------------------------------------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
const classes = computed(() => ({
|
|
81
|
+
'sk-autocomplete-item': true,
|
|
82
|
+
}));
|
|
83
|
+
</script>
|
|
84
|
+
|
|
85
|
+
<!--------------------------------------------------------------------------------------------------------------------->
|