@skewedaspect/sleekspace-ui 0.8.1 → 0.9.1

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 (191) hide show
  1. package/dist/components/Dropdown/SkDropdown.vue.d.ts +9 -1
  2. package/dist/components/Dropdown/types.d.ts +2 -1
  3. package/dist/components/NavBar/SkNavBar.vue.d.ts +9 -1
  4. package/dist/components/NavBar/context.d.ts +2 -0
  5. package/dist/components/NavBar/types.d.ts +5 -1
  6. package/dist/components/NumberInput/SkNumberInput.vue.d.ts +8 -0
  7. package/dist/components/Page/SkPage.vue.d.ts +9 -0
  8. package/dist/components/ScrollArea/SkScrollArea.vue.d.ts +105 -4
  9. package/dist/composables/useCustomColors.d.ts +18 -56
  10. package/{src → dist}/global.d.ts +6 -2
  11. package/dist/sleekspace-ui.css +4257 -1253
  12. package/dist/sleekspace-ui.es.js +300 -170
  13. package/dist/sleekspace-ui.umd.js +299 -169
  14. package/dist/static/classes.d.ts +18 -0
  15. package/dist/static/components/alert.d.ts +12 -0
  16. package/dist/static/components/avatar.d.ts +9 -0
  17. package/dist/static/components/breadcrumbs.d.ts +6 -0
  18. package/dist/static/components/button.d.ts +13 -0
  19. package/dist/static/components/card.d.ts +5 -0
  20. package/dist/static/components/checkbox.d.ts +10 -0
  21. package/dist/static/components/colorPicker.d.ts +8 -0
  22. package/dist/static/components/divider.d.ts +8 -0
  23. package/dist/static/components/dropdown.d.ts +8 -0
  24. package/dist/static/components/field.d.ts +15 -0
  25. package/dist/static/components/group.d.ts +5 -0
  26. package/dist/static/components/input.d.ts +14 -0
  27. package/dist/static/components/navBar.d.ts +16 -0
  28. package/dist/static/components/numberInput.d.ts +15 -0
  29. package/dist/static/components/page.d.ts +9 -0
  30. package/dist/static/components/pagination.d.ts +5 -0
  31. package/dist/static/components/panel.d.ts +11 -0
  32. package/dist/static/components/progress.d.ts +9 -0
  33. package/dist/static/components/radio.d.ts +11 -0
  34. package/dist/static/components/select.d.ts +10 -0
  35. package/dist/static/components/sidebar.d.ts +9 -0
  36. package/dist/static/components/skeleton.d.ts +11 -0
  37. package/dist/static/components/slider.d.ts +12 -0
  38. package/dist/static/components/spinner.d.ts +12 -0
  39. package/dist/static/components/switchInput.d.ts +10 -0
  40. package/dist/static/components/table.d.ts +12 -0
  41. package/dist/static/components/tag.d.ts +8 -0
  42. package/dist/static/components/tagsInput.d.ts +7 -0
  43. package/dist/static/components/textarea.d.ts +12 -0
  44. package/dist/static/components/toolbar.d.ts +12 -0
  45. package/dist/static/components/tooltip.d.ts +7 -0
  46. package/dist/static/escape.d.ts +2 -0
  47. package/dist/static/index.cjs.js +1 -0
  48. package/dist/static/index.d.ts +68 -0
  49. package/dist/static/index.es.js +732 -0
  50. package/dist/static/render.d.ts +12 -0
  51. package/dist/static/specs.d.ts +2 -0
  52. package/dist/static/types.d.ts +43 -0
  53. package/dist/tokens.css +322 -0
  54. package/dist/types/index.d.ts +36 -0
  55. package/dist/utils/slots.d.ts +6 -0
  56. package/docs/guides/installation.md +8 -2
  57. package/docs/guides/pure-css/_meta.yaml +8 -0
  58. package/docs/guides/pure-css/class-api.md +1070 -0
  59. package/docs/guides/pure-css/custom-elements.md +574 -0
  60. package/docs/guides/pure-css/index.md +86 -0
  61. package/docs/guides/pure-css/limitations.md +152 -0
  62. package/docs/guides/pure-css/static-helpers.md +1203 -0
  63. package/llms-full.txt +3739 -261
  64. package/package.json +19 -5
  65. package/src/components/Alert/SkAlert.vue +4 -2
  66. package/src/components/Breadcrumbs/SkBreadcrumbs.vue +6 -12
  67. package/src/components/Button/SkButton.vue +8 -5
  68. package/src/components/Card/SkCard.vue +13 -5
  69. package/src/components/Checkbox/SkCheckbox.vue +9 -2
  70. package/src/components/ContextMenu/SkContextMenuRadioGroup.vue +4 -1
  71. package/src/components/Dropdown/SkDropdown.vue +20 -3
  72. package/src/components/Dropdown/SkDropdownRadioGroup.vue +4 -1
  73. package/src/components/Dropdown/types.ts +2 -1
  74. package/src/components/Modal/SkModal.vue +11 -4
  75. package/src/components/NavBar/SkNavBar.vue +19 -8
  76. package/src/components/NavBar/context.ts +4 -2
  77. package/src/components/NavBar/types.ts +6 -1
  78. package/src/components/NumberInput/SkNumberInput.vue +10 -1
  79. package/src/components/Page/SkPage.vue +29 -15
  80. package/src/components/Panel/SkPanel.vue +2 -1
  81. package/src/components/Popover/SkPopover.vue +11 -4
  82. package/src/components/Radio/SkRadio.vue +9 -2
  83. package/src/components/ScrollArea/SkScrollArea.vue +78 -5
  84. package/src/components/Switch/SkSwitch.vue +14 -13
  85. package/src/components/Tabs/SkTab.vue +7 -2
  86. package/src/components/TreeView/SkTreeItem.vue +10 -2
  87. package/src/components/TreeView/SkTreeView.vue +7 -2
  88. package/src/composables/useCustomColors.ts +86 -77
  89. package/src/composables/usePortalContext.test.ts +0 -2
  90. package/src/shims.d.ts +10 -0
  91. package/src/static/__tests__/parity.test.ts +717 -0
  92. package/src/static/__tests__/parityHarness.test.ts +98 -0
  93. package/src/static/__tests__/parityHarness.ts +260 -0
  94. package/src/static/classes.test.ts +82 -0
  95. package/src/static/classes.ts +111 -0
  96. package/src/static/components/__tests__/helpers.test.ts +837 -0
  97. package/src/static/components/alert.ts +117 -0
  98. package/src/static/components/avatar.ts +86 -0
  99. package/src/static/components/breadcrumbs.ts +28 -0
  100. package/src/static/components/button.ts +75 -0
  101. package/src/static/components/card.ts +27 -0
  102. package/src/static/components/checkbox.ts +48 -0
  103. package/src/static/components/colorPicker.ts +45 -0
  104. package/src/static/components/divider.ts +39 -0
  105. package/src/static/components/dropdown.ts +36 -0
  106. package/src/static/components/field.ts +86 -0
  107. package/src/static/components/group.ts +27 -0
  108. package/src/static/components/input.ts +55 -0
  109. package/src/static/components/navBar.ts +94 -0
  110. package/src/static/components/numberInput.ts +64 -0
  111. package/src/static/components/page.ts +31 -0
  112. package/src/static/components/pagination.ts +27 -0
  113. package/src/static/components/panel.ts +33 -0
  114. package/src/static/components/progress.ts +31 -0
  115. package/src/static/components/radio.ts +53 -0
  116. package/src/static/components/select.ts +51 -0
  117. package/src/static/components/sidebar.ts +85 -0
  118. package/src/static/components/skeleton.ts +66 -0
  119. package/src/static/components/slider.ts +50 -0
  120. package/src/static/components/spinner.ts +94 -0
  121. package/src/static/components/switchInput.ts +49 -0
  122. package/src/static/components/table.ts +88 -0
  123. package/src/static/components/tag.ts +76 -0
  124. package/src/static/components/tagsInput.ts +35 -0
  125. package/src/static/components/textarea.ts +53 -0
  126. package/src/static/components/toolbar.ts +74 -0
  127. package/src/static/components/tooltip.ts +29 -0
  128. package/src/static/escape.test.ts +53 -0
  129. package/src/static/escape.ts +28 -0
  130. package/src/static/generated/defaults.ts +379 -0
  131. package/src/static/generated/propTypes.ts +426 -0
  132. package/src/static/index.ts +116 -0
  133. package/src/static/render.test.ts +83 -0
  134. package/src/static/render.ts +76 -0
  135. package/src/static/specs.test.ts +58 -0
  136. package/src/static/specs.ts +230 -0
  137. package/src/static/types.ts +176 -0
  138. package/src/styles/__tests__/testHelpers.ts +97 -0
  139. package/src/styles/base/_custom-elements.scss +51 -0
  140. package/src/styles/base/_index.scss +4 -0
  141. package/src/styles/components/__tests__/componentSelectors.test.ts +2575 -0
  142. package/src/styles/components/_alert.scss +82 -39
  143. package/src/styles/components/_avatar.scss +102 -47
  144. package/src/styles/components/_breadcrumbs.scss +39 -37
  145. package/src/styles/components/_button.scss +58 -5
  146. package/src/styles/components/_card.scss +64 -2
  147. package/src/styles/components/_checkbox.scss +35 -5
  148. package/src/styles/components/_color-picker.scss +48 -13
  149. package/src/styles/components/_divider.scss +86 -52
  150. package/src/styles/components/_dropdown.scss +214 -0
  151. package/src/styles/components/_field.scss +76 -23
  152. package/src/styles/components/_group.scss +190 -79
  153. package/src/styles/components/_index.scss +1 -0
  154. package/src/styles/components/_input.scss +81 -5
  155. package/src/styles/components/_menu.scss +1 -1
  156. package/src/styles/components/_navbar.scss +76 -45
  157. package/src/styles/components/_number-input.scss +98 -85
  158. package/src/styles/components/_page.scss +82 -23
  159. package/src/styles/components/_pagination.scss +240 -212
  160. package/src/styles/components/_panel.scss +268 -122
  161. package/src/styles/components/_progress.scss +120 -70
  162. package/src/styles/components/_radio.scss +35 -5
  163. package/src/styles/components/_scroll-area.scss +50 -22
  164. package/src/styles/components/_select.scss +40 -9
  165. package/src/styles/components/_sidebar.scss +59 -34
  166. package/src/styles/components/_skeleton.scss +111 -65
  167. package/src/styles/components/_slider.scss +34 -10
  168. package/src/styles/components/_spinner.scss +107 -56
  169. package/src/styles/components/_switch.scss +36 -5
  170. package/src/styles/components/_table.scss +150 -166
  171. package/src/styles/components/_tag.scss +244 -154
  172. package/src/styles/components/_tags-input.scss +46 -12
  173. package/src/styles/components/_textarea.scss +36 -5
  174. package/src/styles/components/_toolbar.scss +85 -31
  175. package/src/styles/components/_tooltip.scss +172 -3
  176. package/src/styles/mixins/_cut-border.scss +18 -4
  177. package/src/styles/mixins/_dual-selector.scss +192 -0
  178. package/src/styles/mixins/_index.scss +1 -0
  179. package/src/styles/mixins/dualSelector.test.ts +151 -0
  180. package/src/styles/themes/_colorful.scss +25 -0
  181. package/src/styles/themes/_greyscale.scss +25 -0
  182. package/src/styles/themes/_shade-scale.scss +39 -0
  183. package/src/styles/tokens/_semantic-color-kinds.scss +66 -0
  184. package/src/{types.ts → types/index.ts} +19 -11
  185. package/src/utils/slots.ts +75 -0
  186. package/web-types.json +980 -137
  187. package/dist/composables/useCustomColors.test.d.ts +0 -1
  188. package/dist/composables/useFocusTrap.test.d.ts +0 -1
  189. package/dist/composables/usePortalContext.test.d.ts +0 -1
  190. package/dist/styles/mixins/fluidSize.test.d.ts +0 -1
  191. package/dist/types.d.ts +0 -29
@@ -0,0 +1,717 @@
1
+ //----------------------------------------------------------------------------------------------------------------------
2
+ // Vue <-> Static Parity Tests
3
+ //
4
+ // For each in-scope component, mount the Vue component with specific props, invoke the matching
5
+ // static helper with the same props, and verify the emitted DOM trees match. Fails loudly when
6
+ // Vue and static drift apart.
7
+ //
8
+ // Props must be passed explicitly (including defaults) so both the Vue component and the static
9
+ // helper see the same values — Vue applies withDefaults() internally, but the static helper only
10
+ // adds modifier classes for props that are explicitly provided.
11
+ //----------------------------------------------------------------------------------------------------------------------
12
+
13
+ import { describe, expect, it } from 'vitest';
14
+
15
+ import SkPanel from '../../components/Panel/SkPanel.vue';
16
+ import SkAlert from '../../components/Alert/SkAlert.vue';
17
+ import SkNavBar from '../../components/NavBar/SkNavBar.vue';
18
+ import SkToolbar from '../../components/Toolbar/SkToolbar.vue';
19
+ import SkSidebar from '../../components/Sidebar/SkSidebar.vue';
20
+ import SkDivider from '../../components/Divider/SkDivider.vue';
21
+ import SkSkeleton from '../../components/Skeleton/SkSkeleton.vue';
22
+ import SkSpinner from '../../components/Spinner/SkSpinner.vue';
23
+ import SkTag from '../../components/Tag/SkTag.vue';
24
+ import SkAvatar from '../../components/Avatar/SkAvatar.vue';
25
+ import SkField from '../../components/Field/SkField.vue';
26
+ import SkTable from '../../components/Table/SkTable.vue';
27
+
28
+ import { panel } from '../components/panel';
29
+ import { skeleton } from '../components/skeleton';
30
+ import { alert } from '../components/alert';
31
+ import { navBar } from '../components/navBar';
32
+ import { toolbar } from '../components/toolbar';
33
+ import { sidebar } from '../components/sidebar';
34
+ import { divider } from '../components/divider';
35
+ import { spinner } from '../components/spinner';
36
+ import { tag } from '../components/tag';
37
+ import { avatar } from '../components/avatar';
38
+ import { field } from '../components/field';
39
+ import { table } from '../components/table';
40
+
41
+ import { compareParity } from './parityHarness';
42
+
43
+ //----------------------------------------------------------------------------------------------------------------------
44
+
45
+ type StaticFn = (p : Record<string, unknown>, c ?: string) => string;
46
+
47
+ function expectParity(result : ReturnType<typeof compareParity>) : void
48
+ {
49
+ if(!result.match)
50
+ {
51
+ throw new Error(`Parity failed:\n${ result.diff }`);
52
+ }
53
+ expect(result.match).toBe(true);
54
+ }
55
+
56
+ // Document an intentional Vue/static non-parity. Renders as a skipped test so the
57
+ // reason shows up in test output without ever executing.
58
+ function skipNonParity(reason : string) : void
59
+ {
60
+ it.skip(`parity not asserted — ${ reason }`, () =>
61
+ {
62
+ // intentionally not run; description above documents the divergence
63
+ });
64
+ }
65
+
66
+ //----------------------------------------------------------------------------------------------------------------------
67
+
68
+ describe('parity', () =>
69
+ {
70
+ //------------------------------------------------------------------------------------------------------------------
71
+ // Panel
72
+ //
73
+ // Simple <div> container that directly matches the static helper. Props must be spelled out
74
+ // explicitly (including withDefaults values) so both sides emit the same modifier classes.
75
+ //------------------------------------------------------------------------------------------------------------------
76
+
77
+ describe('panel', () =>
78
+ {
79
+ it('default props', () =>
80
+ {
81
+ expectParity(compareParity({
82
+ vueComponent: SkPanel,
83
+ staticHelper: panel as StaticFn,
84
+ // Spell out Panel's withDefaults values so static receives the same prop values.
85
+ props: { kind: 'neutral', size: 'md', corners: [ 'bottom-right' ], decorationCorner: 'bottom-right' },
86
+ children: 'content',
87
+ }));
88
+ });
89
+
90
+ it('kind variant', () =>
91
+ {
92
+ expectParity(compareParity({
93
+ vueComponent: SkPanel,
94
+ staticHelper: panel as StaticFn,
95
+ props: { kind: 'primary', size: 'md', corners: [ 'bottom-right' ], decorationCorner: 'bottom-right' },
96
+ children: 'x',
97
+ }));
98
+ });
99
+
100
+ it('size variant', () =>
101
+ {
102
+ expectParity(compareParity({
103
+ vueComponent: SkPanel,
104
+ staticHelper: panel as StaticFn,
105
+ props: { kind: 'neutral', size: 'lg', corners: [ 'bottom-right' ], decorationCorner: 'bottom-right' },
106
+ children: 'x',
107
+ }));
108
+ });
109
+
110
+ it('corners + decoration combo', () =>
111
+ {
112
+ expectParity(compareParity({
113
+ vueComponent: SkPanel,
114
+ staticHelper: panel as StaticFn,
115
+ props: {
116
+ kind: 'accent',
117
+ size: 'sm',
118
+ corners: [ 'top-left', 'bottom-right' ],
119
+ decorationCorner: 'top-left',
120
+ },
121
+ children: 'x',
122
+ }));
123
+ });
124
+
125
+ it('noBorder flag', () =>
126
+ {
127
+ expectParity(compareParity({
128
+ vueComponent: SkPanel,
129
+ staticHelper: panel as StaticFn,
130
+ props: {
131
+ kind: 'neutral',
132
+ size: 'md',
133
+ noBorder: true,
134
+ corners: [ 'bottom-right' ],
135
+ decorationCorner: 'bottom-right',
136
+ },
137
+ children: 'x',
138
+ }));
139
+ });
140
+ });
141
+
142
+ //------------------------------------------------------------------------------------------------------------------
143
+ // Card
144
+ //
145
+ // SKIP: SkCard wraps SkPanel — Vue emits a nested SkPanel structure with sk-card added to its
146
+ // class, while the static helper emits a plain <div class="sk-card">. These are structurally
147
+ // divergent by design (static layer intentionally flattens the panel hierarchy).
148
+ //------------------------------------------------------------------------------------------------------------------
149
+
150
+ describe('card', () =>
151
+ {
152
+ skipNonParity('SkCard wraps SkPanel; static emits a flat <div class="sk-card"> with no inner panel structure');
153
+ });
154
+
155
+ //------------------------------------------------------------------------------------------------------------------
156
+ // Alert
157
+ //
158
+ // The static helper now emits the full alert structure: kind-appropriate icon div (for
159
+ // feedback kinds) + sk-alert-content wrapper, matching what SkAlert.vue renders.
160
+ //------------------------------------------------------------------------------------------------------------------
161
+
162
+ describe('alert', () =>
163
+ {
164
+ it('info kind (has icon)', () =>
165
+ {
166
+ expectParity(compareParity({
167
+ vueComponent: SkAlert,
168
+ staticHelper: alert as StaticFn,
169
+ props: { kind: 'info', subtle: false },
170
+ children: 'Check this out.',
171
+ }));
172
+ });
173
+
174
+ it('neutral kind (no icon)', () =>
175
+ {
176
+ expectParity(compareParity({
177
+ vueComponent: SkAlert,
178
+ staticHelper: alert as StaticFn,
179
+ props: { kind: 'neutral', subtle: false },
180
+ children: 'Neutral message.',
181
+ }));
182
+ });
183
+
184
+ it('warning kind (has icon)', () =>
185
+ {
186
+ expectParity(compareParity({
187
+ vueComponent: SkAlert,
188
+ staticHelper: alert as StaticFn,
189
+ props: { kind: 'warning', subtle: false },
190
+ children: 'Watch out.',
191
+ }));
192
+ });
193
+ });
194
+
195
+ //------------------------------------------------------------------------------------------------------------------
196
+ // Divider
197
+ //
198
+ // The static helper now emits <hr> with the same class structure as SkDivider.vue:
199
+ // sk-divider, sk-<orientation>, sk-<kind>, sk-<size>. The aria-orientation attribute is
200
+ // filtered by the parity harness (it's in REKA_UI_INJECTED_ATTRS) so only the tag name
201
+ // and class list are compared.
202
+ //------------------------------------------------------------------------------------------------------------------
203
+
204
+ describe('divider', () =>
205
+ {
206
+ it('horizontal (default)', () =>
207
+ {
208
+ expectParity(compareParity({
209
+ vueComponent: SkDivider,
210
+ staticHelper: divider as StaticFn,
211
+ props: { orientation: 'horizontal', kind: 'neutral', size: 'md' },
212
+ }));
213
+ });
214
+
215
+ it('vertical orientation', () =>
216
+ {
217
+ expectParity(compareParity({
218
+ vueComponent: SkDivider,
219
+ staticHelper: divider as StaticFn,
220
+ props: { orientation: 'vertical', kind: 'neutral', size: 'md' },
221
+ }));
222
+ });
223
+
224
+ it('primary kind', () =>
225
+ {
226
+ expectParity(compareParity({
227
+ vueComponent: SkDivider,
228
+ staticHelper: divider as StaticFn,
229
+ props: { orientation: 'horizontal', kind: 'primary', size: 'md' },
230
+ }));
231
+ });
232
+ });
233
+
234
+ //------------------------------------------------------------------------------------------------------------------
235
+ // Page
236
+ //
237
+ // SKIP: SkPage is a complex multi-slot layout shell with sidebars, drawers, headers, footers,
238
+ // and responsive behavior. The static helper emits a plain <div class="sk-page"> — there is no
239
+ // practical parity between the two at this level.
240
+ //------------------------------------------------------------------------------------------------------------------
241
+
242
+ describe('page', () =>
243
+ {
244
+ skipNonParity('SkPage is a full layout shell; static emits a bare container');
245
+ });
246
+
247
+ //------------------------------------------------------------------------------------------------------------------
248
+ // Group
249
+ //
250
+ // SKIP: SkGroup emits `sk-${orientation}` (e.g. sk-horizontal) via a template literal, while
251
+ // the static spec correctly targets the CSS class family `sk-orientation-horizontal` used by
252
+ // the SCSS single-choice-modifier mixin. These diverge at the class name level. The static
253
+ // spec matches the CSS — Vue is applying the wrong class name (it works only because
254
+ // defaults-when-absent catches the unclassed case). Do not modify Vue; document this drift.
255
+ //------------------------------------------------------------------------------------------------------------------
256
+
257
+ describe('group', () =>
258
+ {
259
+ skipNonParity('Vue emits sk-horizontal; static emits sk-orientation-horizontal (matches CSS selector)');
260
+ });
261
+
262
+ //------------------------------------------------------------------------------------------------------------------
263
+ // Skeleton
264
+ //
265
+ // The static helper now emits the same class names as Vue: sk-<variant> (e.g. sk-text) and
266
+ // sk-<animation> (e.g. sk-shimmer) when animation is not 'none'. Width/height are forwarded
267
+ // as inline style so the style attribute also matches. Pass all props explicitly (including
268
+ // Vue defaults) to ensure both sides emit the same output.
269
+ //------------------------------------------------------------------------------------------------------------------
270
+
271
+ describe('skeleton', () =>
272
+ {
273
+ it('text variant, shimmer animation (Vue defaults)', () =>
274
+ {
275
+ expectParity(compareParity({
276
+ vueComponent: SkSkeleton,
277
+ staticHelper: skeleton as StaticFn,
278
+ // Vue defaults: variant='text', animation='shimmer', width='100%'
279
+ props: { variant: 'text', animation: 'shimmer', width: '100%' },
280
+ }));
281
+ });
282
+
283
+ it('circular variant', () =>
284
+ {
285
+ expectParity(compareParity({
286
+ vueComponent: SkSkeleton,
287
+ staticHelper: skeleton as StaticFn,
288
+ props: { variant: 'circular', animation: 'shimmer', width: '100%' },
289
+ }));
290
+ });
291
+
292
+ it('pulse animation', () =>
293
+ {
294
+ expectParity(compareParity({
295
+ vueComponent: SkSkeleton,
296
+ staticHelper: skeleton as StaticFn,
297
+ props: { variant: 'text', animation: 'pulse', width: '100%' },
298
+ }));
299
+ });
300
+ });
301
+
302
+ //------------------------------------------------------------------------------------------------------------------
303
+ // Progress
304
+ //
305
+ // SKIP: SkProgress uses reka-ui ProgressRoot which renders as a role="progressbar" div
306
+ // with aria-valuenow/aria-valuemin/aria-valuemax. The static helper emits a void <progress />
307
+ // element — different tag, different ARIA model.
308
+ //------------------------------------------------------------------------------------------------------------------
309
+
310
+ describe('progress', () =>
311
+ {
312
+ skipNonParity('Vue uses reka-ui ProgressRoot with aria-value* attrs; static emits <progress />');
313
+ });
314
+
315
+ //------------------------------------------------------------------------------------------------------------------
316
+ // Spinner
317
+ //
318
+ // The static helper now emits the full inner DOM structure for each variant, matching
319
+ // SkSpinner.vue's v-if/v-else-if template blocks. ARIA attributes (role, aria-live,
320
+ // aria-label) are emitted on the root element. Pass all props explicitly including defaults.
321
+ //------------------------------------------------------------------------------------------------------------------
322
+
323
+ describe('spinner', () =>
324
+ {
325
+ it('circular variant (default)', () =>
326
+ {
327
+ expectParity(compareParity({
328
+ vueComponent: SkSpinner,
329
+ staticHelper: spinner as StaticFn,
330
+ props: { kind: 'primary', size: 'md', variant: 'circular' },
331
+ }));
332
+ });
333
+
334
+ it('dots variant', () =>
335
+ {
336
+ expectParity(compareParity({
337
+ vueComponent: SkSpinner,
338
+ staticHelper: spinner as StaticFn,
339
+ props: { kind: 'primary', size: 'md', variant: 'dots' },
340
+ }));
341
+ });
342
+
343
+ it('crosshair variant', () =>
344
+ {
345
+ expectParity(compareParity({
346
+ vueComponent: SkSpinner,
347
+ staticHelper: spinner as StaticFn,
348
+ props: { kind: 'primary', size: 'md', variant: 'crosshair' },
349
+ }));
350
+ });
351
+ });
352
+
353
+ //------------------------------------------------------------------------------------------------------------------
354
+ // NavBar
355
+ //
356
+ // The static helper now emits the sk-navbar-content wrapper and conditional slot divs,
357
+ // matching SkNavBar's rendered output. Sticky defaults to true on both sides.
358
+ //------------------------------------------------------------------------------------------------------------------
359
+
360
+ describe('navBar', () =>
361
+ {
362
+ it('default kind with nav children', () =>
363
+ {
364
+ expectParity(compareParity({
365
+ vueComponent: SkNavBar,
366
+ staticHelper: navBar as StaticFn,
367
+ props: { kind: 'neutral', sticky: true },
368
+ children: 'Nav Links',
369
+ }));
370
+ });
371
+
372
+ it('primary kind, sticky', () =>
373
+ {
374
+ expectParity(compareParity({
375
+ vueComponent: SkNavBar,
376
+ staticHelper: navBar as StaticFn,
377
+ props: { kind: 'primary', sticky: true },
378
+ children: 'x',
379
+ }));
380
+ });
381
+
382
+ it('accent kind, non-sticky', () =>
383
+ {
384
+ expectParity(compareParity({
385
+ vueComponent: SkNavBar,
386
+ staticHelper: navBar as StaticFn,
387
+ props: { kind: 'accent', sticky: false },
388
+ children: 'Link A',
389
+ }));
390
+ });
391
+ });
392
+
393
+ //------------------------------------------------------------------------------------------------------------------
394
+ // Toolbar
395
+ //
396
+ // The static helper now emits the full class structure matching SkToolbar (sk-toolbar,
397
+ // sk-<kind>, sk-<orientation>, sk-cut-* for all corners). The parity harness filters
398
+ // reka-ui runtime attributes (aria-orientation, data-orientation, dir, tabindex, and the
399
+ // outline-reset inline style) since these are injected by ToolbarRoot and have no static
400
+ // equivalent. Only the class structure is compared — which is what the CSS consumes.
401
+ //------------------------------------------------------------------------------------------------------------------
402
+
403
+ describe('toolbar', () =>
404
+ {
405
+ it('default props (all four corners, horizontal)', () =>
406
+ {
407
+ expectParity(compareParity({
408
+ vueComponent: SkToolbar,
409
+ staticHelper: toolbar as StaticFn,
410
+ props: {
411
+ kind: 'neutral',
412
+ orientation: 'horizontal',
413
+ corners: [ 'top-left', 'top-right', 'bottom-right', 'bottom-left' ],
414
+ },
415
+ children: 'content',
416
+ }));
417
+ });
418
+
419
+ it('primary kind, vertical', () =>
420
+ {
421
+ expectParity(compareParity({
422
+ vueComponent: SkToolbar,
423
+ staticHelper: toolbar as StaticFn,
424
+ props: {
425
+ kind: 'primary',
426
+ orientation: 'vertical',
427
+ corners: [ 'top-left', 'top-right', 'bottom-right', 'bottom-left' ],
428
+ },
429
+ children: 'x',
430
+ }));
431
+ });
432
+ });
433
+
434
+ //------------------------------------------------------------------------------------------------------------------
435
+ // Sidebar
436
+ //
437
+ // The static helper now emits the full nested structure: aside → sk-panel (sk-sidebar-panel)
438
+ // → sk-panel-scroll-content → sk-sidebar-nav. The panel corner/decoration corner is computed
439
+ // from the `side` prop, matching SkSidebar's computed panelCorners/panelDecorationCorner.
440
+ //------------------------------------------------------------------------------------------------------------------
441
+
442
+ describe('sidebar', () =>
443
+ {
444
+ it('left side (default)', () =>
445
+ {
446
+ expectParity(compareParity({
447
+ vueComponent: SkSidebar,
448
+ staticHelper: sidebar as StaticFn,
449
+ props: { kind: 'neutral', side: 'left', dense: false },
450
+ children: 'nav-content',
451
+ }));
452
+ });
453
+
454
+ it('right side', () =>
455
+ {
456
+ expectParity(compareParity({
457
+ vueComponent: SkSidebar,
458
+ staticHelper: sidebar as StaticFn,
459
+ props: { kind: 'neutral', side: 'right', dense: false },
460
+ children: 'x',
461
+ }));
462
+ });
463
+
464
+ it('primary kind', () =>
465
+ {
466
+ expectParity(compareParity({
467
+ vueComponent: SkSidebar,
468
+ staticHelper: sidebar as StaticFn,
469
+ props: { kind: 'primary', side: 'left', dense: false },
470
+ children: 'x',
471
+ }));
472
+ });
473
+ });
474
+
475
+ //------------------------------------------------------------------------------------------------------------------
476
+ // Breadcrumbs
477
+ //
478
+ // SKIP: SkBreadcrumbs wraps slot content in <ol class="sk-breadcrumbs-list"> and inserts
479
+ // SkBreadcrumbSeparator components between items. The static helper emits a bare <nav>
480
+ // with no inner list structure.
481
+ //------------------------------------------------------------------------------------------------------------------
482
+
483
+ describe('breadcrumbs', () =>
484
+ {
485
+ skipNonParity('Vue wraps items in <ol class="sk-breadcrumbs-list"> with separators; static emits bare <nav>');
486
+ });
487
+
488
+ //------------------------------------------------------------------------------------------------------------------
489
+ // Pagination
490
+ //
491
+ // SKIP: SkPagination renders SkPaginationItem children (first/prev/page numbers/next/last)
492
+ // and requires a `total` prop to compute its item list. The static helper emits only the
493
+ // <nav class="sk-pagination ..."> container with no inner items.
494
+ //------------------------------------------------------------------------------------------------------------------
495
+
496
+ describe('pagination', () =>
497
+ {
498
+ skipNonParity('Vue renders pagination items (prev/page/next); static emits bare <nav> container');
499
+ });
500
+
501
+ //------------------------------------------------------------------------------------------------------------------
502
+ // Tag
503
+ //
504
+ // The static helper now wraps children in <span class="sk-tag-content"> and conditionally
505
+ // emits the remove button when `removable: true`, matching SkTag.vue's template structure.
506
+ //------------------------------------------------------------------------------------------------------------------
507
+
508
+ describe('tag', () =>
509
+ {
510
+ it('default (neutral, solid, md)', () =>
511
+ {
512
+ expectParity(compareParity({
513
+ vueComponent: SkTag,
514
+ staticHelper: tag as StaticFn,
515
+ props: { kind: 'neutral', variant: 'solid', size: 'md', removable: false },
516
+ children: 'Label',
517
+ }));
518
+ });
519
+
520
+ it('primary kind, outline variant', () =>
521
+ {
522
+ expectParity(compareParity({
523
+ vueComponent: SkTag,
524
+ staticHelper: tag as StaticFn,
525
+ props: { kind: 'primary', variant: 'outline', size: 'md', removable: false },
526
+ children: 'Feature',
527
+ }));
528
+ });
529
+
530
+ it('removable: true emits remove button', () =>
531
+ {
532
+ expectParity(compareParity({
533
+ vueComponent: SkTag,
534
+ staticHelper: tag as StaticFn,
535
+ props: { kind: 'neutral', variant: 'solid', size: 'md', removable: true },
536
+ children: 'Close me',
537
+ }));
538
+ });
539
+ });
540
+
541
+ //------------------------------------------------------------------------------------------------------------------
542
+ // Avatar
543
+ //
544
+ // The static helper now emits the correct inner content based on src/initials props:
545
+ // - src provided → <img class="sk-avatar-image" src="..." alt="...">
546
+ // - initials provided (no src) → <span class="sk-avatar-initials">AB</span>
547
+ // - neither → default SVG icon <svg class="sk-avatar-icon">...</svg>
548
+ //------------------------------------------------------------------------------------------------------------------
549
+
550
+ describe('avatar', () =>
551
+ {
552
+ it('default (no src, no initials — SVG icon)', () =>
553
+ {
554
+ expectParity(compareParity({
555
+ vueComponent: SkAvatar,
556
+ staticHelper: avatar as StaticFn,
557
+ props: { kind: 'neutral', size: 'md' },
558
+ }));
559
+ });
560
+
561
+ it('with src and alt — img element', () =>
562
+ {
563
+ expectParity(compareParity({
564
+ vueComponent: SkAvatar,
565
+ staticHelper: avatar as StaticFn,
566
+ props: { kind: 'neutral', size: 'md', src: '/test.jpg', alt: 'Test User' },
567
+ }));
568
+ });
569
+
570
+ it('initials fallback', () =>
571
+ {
572
+ expectParity(compareParity({
573
+ vueComponent: SkAvatar,
574
+ staticHelper: avatar as StaticFn,
575
+ props: { kind: 'primary', size: 'lg', initials: 'JD' },
576
+ }));
577
+ });
578
+ });
579
+
580
+ //------------------------------------------------------------------------------------------------------------------
581
+ // Field
582
+ //
583
+ // The static helper now emits the full field structure matching SkField.vue: sk-label-top
584
+ // default, sk-field-input-wrapper always wrapping children, and optional label/description/
585
+ // error elements. An explicit `id` prop is passed so that Vue's `for`/`id` attrs are
586
+ // deterministic and match the static output (Vue would otherwise auto-generate a random ID).
587
+ //------------------------------------------------------------------------------------------------------------------
588
+
589
+ describe('field', () =>
590
+ {
591
+ it('no label, no error (input wrapper only)', () =>
592
+ {
593
+ expectParity(compareParity({
594
+ vueComponent: SkField,
595
+ staticHelper: field as StaticFn,
596
+ props: { labelPosition: 'top', id: 'f1' },
597
+ children: '<input class="sk-input" />',
598
+ }));
599
+ });
600
+
601
+ it('with label', () =>
602
+ {
603
+ expectParity(compareParity({
604
+ vueComponent: SkField,
605
+ staticHelper: field as StaticFn,
606
+ props: { label: 'Email', labelPosition: 'top', id: 'f2' },
607
+ children: '<input class="sk-input" />',
608
+ }));
609
+ });
610
+
611
+ it('with error', () =>
612
+ {
613
+ expectParity(compareParity({
614
+ vueComponent: SkField,
615
+ staticHelper: field as StaticFn,
616
+ props: { error: 'Required', labelPosition: 'top', id: 'f3' },
617
+ children: '<input class="sk-input" />',
618
+ }));
619
+ });
620
+
621
+ it('with description (no error)', () =>
622
+ {
623
+ expectParity(compareParity({
624
+ vueComponent: SkField,
625
+ staticHelper: field as StaticFn,
626
+ props: { description: 'Help text', labelPosition: 'top', id: 'f4' },
627
+ children: '<input class="sk-input" />',
628
+ }));
629
+ });
630
+ });
631
+
632
+ //------------------------------------------------------------------------------------------------------------------
633
+ // Table
634
+ //
635
+ // The static helper now emits the wrapper div + table, matching SkTable.vue's rendered output.
636
+ // Vue defaults (hoverable=true, bordered=true, innerBorders=false, variant='default') must be
637
+ // passed explicitly so both sides emit the same class sets.
638
+ //------------------------------------------------------------------------------------------------------------------
639
+
640
+ describe('table', () =>
641
+ {
642
+ it('default props (neutral, default variant, hoverable, bordered)', () =>
643
+ {
644
+ expectParity(compareParity({
645
+ vueComponent: SkTable,
646
+ staticHelper: table as StaticFn,
647
+ props: {
648
+ kind: 'neutral',
649
+ variant: 'default',
650
+ striped: false,
651
+ hoverable: true,
652
+ bordered: true,
653
+ innerBorders: false,
654
+ darkBackground: false,
655
+ subtle: false,
656
+ },
657
+ children: '<thead><tr><th>Name</th></tr></thead>',
658
+ }));
659
+ });
660
+
661
+ it('primary kind', () =>
662
+ {
663
+ expectParity(compareParity({
664
+ vueComponent: SkTable,
665
+ staticHelper: table as StaticFn,
666
+ props: {
667
+ kind: 'primary',
668
+ variant: 'default',
669
+ striped: false,
670
+ hoverable: true,
671
+ bordered: true,
672
+ innerBorders: false,
673
+ darkBackground: false,
674
+ subtle: false,
675
+ },
676
+ children: 'x',
677
+ }));
678
+ });
679
+
680
+ it('striped + compact variant', () =>
681
+ {
682
+ expectParity(compareParity({
683
+ vueComponent: SkTable,
684
+ staticHelper: table as StaticFn,
685
+ props: {
686
+ kind: 'neutral',
687
+ variant: 'compact',
688
+ striped: true,
689
+ hoverable: true,
690
+ bordered: true,
691
+ innerBorders: false,
692
+ darkBackground: false,
693
+ subtle: false,
694
+ },
695
+ children: 'x',
696
+ }));
697
+ });
698
+ });
699
+
700
+ //------------------------------------------------------------------------------------------------------------------
701
+ // Tooltip
702
+ //
703
+ // SKIP: SkTooltip uses reka-ui's portal (TooltipPortal) — the tooltip content is rendered
704
+ // outside the component tree in a separate DOM node. The static helper emits a plain
705
+ // <div role="tooltip"> inline. These are fundamentally different rendering models.
706
+ //------------------------------------------------------------------------------------------------------------------
707
+
708
+ describe('tooltip', () =>
709
+ {
710
+ skipNonParity(
711
+ 'Vue uses reka-ui portal (tooltip content is teleported);'
712
+ + ' static emits inline <div role="tooltip">'
713
+ );
714
+ });
715
+ });
716
+
717
+ //----------------------------------------------------------------------------------------------------------------------