@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.
Files changed (200) 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 +7 -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 +831 -277
  39. package/dist/sleekspace-ui.es.js +3693 -2514
  40. package/dist/sleekspace-ui.umd.js +3700 -2513
  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/llms-full.txt +14 -9
  85. package/package.json +6 -3
  86. package/src/components/Accordion/SkAccordion.vue +5 -2
  87. package/src/components/Accordion/SkAccordionItem.vue +7 -4
  88. package/src/components/Accordion/context.ts +23 -0
  89. package/src/components/Autocomplete/SkAutocomplete.test.ts +83 -0
  90. package/src/components/Autocomplete/SkAutocomplete.vue +305 -0
  91. package/src/components/Autocomplete/SkAutocompleteEmpty.vue +39 -0
  92. package/src/components/Autocomplete/SkAutocompleteGroup.vue +46 -0
  93. package/src/components/Autocomplete/SkAutocompleteGroupLabel.vue +39 -0
  94. package/src/components/Autocomplete/SkAutocompleteItem.vue +85 -0
  95. package/src/components/Autocomplete/SkAutocompleteSeparator.vue +39 -0
  96. package/src/components/Autocomplete/index.ts +13 -0
  97. package/src/components/Autocomplete/types.ts +10 -0
  98. package/src/components/Breadcrumbs/SkBreadcrumbItem.vue +8 -3
  99. package/src/components/Breadcrumbs/SkBreadcrumbSeparator.vue +8 -2
  100. package/src/components/Breadcrumbs/SkBreadcrumbs.vue +5 -2
  101. package/src/components/Breadcrumbs/context.ts +20 -0
  102. package/src/components/Button/SkButton.vue +46 -6
  103. package/src/components/Button/types.ts +6 -0
  104. package/src/components/ColorPicker/SkColorPicker.vue +27 -5
  105. package/src/components/ContextMenu/SkContextMenu.vue +4 -1
  106. package/src/components/ContextMenu/SkContextMenuSubmenu.vue +5 -2
  107. package/src/components/ContextMenu/context.ts +17 -0
  108. package/src/components/Dropdown/SkDropdown.vue +2 -1
  109. package/src/components/Dropdown/SkDropdownSubmenu.vue +4 -3
  110. package/src/components/Dropdown/context.ts +16 -0
  111. package/src/components/Field/SkField.test.ts +88 -0
  112. package/src/components/Field/SkField.vue +15 -7
  113. package/src/components/Input/SkInput.test.ts +61 -0
  114. package/src/components/Input/SkInput.vue +42 -7
  115. package/src/components/Input/types.ts +2 -0
  116. package/src/components/InputGroup/SkInputGroup.test.ts +171 -0
  117. package/src/components/InputGroup/SkInputGroup.vue +131 -0
  118. package/src/components/InputGroup/SkInputGroupAddon.test.ts +104 -0
  119. package/src/components/InputGroup/SkInputGroupAddon.vue +107 -0
  120. package/src/components/InputGroup/types.ts +27 -0
  121. package/src/components/Listbox/SkListbox.vue +27 -6
  122. package/src/components/NumberInput/SkNumberInput.vue +39 -7
  123. package/src/components/NumberInput/types.ts +2 -0
  124. package/src/components/Pagination/SkPagination.vue +6 -3
  125. package/src/components/Pagination/SkPaginationItem.vue +8 -5
  126. package/src/components/Pagination/context.ts +19 -0
  127. package/src/components/Panel/types.ts +3 -2
  128. package/src/components/Radio/SkRadio.vue +6 -3
  129. package/src/components/Radio/SkRadioGroup.vue +4 -2
  130. package/src/components/Radio/context.ts +17 -0
  131. package/src/components/Select/SkSelect.vue +39 -7
  132. package/src/components/Select/types.ts +2 -0
  133. package/src/components/Tabs/SkTab.vue +4 -2
  134. package/src/components/Tabs/SkTabList.vue +4 -2
  135. package/src/components/Tabs/SkTabs.vue +5 -3
  136. package/src/components/Tabs/context.ts +19 -0
  137. package/src/components/TagsInput/SkTagsInput.vue +28 -7
  138. package/src/components/Textarea/SkTextarea.vue +27 -6
  139. package/src/composables/injectionKeys.ts +52 -0
  140. package/src/index.ts +28 -0
  141. package/src/static/__tests__/parity.test.ts +2 -1
  142. package/src/static/__tests__/parityHarness.ts +5 -2
  143. package/src/static/components/__tests__/helpers.test.ts +191 -99
  144. package/src/static/components/alert.ts +12 -11
  145. package/src/static/components/avatar.ts +15 -16
  146. package/src/static/components/breadcrumbs.ts +3 -2
  147. package/src/static/components/button.ts +23 -27
  148. package/src/static/components/card.ts +3 -2
  149. package/src/static/components/checkbox.ts +11 -14
  150. package/src/static/components/colorPicker.ts +7 -9
  151. package/src/static/components/divider.ts +4 -3
  152. package/src/static/components/dropdown.ts +15 -6
  153. package/src/static/components/field.ts +32 -15
  154. package/src/static/components/group.ts +3 -2
  155. package/src/static/components/input.ts +20 -15
  156. package/src/static/components/inputGroup.ts +30 -0
  157. package/src/static/components/inputGroupAddon.ts +29 -0
  158. package/src/static/components/navBar.ts +30 -17
  159. package/src/static/components/numberInput.ts +17 -17
  160. package/src/static/components/page.ts +3 -2
  161. package/src/static/components/pagination.ts +3 -2
  162. package/src/static/components/panel.ts +3 -2
  163. package/src/static/components/progress.ts +3 -2
  164. package/src/static/components/radio.ts +14 -20
  165. package/src/static/components/select.ts +18 -15
  166. package/src/static/components/sidebar.ts +9 -13
  167. package/src/static/components/skeleton.ts +7 -10
  168. package/src/static/components/slider.ts +7 -9
  169. package/src/static/components/spinner.ts +22 -22
  170. package/src/static/components/switchInput.ts +12 -14
  171. package/src/static/components/table.ts +8 -10
  172. package/src/static/components/tag.ts +17 -11
  173. package/src/static/components/tagsInput.ts +3 -3
  174. package/src/static/components/textarea.ts +8 -13
  175. package/src/static/components/toolbar.ts +7 -10
  176. package/src/static/components/tooltip.ts +3 -2
  177. package/src/static/generated/defaults.ts +24 -9
  178. package/src/static/generated/propTypes.ts +18 -2
  179. package/src/static/h.ts +16 -0
  180. package/src/static/index.ts +8 -0
  181. package/src/static/render.test.ts +14 -10
  182. package/src/static/render.ts +33 -18
  183. package/src/static/specs.test.ts +1 -0
  184. package/src/static/specs.ts +22 -2
  185. package/src/static/stringH.ts +104 -0
  186. package/src/static/types.ts +25 -0
  187. package/src/styles/components/_autocomplete.scss +498 -0
  188. package/src/styles/components/_button.scss +55 -6
  189. package/src/styles/components/_index.scss +2 -0
  190. package/src/styles/components/_input-group.scss +292 -0
  191. package/src/styles/components/_input.scss +57 -9
  192. package/src/styles/components/_number-input.scss +84 -18
  193. package/src/styles/components/_select.scss +56 -9
  194. package/src/styles/mixins/_cut-border.scss +83 -0
  195. package/src/styles/tailwind.scss +262 -0
  196. package/src/styles/tokens.scss +8 -255
  197. package/src/types/corners.ts +10 -0
  198. package/src/utils/slots.test.ts +89 -0
  199. package/src/utils/slots.ts +6 -1
  200. package/web-types.json +382 -12
@@ -0,0 +1,39 @@
1
+ <!----------------------------------------------------------------------------------------------------------------------
2
+ - AutocompleteSeparator Component
3
+ --------------------------------------------------------------------------------------------------------------------->
4
+
5
+ <template>
6
+ <AutocompleteSeparator class="sk-autocomplete-separator" />
7
+ </template>
8
+
9
+ <!--------------------------------------------------------------------------------------------------------------------->
10
+
11
+ <style lang="scss" scoped>
12
+ // Component styles are in /src/styles/components/_autocomplete.scss
13
+ </style>
14
+
15
+ <!--------------------------------------------------------------------------------------------------------------------->
16
+
17
+ <script setup lang="ts">
18
+ /**
19
+ * @component SkAutocompleteSeparator
20
+ * @description A visual divider for organizing suggestions within an SkAutocomplete dropdown. Renders
21
+ * as a horizontal line between items with appropriate spacing.
22
+ *
23
+ * @example
24
+ * ```vue
25
+ * <SkAutocomplete v-model="query">
26
+ * <SkAutocompleteItem value="recent1">Recent search 1</SkAutocompleteItem>
27
+ * <SkAutocompleteItem value="recent2">Recent search 2</SkAutocompleteItem>
28
+ * <SkAutocompleteSeparator />
29
+ * <SkAutocompleteItem value="popular">Popular searches...</SkAutocompleteItem>
30
+ * </SkAutocomplete>
31
+ * ```
32
+ *
33
+ * @slot - Not applicable. This component does not accept slot content.
34
+ */
35
+
36
+ import { AutocompleteSeparator } from 'reka-ui';
37
+ </script>
38
+
39
+ <!--------------------------------------------------------------------------------------------------------------------->
@@ -0,0 +1,13 @@
1
+ //----------------------------------------------------------------------------------------------------------------------
2
+ // Autocomplete Component Exports
3
+ //----------------------------------------------------------------------------------------------------------------------
4
+
5
+ export { default as SkAutocomplete } from './SkAutocomplete.vue';
6
+ export { default as SkAutocompleteEmpty } from './SkAutocompleteEmpty.vue';
7
+ export { default as SkAutocompleteGroup } from './SkAutocompleteGroup.vue';
8
+ export { default as SkAutocompleteGroupLabel } from './SkAutocompleteGroupLabel.vue';
9
+ export { default as SkAutocompleteItem } from './SkAutocompleteItem.vue';
10
+ export { default as SkAutocompleteSeparator } from './SkAutocompleteSeparator.vue';
11
+ export type { SkAutocompleteKind, SkAutocompleteSize } from './types';
12
+
13
+ //----------------------------------------------------------------------------------------------------------------------
@@ -0,0 +1,10 @@
1
+ //----------------------------------------------------------------------------------------------------------------------
2
+ // Autocomplete Component Types
3
+ //----------------------------------------------------------------------------------------------------------------------
4
+
5
+ import type { ComponentKind, ComponentSize } from '@/types';
6
+
7
+ export type SkAutocompleteKind = ComponentKind;
8
+ export type SkAutocompleteSize = ComponentSize;
9
+
10
+ //----------------------------------------------------------------------------------------------------------------------
@@ -59,7 +59,10 @@
59
59
  import { RouterLink } from 'vue-router';
60
60
 
61
61
  // Types
62
- import type { SkBreadcrumbItemProps, SkBreadcrumbsKind } from './types';
62
+ import type { SkBreadcrumbItemProps } from './types';
63
+
64
+ // Context
65
+ import { BREADCRUMBS_KIND_KEY } from './context';
63
66
 
64
67
  //------------------------------------------------------------------------------------------------------------------
65
68
 
@@ -106,7 +109,9 @@
106
109
 
107
110
  //------------------------------------------------------------------------------------------------------------------
108
111
 
109
- const kind = inject<SkBreadcrumbsKind>('breadcrumbs-kind', 'neutral');
112
+ // BREADCRUMBS_KIND_KEY carries a Ref<SkBreadcrumbsKind | undefined>, so we read .value below.
113
+ // (Pre-typed-key code mistakenly treated this as a plain string and rendered `sk-[object Object]`.)
114
+ const kindRef = inject(BREADCRUMBS_KIND_KEY, computed(() => undefined));
110
115
 
111
116
  //------------------------------------------------------------------------------------------------------------------
112
117
 
@@ -129,7 +134,7 @@
129
134
 
130
135
  const linkClasses = computed(() => ({
131
136
  'sk-breadcrumb-link': true,
132
- [`sk-${ kind }`]: true,
137
+ [`sk-${ kindRef.value ?? 'neutral' }`]: true,
133
138
  }));
134
139
 
135
140
  //------------------------------------------------------------------------------------------------------------------
@@ -34,6 +34,9 @@
34
34
 
35
35
  import { computed, inject } from 'vue';
36
36
 
37
+ // Context
38
+ import { BREADCRUMBS_SEPARATOR_KEY } from './context';
39
+
37
40
  //------------------------------------------------------------------------------------------------------------------
38
41
 
39
42
  export interface SkBreadcrumbSeparatorComponentProps
@@ -55,11 +58,14 @@
55
58
 
56
59
  //------------------------------------------------------------------------------------------------------------------
57
60
 
58
- const parentSeparator = inject<string>('breadcrumbs-separator', '/');
61
+ // BREADCRUMBS_SEPARATOR_KEY carries a Ref<string | undefined>, so we read .value below.
62
+ // (Pre-typed-key code mistakenly treated this as a plain string and rendered `[object Object]`
63
+ // for the separator inside an SkBreadcrumbs.)
64
+ const parentSeparator = inject(BREADCRUMBS_SEPARATOR_KEY, computed(() => undefined));
59
65
 
60
66
  //------------------------------------------------------------------------------------------------------------------
61
67
 
62
- const displaySeparator = computed<string>(() => props.separator ?? parentSeparator);
68
+ const displaySeparator = computed<string>(() => props.separator ?? parentSeparator.value ?? '/');
63
69
 
64
70
  //------------------------------------------------------------------------------------------------------------------
65
71
  </script>
@@ -77,6 +77,9 @@
77
77
  // Composables
78
78
  import { useCustomColors } from '@/composables/useCustomColors';
79
79
 
80
+ // Context
81
+ import { BREADCRUMBS_KIND_KEY, BREADCRUMBS_SEPARATOR_KEY } from './context';
82
+
80
83
  //------------------------------------------------------------------------------------------------------------------
81
84
 
82
85
  const props = withDefaults(defineProps<SkBreadcrumbsComponentProps>(), {
@@ -92,8 +95,8 @@
92
95
 
93
96
  //------------------------------------------------------------------------------------------------------------------
94
97
 
95
- provide('breadcrumbs-kind', toRef(() => props.kind));
96
- provide('breadcrumbs-separator', toRef(() => props.separator));
98
+ provide(BREADCRUMBS_KIND_KEY, toRef(() => props.kind));
99
+ provide(BREADCRUMBS_SEPARATOR_KEY, toRef(() => props.separator));
97
100
 
98
101
  //------------------------------------------------------------------------------------------------------------------
99
102
 
@@ -0,0 +1,20 @@
1
+ //----------------------------------------------------------------------------------------------------------------------
2
+ // Breadcrumbs Context
3
+ //
4
+ // Injection keys for SkBreadcrumbItem and SkBreadcrumbSeparator descendants to inherit kind and
5
+ // separator from the surrounding SkBreadcrumbs without manual wiring.
6
+ //----------------------------------------------------------------------------------------------------------------------
7
+
8
+ import type { InjectionKey, Ref } from 'vue';
9
+
10
+ import type { SkBreadcrumbsKind } from './types';
11
+
12
+ //----------------------------------------------------------------------------------------------------------------------
13
+
14
+ export const BREADCRUMBS_KIND_KEY : InjectionKey<Ref<SkBreadcrumbsKind | undefined>>
15
+ = Symbol('sk-breadcrumbs-kind');
16
+
17
+ export const BREADCRUMBS_SEPARATOR_KEY : InjectionKey<Ref<string | undefined>>
18
+ = Symbol('sk-breadcrumbs-separator');
19
+
20
+ //----------------------------------------------------------------------------------------------------------------------
@@ -63,13 +63,20 @@
63
63
  * @slot icon - Icon for icon-only buttons. When used without the default slot, creates a square icon button.
64
64
  */
65
65
 
66
- import { type Slots, computed, toRef, useSlots } from 'vue';
66
+ import { type Slots, computed, inject, toRef, useSlots } from 'vue';
67
67
 
68
68
  // Types
69
69
  import type { ComponentCustomColors } from '@/types';
70
- import type { SkButtonKind, SkButtonSize, SkButtonType, SkButtonVariant } from './types';
70
+ import type { SkButtonCorner, SkButtonKind, SkButtonSize, SkButtonType, SkButtonVariant } from './types';
71
71
 
72
72
  // Composables
73
+ import {
74
+ NO_KIND,
75
+ NO_SIZE,
76
+ inheritedKindKey,
77
+ inputGroupSizeKey,
78
+ validationKindKey,
79
+ } from '@/composables/injectionKeys';
73
80
  import { useCustomColors } from '@/composables/useCustomColors';
74
81
 
75
82
  // Utils
@@ -143,6 +150,13 @@
143
150
  */
144
151
  dense ?: boolean;
145
152
 
153
+ /**
154
+ * Which corners receive the beveled cut. Pass an empty array for square corners.
155
+ * When omitted, defaults to the button's standalone visual (`top-left` + `bottom-right`).
156
+ * @default undefined (renders as ['top-left', 'bottom-right'])
157
+ */
158
+ corners ?: SkButtonCorner[];
159
+
146
160
  /**
147
161
  * URL for the button to navigate to. When provided, the button renders as an `<a>` element
148
162
  * instead of a `<button>`. Use for external links or simple navigation that doesn't require
@@ -164,13 +178,14 @@
164
178
 
165
179
  const props = withDefaults(defineProps<SkButtonComponentProps>(), {
166
180
  type: 'button',
167
- kind: 'neutral',
181
+ kind: undefined,
168
182
  variant: 'solid',
169
- size: 'md',
183
+ size: undefined,
170
184
  disabled: false,
171
185
  loading: false,
172
186
  pressed: false,
173
187
  dense: false,
188
+ corners: undefined,
174
189
  href: undefined,
175
190
  to: undefined,
176
191
  });
@@ -181,6 +196,27 @@
181
196
 
182
197
  //------------------------------------------------------------------------------------------------------------------
183
198
 
199
+ const validationKind = inject(validationKindKey, NO_KIND);
200
+ const inheritedKind = inject(inheritedKindKey, NO_KIND);
201
+ const inputGroupSize = inject(inputGroupSizeKey, NO_SIZE);
202
+
203
+ const effectiveKind = computed<SkButtonKind>(() =>
204
+ {
205
+ if(validationKind.value !== undefined) { return validationKind.value; }
206
+ if(props.kind !== undefined) { return props.kind; }
207
+ if(inheritedKind.value !== undefined) { return inheritedKind.value; }
208
+ return 'neutral';
209
+ });
210
+
211
+ const effectiveSize = computed<SkButtonSize>(() =>
212
+ {
213
+ if(props.size !== undefined) { return props.size; }
214
+ if(inputGroupSize.value !== undefined) { return inputGroupSize.value; }
215
+ return 'md';
216
+ });
217
+
218
+ //------------------------------------------------------------------------------------------------------------------
219
+
184
220
  // Determine which component to render
185
221
  const componentTag = computed<string>(() =>
186
222
  {
@@ -206,13 +242,17 @@
206
242
 
207
243
  return {
208
244
  'sk-button': true,
209
- [`sk-${ props.kind }`]: true,
245
+ [`sk-${ effectiveKind.value }`]: true,
210
246
  [`sk-${ props.variant }`]: true,
211
- [`sk-${ props.size }`]: true,
247
+ [`sk-${ effectiveSize.value }`]: true,
212
248
  'sk-loading': props.loading,
213
249
  'sk-pressed': props.pressed,
214
250
  'sk-dense': props.dense,
215
251
  'sk-icon-only': isIconOnly,
252
+ 'sk-cut-top-left': props.corners?.includes('top-left') ?? false,
253
+ 'sk-cut-top-right': props.corners?.includes('top-right') ?? false,
254
+ 'sk-cut-bottom-right': props.corners?.includes('bottom-right') ?? false,
255
+ 'sk-cut-bottom-left': props.corners?.includes('bottom-left') ?? false,
216
256
  };
217
257
  });
218
258
 
@@ -4,11 +4,17 @@
4
4
 
5
5
  import type { ComponentKind, ComponentSize, ComponentVariant } from '@/types';
6
6
 
7
+ // Types
8
+ import type { SkCorner } from '@/types/corners';
9
+
7
10
  //----------------------------------------------------------------------------------------------------------------------
8
11
 
9
12
  // HTML button type attribute
10
13
  export type SkButtonType = 'button' | 'submit' | 'reset';
11
14
 
15
+ // Button corner type (controls which corners receive the beveled cut)
16
+ export type SkButtonCorner = SkCorner;
17
+
12
18
  // Button semantic kinds (uses common types)
13
19
  export type SkButtonKind = ComponentKind;
14
20
 
@@ -226,7 +226,7 @@
226
226
  <!--------------------------------------------------------------------------------------------------------------------->
227
227
 
228
228
  <script setup lang="ts">
229
- import { type Component, type ComputedRef, computed, inject, ref, toRef } from 'vue';
229
+ import { type Component, computed, inject, ref, toRef } from 'vue';
230
230
  import {
231
231
  type ColorObject,
232
232
  ColorPickerCanvas,
@@ -251,6 +251,13 @@
251
251
  import type { SkColorPickerFormat, SkColorPickerKind, SkColorPickerSize } from './types';
252
252
 
253
253
  // Composables
254
+ import {
255
+ NO_KIND,
256
+ NO_SIZE,
257
+ inheritedKindKey,
258
+ inputGroupSizeKey,
259
+ validationKindKey,
260
+ } from '@/composables/injectionKeys';
254
261
  import { useCustomColors } from '@/composables/useCustomColors';
255
262
  import { usePortalContext } from '@/composables/usePortalContext';
256
263
 
@@ -283,7 +290,7 @@
283
290
 
284
291
  const props = withDefaults(defineProps<SkColorPickerComponentProps>(), {
285
292
  kind: undefined,
286
- size: 'md',
293
+ size: undefined,
287
294
  format: 'hexa',
288
295
  showAlpha: true,
289
296
  disabled: false,
@@ -299,19 +306,34 @@
299
306
 
300
307
  const { theme } = usePortalContext();
301
308
 
302
- const fieldKind = inject<ComputedRef<SkColorPickerKind | undefined>>('field-kind', computed(() => undefined));
309
+ const validationKind = inject(validationKindKey, NO_KIND);
310
+ const inheritedKind = inject(inheritedKindKey, NO_KIND);
311
+ const inputGroupSize = inject(inputGroupSizeKey, NO_SIZE);
303
312
  const displayFormat = ref<DisplayFormat>('Hex');
304
313
 
305
314
  //------------------------------------------------------------------------------------------------------------------
306
315
 
307
- const effectiveKind = computed(() => fieldKind.value || props.kind || 'neutral');
316
+ const effectiveKind = computed<SkColorPickerKind>(() =>
317
+ {
318
+ if(validationKind.value !== undefined) { return validationKind.value; }
319
+ if(props.kind !== undefined) { return props.kind; }
320
+ if(inheritedKind.value !== undefined) { return inheritedKind.value; }
321
+ return 'neutral';
322
+ });
323
+
324
+ const effectiveSize = computed<SkColorPickerSize>(() =>
325
+ {
326
+ if(props.size !== undefined) { return props.size; }
327
+ if(inputGroupSize.value !== undefined) { return inputGroupSize.value; }
328
+ return 'md';
329
+ });
308
330
 
309
331
  //------------------------------------------------------------------------------------------------------------------
310
332
 
311
333
  const wrapperClasses = computed(() => ({
312
334
  'sk-color-picker': true,
313
335
  [`sk-${ effectiveKind.value }`]: true,
314
- [`sk-${ props.size }`]: true,
336
+ [`sk-${ effectiveSize.value }`]: true,
315
337
  'sk-disabled': props.disabled,
316
338
  }));
317
339
 
@@ -45,6 +45,9 @@
45
45
  import { useCustomColors } from '@/composables/useCustomColors';
46
46
  import { usePortalContext } from '@/composables/usePortalContext';
47
47
 
48
+ // Context
49
+ import { CONTEXT_MENU_KIND_KEY } from './context';
50
+
48
51
  //------------------------------------------------------------------------------------------------------------------
49
52
 
50
53
  export interface SkContextMenuComponentProps extends ComponentCustomColors
@@ -62,7 +65,7 @@
62
65
 
63
66
  const { theme } = usePortalContext();
64
67
 
65
- provide('context-menu-kind', computed(() => props.kind));
68
+ provide(CONTEXT_MENU_KIND_KEY, computed(() => props.kind));
66
69
 
67
70
  //------------------------------------------------------------------------------------------------------------------
68
71
 
@@ -53,6 +53,9 @@
53
53
  // Composables
54
54
  import { usePortalContext } from '@/composables/usePortalContext';
55
55
 
56
+ // Context
57
+ import { CONTEXT_MENU_KIND_KEY } from './context';
58
+
56
59
  //------------------------------------------------------------------------------------------------------------------
57
60
 
58
61
  export interface SkContextMenuSubmenuComponentProps
@@ -71,11 +74,11 @@
71
74
 
72
75
  const { theme } = usePortalContext();
73
76
 
74
- const parentKind = inject<any>('context-menu-kind', computed(() => 'neutral'));
77
+ const parentKind = inject(CONTEXT_MENU_KIND_KEY, computed(() => undefined));
75
78
 
76
79
  //------------------------------------------------------------------------------------------------------------------
77
80
 
78
- const effectiveKind = computed(() => props.kind || parentKind.value || 'neutral');
81
+ const effectiveKind = computed<SkContextMenuKind>(() => props.kind ?? parentKind.value ?? 'neutral');
79
82
 
80
83
  //------------------------------------------------------------------------------------------------------------------
81
84
 
@@ -0,0 +1,17 @@
1
+ //----------------------------------------------------------------------------------------------------------------------
2
+ // ContextMenu Context
3
+ //
4
+ // Injection key for nested SkContextMenuSubmenu descendants to inherit the surrounding
5
+ // SkContextMenu's kind without manual wiring.
6
+ //----------------------------------------------------------------------------------------------------------------------
7
+
8
+ import type { ComputedRef, InjectionKey } from 'vue';
9
+
10
+ import type { SkContextMenuKind } from './types';
11
+
12
+ //----------------------------------------------------------------------------------------------------------------------
13
+
14
+ export const CONTEXT_MENU_KIND_KEY : InjectionKey<ComputedRef<SkContextMenuKind | undefined>>
15
+ = Symbol('sk-context-menu-kind');
16
+
17
+ //----------------------------------------------------------------------------------------------------------------------
@@ -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('dropdown-kind', computed(() => props.kind));
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
- // Inject parent dropdown kind (will be a ComputedRef)
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 || parentKind.value || 'neutral');
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 ?: string;
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 ?: string;
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
- const effectiveKind = computed(() =>
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; // Let the input use its own kind prop
209
+ return undefined;
204
210
  });
205
211
 
206
212
  //------------------------------------------------------------------------------------------------------------------
207
213
 
208
- // Provide kind to child inputs so they can automatically pick it up
209
- provide('field-kind', effectiveKind);
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