@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.
- 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 +15 -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 +836 -280
- package/dist/sleekspace-ui.es.js +3759 -2545
- package/dist/sleekspace-ui.umd.js +3765 -2543
- 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/dist/utils/slots.d.ts +6 -0
- package/llms-full.txt +17 -9
- package/package.json +9 -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/Alert/SkAlert.vue +4 -2
- 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 +11 -14
- package/src/components/Breadcrumbs/context.ts +20 -0
- package/src/components/Button/SkButton.vue +54 -11
- package/src/components/Button/types.ts +6 -0
- package/src/components/Card/SkCard.vue +12 -5
- package/src/components/Checkbox/SkCheckbox.vue +9 -2
- 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/Modal/SkModal.vue +11 -4
- package/src/components/NavBar/SkNavBar.vue +5 -4
- package/src/components/NumberInput/SkNumberInput.vue +49 -8
- package/src/components/NumberInput/types.ts +2 -0
- package/src/components/Page/SkPage.vue +18 -15
- 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/Popover/SkPopover.vue +11 -4
- package/src/components/Radio/SkRadio.vue +14 -4
- 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/Switch/SkSwitch.vue +14 -13
- package/src/components/Tabs/SkTab.vue +10 -3
- 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/components/TreeView/SkTreeItem.vue +10 -2
- 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 +25 -9
- package/src/static/generated/propTypes.ts +19 -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 +88 -14
- 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 +80 -0
- package/web-types.json +392 -12
|
@@ -94,6 +94,7 @@
|
|
|
94
94
|
|
|
95
95
|
// Context
|
|
96
96
|
import { NAVBAR_SIZE_KEY } from '../NavBar/context';
|
|
97
|
+
import { DROPDOWN_KIND_KEY } from './context';
|
|
97
98
|
|
|
98
99
|
//------------------------------------------------------------------------------------------------------------------
|
|
99
100
|
|
|
@@ -170,7 +171,7 @@
|
|
|
170
171
|
const effectiveSize = computed<SkDropdownSize>(() => props.size ?? navbarSize?.value ?? 'md');
|
|
171
172
|
|
|
172
173
|
// Provide kind for submenus (reactive computed so changes propagate)
|
|
173
|
-
provide(
|
|
174
|
+
provide(DROPDOWN_KIND_KEY, computed(() => props.kind));
|
|
174
175
|
|
|
175
176
|
//------------------------------------------------------------------------------------------------------------------
|
|
176
177
|
|
|
@@ -75,6 +75,8 @@
|
|
|
75
75
|
// Types
|
|
76
76
|
import type { SkDropdownKind } from './types';
|
|
77
77
|
|
|
78
|
+
import { DROPDOWN_KIND_KEY } from './context';
|
|
79
|
+
|
|
78
80
|
//------------------------------------------------------------------------------------------------------------------
|
|
79
81
|
|
|
80
82
|
export interface SkDropdownSubmenuComponentProps
|
|
@@ -105,12 +107,11 @@
|
|
|
105
107
|
// Handle portal context (theme injection/re-provision for nested portal components)
|
|
106
108
|
const { theme } = usePortalContext();
|
|
107
109
|
|
|
108
|
-
|
|
109
|
-
const parentKind = inject<any>('dropdown-kind', computed(() => 'neutral'));
|
|
110
|
+
const parentKind = inject(DROPDOWN_KIND_KEY, computed(() => undefined));
|
|
110
111
|
|
|
111
112
|
//------------------------------------------------------------------------------------------------------------------
|
|
112
113
|
|
|
113
|
-
const effectiveKind = computed(() => props.kind
|
|
114
|
+
const effectiveKind = computed<SkDropdownKind>(() => props.kind ?? parentKind.value ?? 'neutral');
|
|
114
115
|
|
|
115
116
|
//------------------------------------------------------------------------------------------------------------------
|
|
116
117
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
2
|
+
// Dropdown Context
|
|
3
|
+
//
|
|
4
|
+
// Injection key for nested SkDropdownSubmenu descendants to inherit the surrounding SkDropdown's
|
|
5
|
+
// kind without manual wiring.
|
|
6
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
import type { ComputedRef, InjectionKey } from 'vue';
|
|
9
|
+
|
|
10
|
+
import type { SkDropdownKind } from './types';
|
|
11
|
+
|
|
12
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
export const DROPDOWN_KIND_KEY : InjectionKey<ComputedRef<SkDropdownKind | undefined>> = Symbol('sk-dropdown-kind');
|
|
15
|
+
|
|
16
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
2
|
+
// SkField Tests — focuses on the validation-kind provide contract.
|
|
3
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
4
|
+
|
|
5
|
+
import { computed, defineComponent, h, inject } from 'vue';
|
|
6
|
+
import { mount } from '@vue/test-utils';
|
|
7
|
+
import { describe, expect, it } from 'vitest';
|
|
8
|
+
|
|
9
|
+
import { inheritedKindKey, validationKindKey } from '@/composables/injectionKeys';
|
|
10
|
+
|
|
11
|
+
import SkField from './SkField.vue';
|
|
12
|
+
|
|
13
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
// Probe component that surfaces the validation-kind / inherited-kind it sees as data attributes.
|
|
16
|
+
// Lets us assert what SkField actually puts on each channel without relying on a leaf form
|
|
17
|
+
// component's class output.
|
|
18
|
+
const Probe = defineComponent({
|
|
19
|
+
setup()
|
|
20
|
+
{
|
|
21
|
+
const validation = inject(validationKindKey, computed(() => 'NONE'));
|
|
22
|
+
const inherited = inject(inheritedKindKey, computed(() => 'NONE'));
|
|
23
|
+
return () => h('div', {
|
|
24
|
+
'data-validation': validation.value ?? 'undef',
|
|
25
|
+
'data-inherited': inherited.value ?? 'undef',
|
|
26
|
+
});
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
function mountField(props : Record<string, unknown> = {}) : ReturnType<typeof mount>
|
|
33
|
+
{
|
|
34
|
+
return mount(SkField, {
|
|
35
|
+
props,
|
|
36
|
+
slots: { default: () => h(Probe) },
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
describe('SkField validation-kind contract', () =>
|
|
43
|
+
{
|
|
44
|
+
it('provides undefined on validation-kind when state is null', () =>
|
|
45
|
+
{
|
|
46
|
+
const wrapper = mountField({ state: null });
|
|
47
|
+
expect(wrapper.find('[data-validation]').attributes('data-validation')).toBe('undef');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('provides undefined on validation-kind when state is omitted', () =>
|
|
51
|
+
{
|
|
52
|
+
const wrapper = mountField();
|
|
53
|
+
expect(wrapper.find('[data-validation]').attributes('data-validation')).toBe('undef');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('provides validKind on validation-kind when state is true', () =>
|
|
57
|
+
{
|
|
58
|
+
const wrapper = mountField({ state: true });
|
|
59
|
+
expect(wrapper.find('[data-validation]').attributes('data-validation')).toBe('success');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('provides invalidKind on validation-kind when state is false', () =>
|
|
63
|
+
{
|
|
64
|
+
const wrapper = mountField({ state: false });
|
|
65
|
+
expect(wrapper.find('[data-validation]').attributes('data-validation')).toBe('danger');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('honors custom validKind / invalidKind', () =>
|
|
69
|
+
{
|
|
70
|
+
const valid = mountField({ state: true, validKind: 'accent' });
|
|
71
|
+
expect(valid.find('[data-validation]').attributes('data-validation')).toBe('accent');
|
|
72
|
+
|
|
73
|
+
const invalid = mountField({ state: false, invalidKind: 'warning' });
|
|
74
|
+
expect(invalid.find('[data-validation]').attributes('data-validation')).toBe('warning');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('does NOT touch the inherited-kind channel', () =>
|
|
78
|
+
{
|
|
79
|
+
// Validation override and ambient default live on separate channels. SkField must not
|
|
80
|
+
// bleed into inherited-kind, or an InputGroup further down the tree would pick up the
|
|
81
|
+
// validation kind as if it were an ambient default. The probe falls back to 'NONE' when
|
|
82
|
+
// no provider exists for inherited-kind — that's the assertion.
|
|
83
|
+
const wrapper = mountField({ state: false });
|
|
84
|
+
expect(wrapper.find('[data-inherited]').attributes('data-inherited')).toBe('NONE');
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
@@ -58,8 +58,12 @@
|
|
|
58
58
|
import { computed, provide } from 'vue';
|
|
59
59
|
|
|
60
60
|
// Types
|
|
61
|
+
import type { ComponentKind } from '@/types';
|
|
61
62
|
import type { SkFieldLabelPosition } from './types';
|
|
62
63
|
|
|
64
|
+
// Composables
|
|
65
|
+
import { validationKindKey } from '@/composables/injectionKeys';
|
|
66
|
+
|
|
63
67
|
//------------------------------------------------------------------------------------------------------------------
|
|
64
68
|
|
|
65
69
|
export interface SkFieldComponentProps
|
|
@@ -127,7 +131,7 @@
|
|
|
127
131
|
* inputs via Vue's provide/inject system.
|
|
128
132
|
* @default 'success'
|
|
129
133
|
*/
|
|
130
|
-
validKind ?:
|
|
134
|
+
validKind ?: ComponentKind;
|
|
131
135
|
|
|
132
136
|
/**
|
|
133
137
|
* Semantic kind to apply to the child input when `state` is `false` (invalid). Typically
|
|
@@ -135,7 +139,7 @@
|
|
|
135
139
|
* inputs via Vue's provide/inject system.
|
|
136
140
|
* @default 'danger'
|
|
137
141
|
*/
|
|
138
|
-
invalidKind ?:
|
|
142
|
+
invalidKind ?: ComponentKind;
|
|
139
143
|
}
|
|
140
144
|
|
|
141
145
|
//------------------------------------------------------------------------------------------------------------------
|
|
@@ -189,8 +193,10 @@
|
|
|
189
193
|
|
|
190
194
|
//------------------------------------------------------------------------------------------------------------------
|
|
191
195
|
|
|
192
|
-
// Determine the kind to apply based on state prop
|
|
193
|
-
|
|
196
|
+
// Determine the kind to apply based on state prop. Only emits a non-undefined value when the
|
|
197
|
+
// dev opted into validation by setting `state`; otherwise children fall through to their own
|
|
198
|
+
// kind prop / inherited-kind / default.
|
|
199
|
+
const effectiveKind = computed<ComponentKind | undefined>(() =>
|
|
194
200
|
{
|
|
195
201
|
if(props.state === true)
|
|
196
202
|
{
|
|
@@ -200,13 +206,15 @@
|
|
|
200
206
|
{
|
|
201
207
|
return props.invalidKind;
|
|
202
208
|
}
|
|
203
|
-
return undefined;
|
|
209
|
+
return undefined;
|
|
204
210
|
});
|
|
205
211
|
|
|
206
212
|
//------------------------------------------------------------------------------------------------------------------
|
|
207
213
|
|
|
208
|
-
//
|
|
209
|
-
|
|
214
|
+
// Validation channel: always wins over the child's prop and any inherited-kind ambient
|
|
215
|
+
// default. Only carries a value when `state` is set, so the absence of a state prop leaves
|
|
216
|
+
// children fully in control.
|
|
217
|
+
provide(validationKindKey, effectiveKind);
|
|
210
218
|
|
|
211
219
|
//------------------------------------------------------------------------------------------------------------------
|
|
212
220
|
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
2
|
+
// SkInput — input-group-size inject tests
|
|
3
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
4
|
+
|
|
5
|
+
import { mount } from '@vue/test-utils';
|
|
6
|
+
import { computed, defineComponent, h, provide } from 'vue';
|
|
7
|
+
import { describe, expect, it } from 'vitest';
|
|
8
|
+
|
|
9
|
+
import { inputGroupSizeKey } from '@/composables/injectionKeys';
|
|
10
|
+
|
|
11
|
+
import SkInput from './SkInput.vue';
|
|
12
|
+
|
|
13
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
describe('SkInput input-group-size inject', () =>
|
|
16
|
+
{
|
|
17
|
+
it('uses injected size when no explicit size prop is set', () =>
|
|
18
|
+
{
|
|
19
|
+
const Provider = defineComponent({
|
|
20
|
+
setup(_, { slots })
|
|
21
|
+
{
|
|
22
|
+
provide(inputGroupSizeKey, computed(() => 'lg'));
|
|
23
|
+
return () => h('div', slots.default ? slots.default() : []);
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const wrapper = mount(Provider, {
|
|
28
|
+
slots: { default: () => h(SkInput) },
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const input = wrapper.find('input.sk-input');
|
|
32
|
+
expect(input.classes()).toContain('sk-lg');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('explicit size prop overrides injected size', () =>
|
|
36
|
+
{
|
|
37
|
+
const Provider = defineComponent({
|
|
38
|
+
setup(_, { slots })
|
|
39
|
+
{
|
|
40
|
+
provide(inputGroupSizeKey, computed(() => 'lg'));
|
|
41
|
+
return () => h('div', slots.default ? slots.default() : []);
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const wrapper = mount(Provider, {
|
|
46
|
+
slots: { default: () => h(SkInput, { size: 'sm' }) },
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const input = wrapper.find('input.sk-input');
|
|
50
|
+
expect(input.classes()).toContain('sk-sm');
|
|
51
|
+
expect(input.classes()).not.toContain('sk-lg');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('falls back to md when neither prop nor inject is set', () =>
|
|
55
|
+
{
|
|
56
|
+
const wrapper = mount(SkInput);
|
|
57
|
+
expect(wrapper.classes()).toContain('sk-md');
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
@@ -43,13 +43,20 @@
|
|
|
43
43
|
* ```
|
|
44
44
|
*/
|
|
45
45
|
|
|
46
|
-
import {
|
|
46
|
+
import { computed, inject, toRef } from 'vue';
|
|
47
47
|
|
|
48
48
|
// Types
|
|
49
49
|
import type { ComponentCustomColors } from '@/types';
|
|
50
|
-
import type { SkInputKind, SkInputSize, SkInputType } from './types';
|
|
50
|
+
import type { SkInputCorner, SkInputKind, SkInputSize, SkInputType } from './types';
|
|
51
51
|
|
|
52
52
|
// Composables
|
|
53
|
+
import {
|
|
54
|
+
NO_KIND,
|
|
55
|
+
NO_SIZE,
|
|
56
|
+
inheritedKindKey,
|
|
57
|
+
inputGroupSizeKey,
|
|
58
|
+
validationKindKey,
|
|
59
|
+
} from '@/composables/injectionKeys';
|
|
53
60
|
import { useCustomColors } from '@/composables/useCustomColors';
|
|
54
61
|
|
|
55
62
|
//------------------------------------------------------------------------------------------------------------------
|
|
@@ -79,6 +86,13 @@
|
|
|
79
86
|
*/
|
|
80
87
|
size ?: SkInputSize;
|
|
81
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Which corners receive the beveled cut. Pass an empty array for square corners.
|
|
91
|
+
* When omitted, defaults to the input's standalone visual (`top-right` only).
|
|
92
|
+
* @default undefined (renders as ['top-right'])
|
|
93
|
+
*/
|
|
94
|
+
corners ?: SkInputCorner[];
|
|
95
|
+
|
|
82
96
|
/**
|
|
83
97
|
* Placeholder text displayed when the input is empty. Use to provide hints about expected
|
|
84
98
|
* input format or example values. The placeholder disappears when the user begins typing.
|
|
@@ -131,7 +145,8 @@
|
|
|
131
145
|
const props = withDefaults(defineProps<SkInputComponentProps>(), {
|
|
132
146
|
type: 'text',
|
|
133
147
|
kind: undefined,
|
|
134
|
-
size:
|
|
148
|
+
size: undefined,
|
|
149
|
+
corners: undefined,
|
|
135
150
|
placeholder: undefined,
|
|
136
151
|
disabled: false,
|
|
137
152
|
readonly: false,
|
|
@@ -151,20 +166,40 @@
|
|
|
151
166
|
|
|
152
167
|
//------------------------------------------------------------------------------------------------------------------
|
|
153
168
|
|
|
154
|
-
//
|
|
155
|
-
|
|
169
|
+
// Validation override (SkField with `state` set) always wins. Inherited kind (an ambient
|
|
170
|
+
// provider like SkInputGroup) is the default when the dev didn't set `kind` themselves.
|
|
171
|
+
const validationKind = inject(validationKindKey, NO_KIND);
|
|
172
|
+
const inheritedKind = inject(inheritedKindKey, NO_KIND);
|
|
173
|
+
const inputGroupSize = inject(inputGroupSizeKey, NO_SIZE);
|
|
156
174
|
|
|
157
175
|
//------------------------------------------------------------------------------------------------------------------
|
|
158
176
|
|
|
159
|
-
const effectiveKind = computed(() =>
|
|
177
|
+
const effectiveKind = computed<SkInputKind>(() =>
|
|
178
|
+
{
|
|
179
|
+
if(validationKind.value !== undefined) { return validationKind.value; }
|
|
180
|
+
if(props.kind !== undefined) { return props.kind; }
|
|
181
|
+
if(inheritedKind.value !== undefined) { return inheritedKind.value; }
|
|
182
|
+
return 'neutral';
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const effectiveSize = computed<SkInputSize>(() =>
|
|
186
|
+
{
|
|
187
|
+
if(props.size !== undefined) { return props.size; }
|
|
188
|
+
if(inputGroupSize.value !== undefined) { return inputGroupSize.value; }
|
|
189
|
+
return 'md';
|
|
190
|
+
});
|
|
160
191
|
|
|
161
192
|
//------------------------------------------------------------------------------------------------------------------
|
|
162
193
|
|
|
163
194
|
const classes = computed(() => ({
|
|
164
195
|
'sk-input': true,
|
|
165
196
|
[`sk-${ effectiveKind.value }`]: true,
|
|
166
|
-
[`sk-${
|
|
197
|
+
[`sk-${ effectiveSize.value }`]: true,
|
|
167
198
|
'sk-readonly': props.readonly,
|
|
199
|
+
'sk-cut-top-left': props.corners?.includes('top-left') ?? false,
|
|
200
|
+
'sk-cut-top-right': props.corners?.includes('top-right') ?? false,
|
|
201
|
+
'sk-cut-bottom-right': props.corners?.includes('bottom-right') ?? false,
|
|
202
|
+
'sk-cut-bottom-left': props.corners?.includes('bottom-left') ?? false,
|
|
168
203
|
}));
|
|
169
204
|
|
|
170
205
|
//------------------------------------------------------------------------------------------------------------------
|
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
//----------------------------------------------------------------------------------------------------------------------
|
|
4
4
|
|
|
5
5
|
import type { ComponentKind, ComponentSize } from '@/types';
|
|
6
|
+
import type { SkCorner } from '@/types/corners';
|
|
6
7
|
|
|
7
8
|
export type SkInputKind = ComponentKind;
|
|
8
9
|
export type SkInputSize = ComponentSize;
|
|
9
10
|
export type SkInputType = 'text' | 'email' | 'password' | 'url' | 'tel' | 'search' | 'number';
|
|
11
|
+
export type SkInputCorner = SkCorner;
|
|
10
12
|
|
|
11
13
|
//----------------------------------------------------------------------------------------------------------------------
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
2
|
+
// SkInputGroup Tests
|
|
3
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
4
|
+
|
|
5
|
+
import { computed, defineComponent, h, inject, provide } from 'vue';
|
|
6
|
+
import { mount } from '@vue/test-utils';
|
|
7
|
+
import { describe, expect, it } from 'vitest';
|
|
8
|
+
|
|
9
|
+
import { inheritedKindKey, validationKindKey } from '@/composables/injectionKeys';
|
|
10
|
+
|
|
11
|
+
import SkInputGroup from './SkInputGroup.vue';
|
|
12
|
+
import SkInputGroupAddon from './SkInputGroupAddon.vue';
|
|
13
|
+
|
|
14
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
describe('SkInputGroup', () =>
|
|
17
|
+
{
|
|
18
|
+
it('renders a div with the group class and default classes', () =>
|
|
19
|
+
{
|
|
20
|
+
const wrapper = mount(SkInputGroup);
|
|
21
|
+
const div = wrapper.find('div.sk-input-group');
|
|
22
|
+
expect(div.exists()).toBe(true);
|
|
23
|
+
expect(div.classes()).toContain('sk-md');
|
|
24
|
+
expect(div.classes()).toContain('sk-neutral');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('applies corner classes from corners prop', () =>
|
|
28
|
+
{
|
|
29
|
+
const wrapper = mount(SkInputGroup, {
|
|
30
|
+
props: { corners: [ 'top-left', 'bottom-right' ] },
|
|
31
|
+
});
|
|
32
|
+
const div = wrapper.find('div.sk-input-group');
|
|
33
|
+
expect(div.classes()).toContain('sk-cut-top-left');
|
|
34
|
+
expect(div.classes()).toContain('sk-cut-bottom-right');
|
|
35
|
+
expect(div.classes()).not.toContain('sk-cut-top-right');
|
|
36
|
+
expect(div.classes()).not.toContain('sk-cut-bottom-left');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('provides input-group-size to children', () =>
|
|
40
|
+
{
|
|
41
|
+
const wrapper = mount(SkInputGroup, {
|
|
42
|
+
props: { size: 'lg' },
|
|
43
|
+
slots: { default: () => h(SkInputGroupAddon, null, { default: () => '$' }) },
|
|
44
|
+
});
|
|
45
|
+
const addon = wrapper.find('span.sk-input-group-addon');
|
|
46
|
+
expect(addon.classes()).toContain('sk-lg');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('provides inherited-kind to children', () =>
|
|
50
|
+
{
|
|
51
|
+
const wrapper = mount(SkInputGroup, {
|
|
52
|
+
props: { kind: 'primary' },
|
|
53
|
+
slots: { default: () => h(SkInputGroupAddon, null, { default: () => '$' }) },
|
|
54
|
+
});
|
|
55
|
+
const addon = wrapper.find('span.sk-input-group-addon');
|
|
56
|
+
expect(addon.classes()).toContain('sk-primary');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('inherits ambient kind from a parent inherited-kind provider', () =>
|
|
60
|
+
{
|
|
61
|
+
const AmbientStub = defineComponent({
|
|
62
|
+
setup(_, { slots })
|
|
63
|
+
{
|
|
64
|
+
provide(inheritedKindKey, computed(() => 'accent'));
|
|
65
|
+
return () => h('div', slots.default ? slots.default() : []);
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const wrapper = mount(AmbientStub, {
|
|
70
|
+
slots: {
|
|
71
|
+
default: () => h(SkInputGroup, null, {
|
|
72
|
+
default: () => h(SkInputGroupAddon, null, { default: () => '$' }),
|
|
73
|
+
}),
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const group = wrapper.find('div.sk-input-group');
|
|
78
|
+
const addon = wrapper.find('span.sk-input-group-addon');
|
|
79
|
+
expect(group.classes()).toContain('sk-accent');
|
|
80
|
+
expect(addon.classes()).toContain('sk-accent');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('explicit kind prop wins over ambient inherited-kind', () =>
|
|
84
|
+
{
|
|
85
|
+
const AmbientStub = defineComponent({
|
|
86
|
+
setup(_, { slots })
|
|
87
|
+
{
|
|
88
|
+
provide(inheritedKindKey, computed(() => 'accent'));
|
|
89
|
+
return () => h('div', slots.default ? slots.default() : []);
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const wrapper = mount(AmbientStub, {
|
|
94
|
+
slots: {
|
|
95
|
+
default: () => h(SkInputGroup, { kind: 'success' }, {
|
|
96
|
+
default: () => h(SkInputGroupAddon, null, { default: () => '$' }),
|
|
97
|
+
}),
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const group = wrapper.find('div.sk-input-group');
|
|
102
|
+
const addon = wrapper.find('span.sk-input-group-addon');
|
|
103
|
+
expect(group.classes()).toContain('sk-success');
|
|
104
|
+
expect(addon.classes()).toContain('sk-success');
|
|
105
|
+
expect(addon.classes()).not.toContain('sk-accent');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('parent validation-kind always wins over explicit kind', () =>
|
|
109
|
+
{
|
|
110
|
+
const FieldStub = defineComponent({
|
|
111
|
+
setup(_, { slots })
|
|
112
|
+
{
|
|
113
|
+
provide(validationKindKey, computed(() => 'danger'));
|
|
114
|
+
return () => h('div', slots.default ? slots.default() : []);
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const wrapper = mount(FieldStub, {
|
|
119
|
+
slots: {
|
|
120
|
+
default: () => h(SkInputGroup, { kind: 'success' }, {
|
|
121
|
+
default: () => h(SkInputGroupAddon, null, { default: () => '$' }),
|
|
122
|
+
}),
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const group = wrapper.find('div.sk-input-group');
|
|
127
|
+
const addon = wrapper.find('span.sk-input-group-addon');
|
|
128
|
+
expect(group.classes()).toContain('sk-danger');
|
|
129
|
+
expect(addon.classes()).toContain('sk-danger');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('re-provides inherited-kind from props.kind, not the validation-resolved kind', () =>
|
|
133
|
+
{
|
|
134
|
+
// The property under test: SkInputGroup's `inherited-kind` re-provide must use props.kind
|
|
135
|
+
// (or the parent's inherited-kind), NOT the validation-overridden effectiveKind. Otherwise
|
|
136
|
+
// a parent SkField's validation override would leak onto the inherited-kind channel and
|
|
137
|
+
// clobber descendants that consult only inherited-kind.
|
|
138
|
+
//
|
|
139
|
+
// To observe what the InputGroup actually puts on inherited-kind, we mount a synthetic
|
|
140
|
+
// probe inside the group that injects ONLY inherited-kind (no validation-kind) and
|
|
141
|
+
// surfaces the resolved value as a data attribute.
|
|
142
|
+
const Probe = defineComponent({
|
|
143
|
+
setup()
|
|
144
|
+
{
|
|
145
|
+
const seen = inject(inheritedKindKey, computed(() => 'NONE'));
|
|
146
|
+
return () => h('div', { 'data-seen': seen.value });
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const FieldStub = defineComponent({
|
|
151
|
+
setup(_, { slots })
|
|
152
|
+
{
|
|
153
|
+
provide(validationKindKey, computed(() => 'danger'));
|
|
154
|
+
return () => h('div', slots.default ? slots.default() : []);
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const wrapper = mount(FieldStub, {
|
|
159
|
+
slots: {
|
|
160
|
+
default: () => h(SkInputGroup, { kind: 'primary' }, {
|
|
161
|
+
default: () => h(Probe),
|
|
162
|
+
}),
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// The probe sees 'primary' (props.kind), not 'danger' (validation override).
|
|
167
|
+
expect(wrapper.find('[data-seen]').attributes('data-seen')).toBe('primary');
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
//----------------------------------------------------------------------------------------------------------------------
|