@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
@@ -0,0 +1,131 @@
1
+ <!----------------------------------------------------------------------------------------------------------------------
2
+ - InputGroup Component
3
+ --------------------------------------------------------------------------------------------------------------------->
4
+
5
+ <template>
6
+ <div :class="classes" :style="customColorStyles">
7
+ <slot />
8
+ </div>
9
+ </template>
10
+
11
+ <!--------------------------------------------------------------------------------------------------------------------->
12
+
13
+ <style lang="scss" scoped>
14
+ // Component styles are implemented in /src/styles/components/_input-group.scss
15
+ </style>
16
+
17
+ <!--------------------------------------------------------------------------------------------------------------------->
18
+
19
+ <script setup lang="ts">
20
+ /**
21
+ * @component SkInputGroup
22
+ * @description Wraps form inputs, buttons, selects, and SkInputGroupAddon children into a
23
+ * single visually unified control. Bevels appear only on the group's outer corners; interior
24
+ * joins are clean. Cascades kind and size to Vue children via provide/inject — size on the
25
+ * `input-group-size` channel, kind on the `inherited-kind` ambient-default channel. A
26
+ * parent SkField's validation override (the `validation-kind` channel) takes precedence over
27
+ * both the group's own kind and the descendants' kinds.
28
+ *
29
+ * @example
30
+ * ```vue
31
+ * <SkInputGroup :corners="['top-left', 'bottom-right']" size="md" kind="primary">
32
+ * <SkInputGroupAddon>$</SkInputGroupAddon>
33
+ * <SkInput v-model="amount" />
34
+ * <SkButton>Submit</SkButton>
35
+ * </SkInputGroup>
36
+ * ```
37
+ *
38
+ * @slot default - Form components, SkButton, and SkInputGroupAddon children in left-to-right order.
39
+ */
40
+
41
+ import { computed, inject, provide, toRef } from 'vue';
42
+
43
+ // Types
44
+ import type {
45
+ SkInputGroupBaseProps,
46
+ SkInputGroupCorner,
47
+ SkInputGroupKind,
48
+ SkInputGroupSize,
49
+ } from './types';
50
+
51
+ export type { SkInputGroupComponentProps } from './types';
52
+
53
+ // Composables
54
+ import {
55
+ NO_KIND,
56
+ NO_SIZE,
57
+ inheritedKindKey,
58
+ inputGroupSizeKey,
59
+ validationKindKey,
60
+ } from '@/composables/injectionKeys';
61
+ import { useCustomColors } from '@/composables/useCustomColors';
62
+
63
+ //------------------------------------------------------------------------------------------------------------------
64
+
65
+ const props = withDefaults(defineProps<SkInputGroupBaseProps>(), {
66
+ kind: undefined,
67
+ size: 'md',
68
+ corners: () => [ 'top-left', 'bottom-right' ],
69
+ });
70
+
71
+ //------------------------------------------------------------------------------------------------------------------
72
+
73
+ // Same resolution rule as the leaf form components: validation override (from a parent
74
+ // SkField with `state` set) wins, then explicit prop, then ambient inherited-kind, then
75
+ // 'neutral'. Validation-kind passes through to descendants on its own channel — we never
76
+ // re-provide it.
77
+ const validationKind = inject(validationKindKey, NO_KIND);
78
+ const inheritedKind = inject(inheritedKindKey, NO_KIND);
79
+
80
+ const effectiveKind = computed<SkInputGroupKind>(() =>
81
+ {
82
+ if(validationKind.value !== undefined) { return validationKind.value; }
83
+ if(props.kind !== undefined) { return props.kind; }
84
+ if(inheritedKind.value !== undefined) { return inheritedKind.value; }
85
+ return 'neutral';
86
+ });
87
+
88
+ //------------------------------------------------------------------------------------------------------------------
89
+
90
+ // Re-provide on the inherited-kind channel so descendants pick up the group's prop-driven
91
+ // kind as their default. Use the prop directly (not the resolved value) so a parent SkField's
92
+ // validation override doesn't propagate as the group's "ambient default" and clobber children
93
+ // that have their own explicit kind.
94
+ const ambientKind = computed<SkInputGroupKind | undefined>(() =>
95
+ {
96
+ if(props.kind !== undefined) { return props.kind; }
97
+ return inheritedKind.value;
98
+ });
99
+ provide(inheritedKindKey, ambientKind);
100
+
101
+ // Size passes straight through to the input-group-size channel — no resolution to do.
102
+ const sizeRef = toRef(props, 'size');
103
+ provide(inputGroupSizeKey, sizeRef);
104
+
105
+ //------------------------------------------------------------------------------------------------------------------
106
+
107
+ function hasCorner(corner : SkInputGroupCorner) : boolean
108
+ {
109
+ return props.corners.includes(corner);
110
+ }
111
+
112
+ const classes = computed<Record<string, boolean>>(() => ({
113
+ 'sk-input-group': true,
114
+ [`sk-${ effectiveKind.value }`]: true,
115
+ [`sk-${ sizeRef.value }`]: true,
116
+ 'sk-cut-top-left': hasCorner('top-left'),
117
+ 'sk-cut-top-right': hasCorner('top-right'),
118
+ 'sk-cut-bottom-right': hasCorner('bottom-right'),
119
+ 'sk-cut-bottom-left': hasCorner('bottom-left'),
120
+ }));
121
+
122
+ //------------------------------------------------------------------------------------------------------------------
123
+
124
+ const customColorStyles = useCustomColors(
125
+ 'input-group',
126
+ toRef(() => props.baseColor),
127
+ toRef(() => props.textColor)
128
+ );
129
+ </script>
130
+
131
+ <!--------------------------------------------------------------------------------------------------------------------->
@@ -0,0 +1,104 @@
1
+ import { type InjectionKey, computed, defineComponent, h, provide } from 'vue';
2
+ import { mount } from '@vue/test-utils';
3
+ import { describe, expect, it } from 'vitest';
4
+
5
+ import { inheritedKindKey, inputGroupSizeKey, validationKindKey } from '@/composables/injectionKeys';
6
+
7
+ import SkInputGroupAddon from './SkInputGroupAddon.vue';
8
+
9
+ //----------------------------------------------------------------------------------------------------------------------
10
+
11
+ function withProvides(
12
+ children : () => ReturnType<typeof h>,
13
+ provides : Map<InjectionKey<unknown>, unknown> = new Map<InjectionKey<unknown>, unknown>()
14
+ ) : ReturnType<typeof defineComponent>
15
+ {
16
+ return defineComponent({
17
+ setup()
18
+ {
19
+ for(const [ key, value ] of provides)
20
+ {
21
+ provide(key, value);
22
+ }
23
+ return () => h('div', children());
24
+ },
25
+ });
26
+ }
27
+
28
+ //----------------------------------------------------------------------------------------------------------------------
29
+
30
+ describe('SkInputGroupAddon', () =>
31
+ {
32
+ it('renders a span with the addon class', () =>
33
+ {
34
+ const wrapper = mount(SkInputGroupAddon, { slots: { default: '$' } });
35
+ const span = wrapper.find('span.sk-input-group-addon');
36
+ expect(span.exists()).toBe(true);
37
+ expect(span.text()).toBe('$');
38
+ });
39
+
40
+ it('uses injected input-group-size when no explicit size is set', () =>
41
+ {
42
+ const Provider = withProvides(
43
+ () => h(SkInputGroupAddon, null, { default: () => '$' }),
44
+ new Map<InjectionKey<unknown>, unknown>([ [ inputGroupSizeKey, computed(() => 'lg') ] ])
45
+ );
46
+ const wrapper = mount(Provider);
47
+ const span = wrapper.find('span.sk-input-group-addon');
48
+ expect(span.classes()).toContain('sk-lg');
49
+ });
50
+
51
+ it('uses injected inherited-kind when no explicit kind is set', () =>
52
+ {
53
+ const Provider = withProvides(
54
+ () => h(SkInputGroupAddon, null, { default: () => '$' }),
55
+ new Map<InjectionKey<unknown>, unknown>([ [ inheritedKindKey, computed(() => 'primary') ] ])
56
+ );
57
+ const wrapper = mount(Provider);
58
+ const span = wrapper.find('span.sk-input-group-addon');
59
+ expect(span.classes()).toContain('sk-primary');
60
+ });
61
+
62
+ it('validation-kind always wins over explicit kind and inherited-kind', () =>
63
+ {
64
+ const Provider = withProvides(
65
+ () => h(SkInputGroupAddon, { kind: 'success' }, { default: () => '$' }),
66
+ new Map<InjectionKey<unknown>, unknown>([
67
+ [ validationKindKey, computed(() => 'danger') ],
68
+ [ inheritedKindKey, computed(() => 'primary') ],
69
+ ])
70
+ );
71
+ const wrapper = mount(Provider);
72
+ const span = wrapper.find('span.sk-input-group-addon');
73
+ expect(span.classes()).toContain('sk-danger');
74
+ expect(span.classes()).not.toContain('sk-success');
75
+ expect(span.classes()).not.toContain('sk-primary');
76
+ });
77
+
78
+ it('explicit props override inherited-kind and input-group-size', () =>
79
+ {
80
+ const Provider = withProvides(
81
+ () => h(SkInputGroupAddon, { size: 'sm', kind: 'danger' }, { default: () => '$' }),
82
+ new Map<InjectionKey<unknown>, unknown>([
83
+ [ inputGroupSizeKey, computed(() => 'lg') ],
84
+ [ inheritedKindKey, computed(() => 'primary') ],
85
+ ])
86
+ );
87
+ const wrapper = mount(Provider);
88
+ const span = wrapper.find('span.sk-input-group-addon');
89
+ expect(span.classes()).toContain('sk-sm');
90
+ expect(span.classes()).toContain('sk-danger');
91
+ expect(span.classes()).not.toContain('sk-lg');
92
+ expect(span.classes()).not.toContain('sk-primary');
93
+ });
94
+
95
+ it('defaults to md/neutral when nothing is provided', () =>
96
+ {
97
+ const wrapper = mount(SkInputGroupAddon);
98
+ const span = wrapper.find('span.sk-input-group-addon');
99
+ expect(span.classes()).toContain('sk-md');
100
+ expect(span.classes()).toContain('sk-neutral');
101
+ });
102
+ });
103
+
104
+ //----------------------------------------------------------------------------------------------------------------------
@@ -0,0 +1,107 @@
1
+ <!----------------------------------------------------------------------------------------------------------------------
2
+ - InputGroup Addon Component
3
+ --------------------------------------------------------------------------------------------------------------------->
4
+
5
+ <template>
6
+ <span :class="classes">
7
+ <slot />
8
+ </span>
9
+ </template>
10
+
11
+ <!--------------------------------------------------------------------------------------------------------------------->
12
+
13
+ <style lang="scss" scoped>
14
+ // Component styles are implemented in /src/styles/components/_input-group.scss
15
+ </style>
16
+
17
+ <!--------------------------------------------------------------------------------------------------------------------->
18
+
19
+ <script setup lang="ts">
20
+ /**
21
+ * @component SkInputGroupAddon
22
+ * @description Styled wrapper for non-component content (text, icons, plain HTML) inside an
23
+ * SkInputGroup. Renders as a span with the addon background, border, and font sizing matching
24
+ * the surrounding group. Inherits size and kind from the parent SkInputGroup via inject.
25
+ *
26
+ * @example
27
+ * ```vue
28
+ * <SkInputGroup>
29
+ * <SkInputGroupAddon>$</SkInputGroupAddon>
30
+ * <SkInput v-model="amount" />
31
+ * </SkInputGroup>
32
+ * ```
33
+ *
34
+ * @slot default - Addon content. Text, icons, or any inline element.
35
+ */
36
+
37
+ import { computed, inject } from 'vue';
38
+
39
+ // Types
40
+ import type { SkInputGroupAddonKind, SkInputGroupAddonSize } from './types';
41
+
42
+ // Composables
43
+ import {
44
+ NO_KIND,
45
+ NO_SIZE,
46
+ inheritedKindKey,
47
+ inputGroupSizeKey,
48
+ validationKindKey,
49
+ } from '@/composables/injectionKeys';
50
+
51
+ //------------------------------------------------------------------------------------------------------------------
52
+
53
+ export interface SkInputGroupAddonComponentProps
54
+ {
55
+ /**
56
+ * Semantic color kind. Inherits from a parent SkInputGroup or SkField when not set.
57
+ * @default inherited or 'neutral'
58
+ */
59
+ kind ?: SkInputGroupAddonKind;
60
+
61
+ /**
62
+ * Size of the addon. Inherits from a parent SkInputGroup when not set.
63
+ * @default inherited or 'md'
64
+ */
65
+ size ?: SkInputGroupAddonSize;
66
+ }
67
+
68
+ //------------------------------------------------------------------------------------------------------------------
69
+
70
+ const props = withDefaults(defineProps<SkInputGroupAddonComponentProps>(), {
71
+ kind: undefined,
72
+ size: undefined,
73
+ });
74
+
75
+ //------------------------------------------------------------------------------------------------------------------
76
+
77
+ const validationKind = inject(validationKindKey, NO_KIND);
78
+ const inheritedKind = inject(inheritedKindKey, NO_KIND);
79
+ const inputGroupSize = inject(inputGroupSizeKey, NO_SIZE);
80
+
81
+ //------------------------------------------------------------------------------------------------------------------
82
+
83
+ const effectiveKind = computed<SkInputGroupAddonKind>(() =>
84
+ {
85
+ if(validationKind.value !== undefined) { return validationKind.value; }
86
+ if(props.kind !== undefined) { return props.kind; }
87
+ if(inheritedKind.value !== undefined) { return inheritedKind.value; }
88
+ return 'neutral';
89
+ });
90
+
91
+ const effectiveSize = computed<SkInputGroupAddonSize>(() =>
92
+ {
93
+ if(props.size !== undefined) { return props.size; }
94
+ if(inputGroupSize.value !== undefined) { return inputGroupSize.value; }
95
+ return 'md';
96
+ });
97
+
98
+ //------------------------------------------------------------------------------------------------------------------
99
+
100
+ const classes = computed<Record<string, boolean>>(() => ({
101
+ 'sk-input-group-addon': true,
102
+ [`sk-${ effectiveKind.value }`]: true,
103
+ [`sk-${ effectiveSize.value }`]: true,
104
+ }));
105
+ </script>
106
+
107
+ <!--------------------------------------------------------------------------------------------------------------------->
@@ -0,0 +1,27 @@
1
+ //----------------------------------------------------------------------------------------------------------------------
2
+ // InputGroup Component Types
3
+ //----------------------------------------------------------------------------------------------------------------------
4
+
5
+ import type { ComponentCustomColors, ComponentKind, ComponentSize } from '@/types';
6
+ import type { SkCorner } from '@/types/corners';
7
+
8
+ export type SkInputGroupKind = ComponentKind;
9
+ export type SkInputGroupSize = ComponentSize;
10
+
11
+ export type SkInputGroupCorner = SkCorner;
12
+
13
+ export type SkInputGroupAddonKind = SkInputGroupKind;
14
+ export type SkInputGroupAddonSize = SkInputGroupSize;
15
+
16
+ //----------------------------------------------------------------------------------------------------------------------
17
+
18
+ export interface SkInputGroupBaseProps extends ComponentCustomColors
19
+ {
20
+ kind ?: SkInputGroupKind;
21
+ size ?: SkInputGroupSize;
22
+ corners ?: SkInputGroupCorner[];
23
+ }
24
+
25
+ export type SkInputGroupComponentProps = SkInputGroupBaseProps;
26
+
27
+ //----------------------------------------------------------------------------------------------------------------------
@@ -71,7 +71,7 @@
71
71
  * to create visual dividers between groups of options.
72
72
  */
73
73
 
74
- import { type ComputedRef, computed, inject, toRef } from 'vue';
74
+ import { computed, inject, toRef } from 'vue';
75
75
  import {
76
76
  ComboboxAnchor,
77
77
  ComboboxContent,
@@ -87,6 +87,13 @@
87
87
  import type { SkListboxKind, SkListboxSize } from './types';
88
88
 
89
89
  // Composables
90
+ import {
91
+ NO_KIND,
92
+ NO_SIZE,
93
+ inheritedKindKey,
94
+ inputGroupSizeKey,
95
+ validationKindKey,
96
+ } from '@/composables/injectionKeys';
90
97
  import { useCustomColors } from '@/composables/useCustomColors';
91
98
  import { usePortalContext } from '@/composables/usePortalContext';
92
99
 
@@ -131,7 +138,7 @@
131
138
 
132
139
  const props = withDefaults(defineProps<SkListboxComponentProps>(), {
133
140
  kind: undefined,
134
- size: 'md',
141
+ size: undefined,
135
142
  placeholder: 'Search...',
136
143
  disabled: false,
137
144
  });
@@ -150,19 +157,33 @@
150
157
  // Handle portal context (theme injection/re-provision for nested portal components)
151
158
  const { theme } = usePortalContext();
152
159
 
153
- // Inject field kind from parent SkField
154
- const fieldKind = inject<ComputedRef<SkListboxKind | undefined>>('field-kind', computed(() => undefined));
160
+ const validationKind = inject(validationKindKey, NO_KIND);
161
+ const inheritedKind = inject(inheritedKindKey, NO_KIND);
162
+ const inputGroupSize = inject(inputGroupSizeKey, NO_SIZE);
155
163
 
156
164
  //------------------------------------------------------------------------------------------------------------------
157
165
 
158
- const effectiveKind = computed(() => fieldKind.value || props.kind || 'neutral');
166
+ const effectiveKind = computed<SkListboxKind>(() =>
167
+ {
168
+ if(validationKind.value !== undefined) { return validationKind.value; }
169
+ if(props.kind !== undefined) { return props.kind; }
170
+ if(inheritedKind.value !== undefined) { return inheritedKind.value; }
171
+ return 'neutral';
172
+ });
173
+
174
+ const effectiveSize = computed<SkListboxSize>(() =>
175
+ {
176
+ if(props.size !== undefined) { return props.size; }
177
+ if(inputGroupSize.value !== undefined) { return inputGroupSize.value; }
178
+ return 'md';
179
+ });
159
180
 
160
181
  //------------------------------------------------------------------------------------------------------------------
161
182
 
162
183
  const wrapperClasses = computed(() => ({
163
184
  'sk-listbox': true,
164
185
  [`sk-${ effectiveKind.value }`]: true,
165
- [`sk-${ props.size }`]: true,
186
+ [`sk-${ effectiveSize.value }`]: true,
166
187
  }));
167
188
 
168
189
  //------------------------------------------------------------------------------------------------------------------
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <DialogRoot v-model:open="isOpen">
3
- <DialogTrigger v-if="$slots.trigger || triggerText" as-child>
3
+ <DialogTrigger v-if="hasSlotContent(slots.trigger) || triggerText" as-child>
4
4
  <slot name="trigger">
5
5
  <SkButton :kind="kind">
6
6
  {{ triggerText }}
@@ -17,7 +17,7 @@
17
17
  @pointer-down-outside="handleOverlayClick"
18
18
  @interact-outside="handleOverlayClick"
19
19
  >
20
- <div v-if="$slots.title || title" class="sk-modal-header">
20
+ <div v-if="hasSlotContent(slots.title) || title" class="sk-modal-header">
21
21
  <DialogTitle class="sk-modal-title">
22
22
  <slot name="title" :close="close">
23
23
  {{ title }}
@@ -40,7 +40,7 @@
40
40
  <div class="sk-modal-body">
41
41
  <slot :close="close" />
42
42
  </div>
43
- <div v-if="$slots.footer" class="sk-modal-footer">
43
+ <div v-if="hasSlotContent(slots.footer)" class="sk-modal-footer">
44
44
  <slot name="footer" :close="close" />
45
45
  </div>
46
46
  </DialogContent>
@@ -88,7 +88,7 @@
88
88
  * @slot footer - Footer content, typically containing action buttons. Receives `{ close }` slot prop.
89
89
  */
90
90
 
91
- import { computed, ref, toRef, watch } from 'vue';
91
+ import { type Slots, computed, ref, toRef, useSlots, watch } from 'vue';
92
92
  import {
93
93
  DialogClose,
94
94
  DialogContent,
@@ -106,6 +106,9 @@
106
106
  import { useCustomColors } from '@/composables/useCustomColors';
107
107
  import { usePortalContext } from '@/composables/usePortalContext';
108
108
 
109
+ // Utils
110
+ import { hasSlotContent } from '@/utils/slots';
111
+
109
112
  // Components
110
113
  import SkButton from '../Button/SkButton.vue';
111
114
 
@@ -206,6 +209,10 @@
206
209
 
207
210
  //------------------------------------------------------------------------------------------------------------------
208
211
 
212
+ const slots : Slots = useSlots();
213
+
214
+ //------------------------------------------------------------------------------------------------------------------
215
+
209
216
  const isOpen = ref(props.open);
210
217
 
211
218
  watch(() => props.open, (newValue) =>
@@ -5,19 +5,19 @@
5
5
  <template>
6
6
  <nav :class="classes" :style="customColorStyles">
7
7
  <div class="sk-navbar-content">
8
- <div v-if="slots.leading" class="sk-navbar-leading">
8
+ <div v-if="hasSlotContent(slots.leading)" class="sk-navbar-leading">
9
9
  <slot name="leading" />
10
10
  </div>
11
11
 
12
- <div v-if="slots.brand" class="sk-navbar-brand">
12
+ <div v-if="hasSlotContent(slots.brand)" class="sk-navbar-brand">
13
13
  <slot name="brand" />
14
14
  </div>
15
15
 
16
- <div v-if="slots.default" class="sk-navbar-nav">
16
+ <div v-if="hasSlotContent(slots.default)" class="sk-navbar-nav">
17
17
  <slot />
18
18
  </div>
19
19
 
20
- <div v-if="slots.actions" class="sk-navbar-actions">
20
+ <div v-if="hasSlotContent(slots.actions)" class="sk-navbar-actions">
21
21
  <slot name="actions" />
22
22
  </div>
23
23
  </div>
@@ -62,6 +62,7 @@
62
62
  import type { SkNavBarKind, SkNavBarSize } from './types';
63
63
  import type { ComponentCustomColors } from '@/types';
64
64
  import { useCustomColors } from '@/composables/useCustomColors';
65
+ import { hasSlotContent } from '@/utils/slots';
65
66
  import { NAVBAR_KIND_KEY, NAVBAR_SIZE_KEY } from './context';
66
67
 
67
68
  //------------------------------------------------------------------------------------------------------------------
@@ -17,7 +17,7 @@
17
17
  :step="step"
18
18
  >
19
19
  <NumberFieldInput :class="inputClasses" :placeholder="placeholder" />
20
- <div class="sk-number-input-steppers">
20
+ <div v-if="showSteppers" class="sk-number-input-steppers">
21
21
  <NumberFieldIncrement class="sk-number-input-increment">
22
22
  <svg
23
23
  xmlns="http://www.w3.org/2000/svg"
@@ -74,7 +74,7 @@
74
74
  * ```
75
75
  */
76
76
 
77
- import { type ComputedRef, computed, inject, toRef } from 'vue';
77
+ import { computed, inject, toRef } from 'vue';
78
78
  import {
79
79
  NumberFieldDecrement,
80
80
  NumberFieldIncrement,
@@ -84,9 +84,16 @@
84
84
 
85
85
  // Types
86
86
  import type { ComponentCustomColors } from '@/types';
87
- import type { SkNumberInputKind, SkNumberInputSize } from './types';
87
+ import type { SkNumberInputCorner, SkNumberInputKind, SkNumberInputSize } from './types';
88
88
 
89
89
  // Composables
90
+ import {
91
+ NO_KIND,
92
+ NO_SIZE,
93
+ inheritedKindKey,
94
+ inputGroupSizeKey,
95
+ validationKindKey,
96
+ } from '@/composables/injectionKeys';
90
97
  import { useCustomColors } from '@/composables/useCustomColors';
91
98
 
92
99
  //------------------------------------------------------------------------------------------------------------------
@@ -170,13 +177,27 @@
170
177
  * @default 1
171
178
  */
172
179
  step ?: number;
180
+
181
+ /**
182
+ * When true, renders the increment/decrement stepper buttons on the right side of the
183
+ * input. Set to false for a plain numeric text field (users can still use arrow keys
184
+ * and type values directly).
185
+ * @default true
186
+ */
187
+ showSteppers ?: boolean;
188
+
189
+ /**
190
+ * Which corners receive the beveled cut. Pass an empty array for square corners.
191
+ * @default undefined (renders as ['top-right'])
192
+ */
193
+ corners ?: SkNumberInputCorner[];
173
194
  }
174
195
 
175
196
  //------------------------------------------------------------------------------------------------------------------
176
197
 
177
198
  const props = withDefaults(defineProps<SkNumberInputComponentProps>(), {
178
199
  kind: undefined,
179
- size: 'md',
200
+ size: undefined,
180
201
  placeholder: undefined,
181
202
  disabled: false,
182
203
  readonly: false,
@@ -185,6 +206,8 @@
185
206
  min: undefined,
186
207
  max: undefined,
187
208
  step: 1,
209
+ showSteppers: true,
210
+ corners: undefined,
188
211
  });
189
212
 
190
213
  //------------------------------------------------------------------------------------------------------------------
@@ -198,19 +221,37 @@
198
221
 
199
222
  //------------------------------------------------------------------------------------------------------------------
200
223
 
201
- // Inject kind from parent SkField if available
202
- const fieldKind = inject<ComputedRef<SkNumberInputKind | undefined>>('field-kind', computed(() => undefined));
224
+ const validationKind = inject(validationKindKey, NO_KIND);
225
+ const inheritedKind = inject(inheritedKindKey, NO_KIND);
226
+ const inputGroupSize = inject(inputGroupSizeKey, NO_SIZE);
203
227
 
204
228
  //------------------------------------------------------------------------------------------------------------------
205
229
 
206
- const effectiveKind = computed(() => fieldKind.value || props.kind || 'neutral');
230
+ const effectiveKind = computed<SkNumberInputKind>(() =>
231
+ {
232
+ if(validationKind.value !== undefined) { return validationKind.value; }
233
+ if(props.kind !== undefined) { return props.kind; }
234
+ if(inheritedKind.value !== undefined) { return inheritedKind.value; }
235
+ return 'neutral';
236
+ });
237
+
238
+ const effectiveSize = computed<SkNumberInputSize>(() =>
239
+ {
240
+ if(props.size !== undefined) { return props.size; }
241
+ if(inputGroupSize.value !== undefined) { return inputGroupSize.value; }
242
+ return 'md';
243
+ });
207
244
 
208
245
  //------------------------------------------------------------------------------------------------------------------
209
246
 
210
247
  const wrapperClasses = computed(() => ({
211
248
  'sk-number-input-wrapper': true,
212
249
  [`sk-${ effectiveKind.value }`]: true,
213
- [`sk-${ props.size }`]: true,
250
+ [`sk-${ effectiveSize.value }`]: true,
251
+ 'sk-cut-top-left': props.corners?.includes('top-left') ?? false,
252
+ 'sk-cut-top-right': props.corners?.includes('top-right') ?? false,
253
+ 'sk-cut-bottom-right': props.corners?.includes('bottom-right') ?? false,
254
+ 'sk-cut-bottom-left': props.corners?.includes('bottom-left') ?? false,
214
255
  }));
215
256
 
216
257
  //------------------------------------------------------------------------------------------------------------------
@@ -3,8 +3,10 @@
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 SkNumberInputKind = ComponentKind;
8
9
  export type SkNumberInputSize = ComponentSize;
10
+ export type SkNumberInputCorner = SkCorner;
9
11
 
10
12
  //----------------------------------------------------------------------------------------------------------------------