@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,837 @@
1
+ //----------------------------------------------------------------------------------------------------------------------
2
+ // Static Component Helper Tests
3
+ //
4
+ // One describe block per helper. Each block has a 'default' test and a modifier test so the
5
+ // render path is exercised without exhaustively covering every prop combination. Modifier tests
6
+ // pick the first non-trivial class the spec can emit (kind, boolean flag, etc.).
7
+ //----------------------------------------------------------------------------------------------------------------------
8
+
9
+ import { describe, expect, it } from 'vitest';
10
+
11
+ import { panel } from '../panel';
12
+ import { card } from '../card';
13
+ import { alert } from '../alert';
14
+ import { divider } from '../divider';
15
+ import { page } from '../page';
16
+ import { group } from '../group';
17
+ import { skeleton } from '../skeleton';
18
+ import { progress } from '../progress';
19
+ import { spinner } from '../spinner';
20
+ import { navBar } from '../navBar';
21
+ import { toolbar } from '../toolbar';
22
+ import { sidebar } from '../sidebar';
23
+ import { breadcrumbs } from '../breadcrumbs';
24
+ import { pagination } from '../pagination';
25
+ import { tag } from '../tag';
26
+ import { avatar } from '../avatar';
27
+ import { field } from '../field';
28
+ import { table } from '../table';
29
+ import { tooltip } from '../tooltip';
30
+ import { button } from '../button';
31
+ import { input } from '../input';
32
+ import { textarea } from '../textarea';
33
+ import { select } from '../select';
34
+ import { slider } from '../slider';
35
+ import { colorPicker } from '../colorPicker';
36
+ import { checkbox } from '../checkbox';
37
+ import { radio } from '../radio';
38
+ import { switchInput } from '../switchInput';
39
+ import { numberInput } from '../numberInput';
40
+ import { tagsInput } from '../tagsInput';
41
+ import { dropdown } from '../dropdown';
42
+
43
+ //----------------------------------------------------------------------------------------------------------------------
44
+ // Task 8: Panel pilot
45
+ //----------------------------------------------------------------------------------------------------------------------
46
+
47
+ describe('panel()', () =>
48
+ {
49
+ it('default', () =>
50
+ {
51
+ expect(panel({}, 'x')).toBe('<div class="sk-panel">x</div>');
52
+ });
53
+
54
+ it('kind', () =>
55
+ {
56
+ expect(panel({ kind: 'primary' }, 'x')).toBe('<div class="sk-panel sk-primary">x</div>');
57
+ });
58
+
59
+ it('size', () =>
60
+ {
61
+ expect(panel({ size: 'lg' }, 'x')).toBe('<div class="sk-panel sk-lg sk-size-lg">x</div>');
62
+ });
63
+
64
+ it('noBorder flag', () =>
65
+ {
66
+ expect(panel({ noBorder: true }, 'x')).toBe('<div class="sk-panel sk-no-border">x</div>');
67
+ });
68
+
69
+ it('custom colors emit inline style', () =>
70
+ {
71
+ expect(panel({ baseColor: '#f00', textColor: '#fff' }, 'x'))
72
+ .toBe('<div class="sk-panel" style="--sk-panel-color-base: #f00; --sk-panel-fg: #fff;">x</div>');
73
+ });
74
+ });
75
+
76
+ //----------------------------------------------------------------------------------------------------------------------
77
+ // Task 9: Container helpers
78
+ //----------------------------------------------------------------------------------------------------------------------
79
+
80
+ describe('card()', () =>
81
+ {
82
+ it('default', () =>
83
+ {
84
+ expect(card({}, 'x')).toBe('<div class="sk-card">x</div>');
85
+ });
86
+
87
+ it('kind', () =>
88
+ {
89
+ expect(card({ kind: 'primary' }, 'x')).toBe('<div class="sk-card sk-primary">x</div>');
90
+ });
91
+ });
92
+
93
+ //----------------------------------------------------------------------------------------------------------------------
94
+
95
+ describe('alert()', () =>
96
+ {
97
+ // Default kind is 'info' — feedback kind, so icon is shown.
98
+ it('default (info kind — has icon)', () =>
99
+ {
100
+ const html = alert({}, 'x');
101
+ expect(html).toContain('class="sk-alert sk-info"');
102
+ expect(html).toContain('role="alert"');
103
+ expect(html).toContain('<div class="sk-alert-icon">');
104
+ expect(html).toContain('<div class="sk-alert-content">x</div>');
105
+ });
106
+
107
+ it('warning kind (has icon)', () =>
108
+ {
109
+ const html = alert({ kind: 'warning' }, 'x');
110
+ expect(html).toContain('class="sk-alert sk-warning"');
111
+ expect(html).toContain('<div class="sk-alert-icon">');
112
+ expect(html).toContain('<div class="sk-alert-content">x</div>');
113
+ });
114
+
115
+ it('neutral kind (no icon)', () =>
116
+ {
117
+ const html = alert({ kind: 'neutral' }, 'x');
118
+ expect(html).toContain('class="sk-alert sk-neutral"');
119
+ expect(html).not.toContain('sk-alert-icon');
120
+ expect(html).toContain('<div class="sk-alert-content">x</div>');
121
+ });
122
+
123
+ it('icon: false suppresses icon on feedback kind', () =>
124
+ {
125
+ const html = alert({ kind: 'danger', icon: false }, 'x');
126
+ expect(html).not.toContain('sk-alert-icon');
127
+ expect(html).toContain('<div class="sk-alert-content">x</div>');
128
+ });
129
+
130
+ it('custom icon string', () =>
131
+ {
132
+ const html = alert({ kind: 'neutral', icon: '<span>!</span>' }, 'x');
133
+ expect(html).toContain('<div class="sk-alert-icon"><span>!</span></div>');
134
+ expect(html).toContain('<div class="sk-alert-content">x</div>');
135
+ });
136
+ });
137
+
138
+ //----------------------------------------------------------------------------------------------------------------------
139
+
140
+ describe('divider()', () =>
141
+ {
142
+ // Default: horizontal orientation, neutral kind, md size — matches Vue's withDefaults().
143
+ it('default', () =>
144
+ {
145
+ expect(divider({})).toBe('<hr class="sk-divider sk-horizontal sk-neutral sk-md" role="separator">');
146
+ });
147
+
148
+ it('orientation vertical', () =>
149
+ {
150
+ expect(divider({ orientation: 'vertical' }))
151
+ .toBe('<hr class="sk-divider sk-vertical sk-neutral sk-md" role="separator">');
152
+ });
153
+
154
+ it('kind', () =>
155
+ {
156
+ expect(divider({ kind: 'primary' }))
157
+ .toBe('<hr class="sk-divider sk-horizontal sk-primary sk-md" role="separator">');
158
+ });
159
+
160
+ it('subtle variant', () =>
161
+ {
162
+ expect(divider({ variant: 'subtle' }))
163
+ .toBe('<hr class="sk-divider sk-horizontal sk-neutral sk-md sk-subtle" role="separator">');
164
+ });
165
+ });
166
+
167
+ //----------------------------------------------------------------------------------------------------------------------
168
+
169
+ describe('page()', () =>
170
+ {
171
+ it('default', () =>
172
+ {
173
+ expect(page({}, 'x')).toBe('<div class="sk-page">x</div>');
174
+ });
175
+
176
+ it('flush flag', () =>
177
+ {
178
+ expect(page({ flush: true }, 'x')).toBe('<div class="sk-page sk-flush">x</div>');
179
+ });
180
+ });
181
+
182
+ //----------------------------------------------------------------------------------------------------------------------
183
+
184
+ describe('group()', () =>
185
+ {
186
+ it('default', () =>
187
+ {
188
+ expect(group({}, 'x')).toBe('<div class="sk-group">x</div>');
189
+ });
190
+
191
+ it('orientation', () =>
192
+ {
193
+ expect(group({ orientation: 'vertical' }, 'x')).toBe('<div class="sk-group sk-orientation-vertical">x</div>');
194
+ });
195
+ });
196
+
197
+ //----------------------------------------------------------------------------------------------------------------------
198
+
199
+ describe('skeleton()', () =>
200
+ {
201
+ // Default: text variant + shimmer animation — matches Vue's withDefaults().
202
+ it('default', () =>
203
+ {
204
+ expect(skeleton({})).toBe('<div class="sk-skeleton sk-text sk-shimmer"></div>');
205
+ });
206
+
207
+ it('variant circular', () =>
208
+ {
209
+ expect(skeleton({ variant: 'circular' })).toBe('<div class="sk-skeleton sk-circular sk-shimmer"></div>');
210
+ });
211
+
212
+ it('animation: none suppresses animation class', () =>
213
+ {
214
+ expect(skeleton({ animation: 'none' })).toBe('<div class="sk-skeleton sk-text"></div>');
215
+ });
216
+
217
+ it('pulse animation', () =>
218
+ {
219
+ expect(skeleton({ animation: 'pulse' })).toBe('<div class="sk-skeleton sk-text sk-pulse"></div>');
220
+ });
221
+
222
+ it('width prop emits inline style', () =>
223
+ {
224
+ expect(skeleton({ width: '200px' })).toBe(
225
+ '<div class="sk-skeleton sk-text sk-shimmer" style="width: 200px;"></div>'
226
+ );
227
+ });
228
+ });
229
+
230
+ //----------------------------------------------------------------------------------------------------------------------
231
+
232
+ describe('progress()', () =>
233
+ {
234
+ it('default', () =>
235
+ {
236
+ expect(progress({}, '')).toBe('<progress class="sk-progress" />');
237
+ });
238
+
239
+ it('kind', () =>
240
+ {
241
+ expect(progress({ kind: 'primary' }, '')).toBe('<progress class="sk-progress sk-primary" />');
242
+ });
243
+ });
244
+
245
+ //----------------------------------------------------------------------------------------------------------------------
246
+
247
+ describe('spinner()', () =>
248
+ {
249
+ // Default: primary kind, md size, circular variant + inner circular DOM structure.
250
+ it('default (circular variant, primary kind)', () =>
251
+ {
252
+ const html = spinner({});
253
+ expect(html).toContain('class="sk-spinner sk-primary sk-md sk-variant-circular"');
254
+ expect(html).toContain('role="status"');
255
+ expect(html).toContain('aria-live="polite"');
256
+ expect(html).toContain('aria-label="Loading"');
257
+ expect(html).toContain('<div class="sk-spinner-circular">');
258
+ expect(html).toContain('<div class="sk-arc sk-arc-large"></div>');
259
+ expect(html).toContain('<div class="sk-arc sk-arc-small"></div>');
260
+ });
261
+
262
+ it('dots variant', () =>
263
+ {
264
+ const html = spinner({ variant: 'dots' });
265
+ expect(html).toContain('sk-variant-dots');
266
+ expect(html).toContain('<div class="sk-spinner-dots">');
267
+ expect(html).toContain('<div class="sk-dot"></div>');
268
+ });
269
+
270
+ it('crosshair variant', () =>
271
+ {
272
+ const html = spinner({ variant: 'crosshair' });
273
+ expect(html).toContain('sk-variant-crosshair');
274
+ expect(html).toContain('<div class="sk-crosshair-loader"></div>');
275
+ });
276
+
277
+ it('kind and size', () =>
278
+ {
279
+ const html = spinner({ kind: 'accent', size: 'lg' });
280
+ expect(html).toContain('sk-spinner sk-accent sk-lg sk-variant-circular');
281
+ });
282
+ });
283
+
284
+ //----------------------------------------------------------------------------------------------------------------------
285
+
286
+ describe('navBar()', () =>
287
+ {
288
+ // sticky defaults to true — sk-sticky always present unless explicitly disabled.
289
+ it('default (neutral kind, sticky)', () =>
290
+ {
291
+ const html = navBar({}, 'x');
292
+ expect(html).toContain('class="sk-navbar sk-sticky"');
293
+ expect(html).toContain('<div class="sk-navbar-content">');
294
+ expect(html).toContain('<div class="sk-navbar-nav">x</div>');
295
+ });
296
+
297
+ it('kind', () =>
298
+ {
299
+ const html = navBar({ kind: 'primary' }, 'x');
300
+ expect(html).toContain('class="sk-navbar sk-primary sk-sticky"');
301
+ expect(html).toContain('<div class="sk-navbar-nav">x</div>');
302
+ });
303
+
304
+ it('sticky: false omits sk-sticky', () =>
305
+ {
306
+ const html = navBar({ sticky: false }, 'x');
307
+ expect(html).not.toContain('sk-sticky');
308
+ });
309
+
310
+ it('brand prop emits sk-navbar-brand', () =>
311
+ {
312
+ const html = navBar({ brand: '<img src="logo.svg" />' }, 'Links');
313
+ expect(html).toContain('<div class="sk-navbar-brand"><img src="logo.svg" /></div>');
314
+ expect(html).toContain('<div class="sk-navbar-nav">Links</div>');
315
+ });
316
+ });
317
+
318
+ //----------------------------------------------------------------------------------------------------------------------
319
+
320
+ describe('toolbar()', () =>
321
+ {
322
+ // Default corners = all four; default orientation = horizontal.
323
+ it('default (all corners, horizontal)', () =>
324
+ {
325
+ const html = toolbar({}, 'x');
326
+ expect(html).toContain(
327
+ 'class="sk-toolbar sk-horizontal sk-cut-top-left sk-cut-top-right sk-cut-bottom-right sk-cut-bottom-left"'
328
+ );
329
+ expect(html).toContain('role="toolbar"');
330
+ expect(html).toContain('>x</div>');
331
+ });
332
+
333
+ it('kind', () =>
334
+ {
335
+ const html = toolbar({ kind: 'primary' }, 'x');
336
+ expect(html).toContain('sk-toolbar sk-primary sk-horizontal');
337
+ });
338
+
339
+ it('vertical orientation', () =>
340
+ {
341
+ const html = toolbar({ orientation: 'vertical' }, 'x');
342
+ expect(html).toContain('sk-vertical');
343
+ expect(html).not.toContain('sk-horizontal');
344
+ });
345
+
346
+ it('empty corners', () =>
347
+ {
348
+ const html = toolbar({ corners: [] }, 'x');
349
+ expect(html).not.toContain('sk-cut-');
350
+ });
351
+ });
352
+
353
+ //----------------------------------------------------------------------------------------------------------------------
354
+
355
+ describe('sidebar()', () =>
356
+ {
357
+ it('default (neutral, left side)', () =>
358
+ {
359
+ const html = sidebar({}, 'x');
360
+ expect(html).toContain('<aside class="sk-sidebar sk-neutral">');
361
+ expect(html).toContain(
362
+ 'class="sk-panel sk-neutral sk-md sk-cut-bottom-right sk-decoration-bottom-right sk-sidebar-panel"'
363
+ );
364
+ expect(html).toContain('<div class="sk-panel-scroll-content">');
365
+ expect(html).toContain('<nav class="sk-sidebar-nav">x</nav>');
366
+ });
367
+
368
+ it('kind', () =>
369
+ {
370
+ const html = sidebar({ kind: 'primary' }, 'x');
371
+ expect(html).toContain('<aside class="sk-sidebar sk-primary">');
372
+ expect(html).toContain('sk-panel sk-primary');
373
+ });
374
+
375
+ it('right side uses bottom-left corner', () =>
376
+ {
377
+ const html = sidebar({ side: 'right' }, 'x');
378
+ expect(html).toContain('sk-sidebar-right');
379
+ expect(html).toContain('sk-cut-bottom-left');
380
+ expect(html).toContain('sk-decoration-bottom-left');
381
+ });
382
+ });
383
+
384
+ //----------------------------------------------------------------------------------------------------------------------
385
+
386
+ describe('breadcrumbs()', () =>
387
+ {
388
+ it('default', () =>
389
+ {
390
+ expect(breadcrumbs({}, 'x')).toBe('<nav class="sk-breadcrumbs" aria-label="Breadcrumbs">x</nav>');
391
+ });
392
+
393
+ it('kind', () =>
394
+ {
395
+ expect(breadcrumbs({ kind: 'primary' }, 'x')).toBe(
396
+ '<nav class="sk-breadcrumbs sk-primary" aria-label="Breadcrumbs">x</nav>'
397
+ );
398
+ });
399
+ });
400
+
401
+ //----------------------------------------------------------------------------------------------------------------------
402
+
403
+ describe('pagination()', () =>
404
+ {
405
+ it('default', () =>
406
+ {
407
+ expect(pagination({}, 'x')).toBe('<nav class="sk-pagination" aria-label="Pagination">x</nav>');
408
+ });
409
+
410
+ it('kind', () =>
411
+ {
412
+ expect(pagination({ kind: 'primary' }, 'x')).toBe(
413
+ '<nav class="sk-pagination sk-primary" aria-label="Pagination">x</nav>'
414
+ );
415
+ });
416
+ });
417
+
418
+ //----------------------------------------------------------------------------------------------------------------------
419
+
420
+ describe('tag()', () =>
421
+ {
422
+ // Default: neutral kind, solid variant, md size — matches Vue's withDefaults().
423
+ // Content is always wrapped in sk-tag-content span.
424
+ it('default (neutral, solid, md — sk-tag-content wrapper)', () =>
425
+ {
426
+ const html = tag({}, 'x');
427
+ expect(html).toContain('class="sk-tag sk-neutral sk-solid sk-md"');
428
+ expect(html).toContain('<span class="sk-tag-content">x</span>');
429
+ });
430
+
431
+ it('kind', () =>
432
+ {
433
+ const html = tag({ kind: 'primary' }, 'x');
434
+ expect(html).toContain('class="sk-tag sk-primary sk-solid sk-md"');
435
+ expect(html).toContain('<span class="sk-tag-content">x</span>');
436
+ });
437
+
438
+ it('removable emits remove button', () =>
439
+ {
440
+ const html = tag({ removable: true }, 'Label');
441
+ expect(html).toContain('sk-removable');
442
+ expect(html).toContain('<span class="sk-tag-content">Label</span>');
443
+ expect(html).toContain('<button type="button" class="sk-tag-remove" aria-label="Remove">');
444
+ expect(html).toContain('</button>');
445
+ });
446
+
447
+ it('variant and size modifiers', () =>
448
+ {
449
+ const html = tag({ variant: 'outline', size: 'sm' }, 'x');
450
+ expect(html).toContain('sk-tag sk-neutral sk-outline sk-sm');
451
+ });
452
+ });
453
+
454
+ //----------------------------------------------------------------------------------------------------------------------
455
+
456
+ describe('avatar()', () =>
457
+ {
458
+ // Default: neutral kind, md size, no src/initials → default SVG icon.
459
+ it('default (neutral, md — SVG icon fallback)', () =>
460
+ {
461
+ const html = avatar({});
462
+ expect(html).toContain('class="sk-avatar sk-neutral sk-md"');
463
+ expect(html).toContain('<svg class="sk-avatar-icon"');
464
+ expect(html).toContain('fill="currentColor"');
465
+ });
466
+
467
+ it('kind emits kind class', () =>
468
+ {
469
+ const html = avatar({ kind: 'primary' });
470
+ expect(html).toContain('class="sk-avatar sk-primary sk-md"');
471
+ expect(html).toContain('<svg class="sk-avatar-icon"');
472
+ });
473
+
474
+ it('src emits <img> element', () =>
475
+ {
476
+ const html = avatar({ src: '/x.jpg', alt: 'Jane Doe' });
477
+ expect(html).toContain('<img src="/x.jpg" alt="Jane Doe" class="sk-avatar-image">');
478
+ expect(html).not.toContain('sk-avatar-icon');
479
+ expect(html).not.toContain('sk-avatar-initials');
480
+ });
481
+
482
+ it('initials emits <span class="sk-avatar-initials">', () =>
483
+ {
484
+ const html = avatar({ initials: 'jd' });
485
+ expect(html).toContain('<span class="sk-avatar-initials">JD</span>');
486
+ expect(html).not.toContain('sk-avatar-icon');
487
+ });
488
+
489
+ it('initials truncated to 2 chars, uppercased', () =>
490
+ {
491
+ const html = avatar({ initials: 'john' });
492
+ expect(html).toContain('<span class="sk-avatar-initials">JO</span>');
493
+ });
494
+ });
495
+
496
+ //----------------------------------------------------------------------------------------------------------------------
497
+
498
+ describe('field()', () =>
499
+ {
500
+ // Default: labelPosition='top' → sk-label-top, children wrapped in sk-field-input-wrapper.
501
+ it('default (sk-label-top, input wrapper)', () =>
502
+ {
503
+ const html = field({}, 'x');
504
+ expect(html).toContain('class="sk-field sk-label-top"');
505
+ expect(html).toContain('<div class="sk-field-input-wrapper">x</div>');
506
+ expect(html).not.toContain('sk-field-label');
507
+ expect(html).not.toContain('sk-field-error');
508
+ });
509
+
510
+ it('label prop emits <label class="sk-field-label">', () =>
511
+ {
512
+ const html = field({ label: 'Email' }, 'x');
513
+ expect(html).toContain('<label class="sk-field-label">Email</label>');
514
+ expect(html).toContain('<div class="sk-field-input-wrapper">x</div>');
515
+ });
516
+
517
+ it('required adds asterisk span inside label', () =>
518
+ {
519
+ const html = field({ label: 'Name', required: true }, 'x');
520
+ expect(html).toContain('Name<span class="sk-field-required">*</span>');
521
+ });
522
+
523
+ it('error prop adds sk-has-error class and sk-field-error paragraph', () =>
524
+ {
525
+ const html = field({ error: 'Required' }, 'x');
526
+ expect(html).toContain('class="sk-field sk-label-top sk-has-error"');
527
+ expect(html).toContain('<p class="sk-field-error">Required</p>');
528
+ expect(html).not.toContain('sk-field-description');
529
+ });
530
+
531
+ it('description shown when no error', () =>
532
+ {
533
+ const html = field({ description: 'Help text' }, 'x');
534
+ expect(html).toContain('<p class="sk-field-description">Help text</p>');
535
+ });
536
+
537
+ it('description hidden when error is present', () =>
538
+ {
539
+ const html = field({ description: 'Help', error: 'Bad' }, 'x');
540
+ expect(html).toContain('<p class="sk-field-error">Bad</p>');
541
+ expect(html).not.toContain('sk-field-description');
542
+ });
543
+
544
+ it('labelPosition left', () =>
545
+ {
546
+ const html = field({ labelPosition: 'left' }, 'x');
547
+ expect(html).toContain('class="sk-field sk-label-left"');
548
+ });
549
+ });
550
+
551
+ //----------------------------------------------------------------------------------------------------------------------
552
+
553
+ describe('table()', () =>
554
+ {
555
+ // Default: sk-table-wrapper + sk-table with neutral kind, default variant,
556
+ // hoverable=true, bordered=true, innerBorders=false → sk-no-inner-borders.
557
+ it('default (wrapper div + table with hoverable/bordered/no-inner-borders)', () =>
558
+ {
559
+ const html = table({}, 'x');
560
+ expect(html).toContain('class="sk-table-wrapper sk-table-wrapper-neutral"');
561
+ expect(html).toContain('class="sk-table sk-neutral sk-default sk-hoverable sk-bordered sk-no-inner-borders"');
562
+ expect(html).toContain('>x</table>');
563
+ });
564
+
565
+ it('kind propagates to both wrapper and table', () =>
566
+ {
567
+ const html = table({ kind: 'primary' }, 'x');
568
+ expect(html).toContain('sk-table-wrapper-primary');
569
+ expect(html).toContain('class="sk-table sk-primary sk-default sk-hoverable sk-bordered sk-no-inner-borders"');
570
+ });
571
+
572
+ it('striped adds sk-striped', () =>
573
+ {
574
+ const html = table({ striped: true }, 'x');
575
+ expect(html).toContain('sk-striped');
576
+ });
577
+
578
+ it('hoverable: false removes sk-hoverable', () =>
579
+ {
580
+ const html = table({ hoverable: false }, 'x');
581
+ expect(html).not.toContain('sk-hoverable');
582
+ });
583
+
584
+ it('darkBackground adds sk-dark-background to wrapper', () =>
585
+ {
586
+ const html = table({ darkBackground: true }, 'x');
587
+ expect(html).toContain('sk-dark-background');
588
+ });
589
+
590
+ it('subtle adds sk-subtle to both wrapper and table', () =>
591
+ {
592
+ const html = table({ subtle: true }, 'x');
593
+ expect(html).toContain('sk-table-wrapper sk-table-wrapper-neutral sk-subtle');
594
+ expect(html).toContain('sk-subtle');
595
+ });
596
+ });
597
+
598
+ //----------------------------------------------------------------------------------------------------------------------
599
+
600
+ describe('tooltip()', () =>
601
+ {
602
+ it('default', () =>
603
+ {
604
+ expect(tooltip({}, 'x')).toBe('<div class="sk-tooltip" role="tooltip">x</div>');
605
+ });
606
+
607
+ it('kind', () =>
608
+ {
609
+ expect(tooltip({ kind: 'primary' }, 'x')).toBe('<div class="sk-tooltip sk-primary" role="tooltip">x</div>');
610
+ });
611
+ });
612
+
613
+ //----------------------------------------------------------------------------------------------------------------------
614
+ // Task 10: Button helper
615
+ //----------------------------------------------------------------------------------------------------------------------
616
+
617
+ describe('button()', () =>
618
+ {
619
+ it('default emits <button type="button"> with chrome span', () =>
620
+ {
621
+ expect(button({}, 'Save'))
622
+ .toBe('<button class="sk-button" type="button"><span class="sk-button-chrome">Save</span></button>');
623
+ });
624
+
625
+ it('kind + size + variant', () =>
626
+ {
627
+ expect(button({ kind: 'primary', size: 'lg', variant: 'outline' }, 'Click'))
628
+ .toBe('<button class="sk-button sk-primary sk-lg sk-size-lg sk-outline" type="button">'
629
+ + '<span class="sk-button-chrome">Click</span></button>');
630
+ });
631
+
632
+ it('loading and pressed emit aria attrs and boolean classes', () =>
633
+ {
634
+ expect(button({ loading: true, pressed: true }, 'x'))
635
+ .toBe('<button class="sk-button sk-loading sk-pressed" type="button" aria-busy="true" aria-pressed="true">'
636
+ + '<span class="sk-button-chrome">x</span></button>');
637
+ });
638
+
639
+ it('href renders as <a>, not <button>', () =>
640
+ {
641
+ expect(button({ href: '/docs' }, 'Docs'))
642
+ .toBe('<a class="sk-button" href="/docs"><span class="sk-button-chrome">Docs</span></a>');
643
+ });
644
+ });
645
+
646
+ //----------------------------------------------------------------------------------------------------------------------
647
+ // Task 11: Form-control helpers
648
+ //----------------------------------------------------------------------------------------------------------------------
649
+
650
+ describe('input()', () =>
651
+ {
652
+ it('default emits void input', () =>
653
+ {
654
+ expect(input({}))
655
+ .toBe('<input class="sk-input" />');
656
+ });
657
+
658
+ it('passes through value, placeholder, name via props', () =>
659
+ {
660
+ expect(input({ type: 'text', value: 'hi', placeholder: 'Your name', name: 'user' }))
661
+ .toBe('<input class="sk-input" type="text" value="hi" placeholder="Your name" name="user" />');
662
+ });
663
+ });
664
+
665
+ //----------------------------------------------------------------------------------------------------------------------
666
+
667
+ describe('textarea()', () =>
668
+ {
669
+ it('default', () =>
670
+ {
671
+ expect(textarea({}, 'content'))
672
+ .toBe('<textarea class="sk-textarea">content</textarea>');
673
+ });
674
+
675
+ it('passes through placeholder and name', () =>
676
+ {
677
+ expect(textarea({ placeholder: 'Enter text', name: 'bio' }, ''))
678
+ .toBe('<textarea class="sk-textarea" placeholder="Enter text" name="bio"></textarea>');
679
+ });
680
+ });
681
+
682
+ //----------------------------------------------------------------------------------------------------------------------
683
+
684
+ describe('select()', () =>
685
+ {
686
+ it('renders with option children', () =>
687
+ {
688
+ const options = '<option value="a">A</option><option value="b">B</option>';
689
+ expect(select({}, options))
690
+ .toBe('<select class="sk-select"><option value="a">A</option><option value="b">B</option></select>');
691
+ });
692
+ });
693
+
694
+ //----------------------------------------------------------------------------------------------------------------------
695
+
696
+ describe('slider()', () =>
697
+ {
698
+ it('default emits type=range', () =>
699
+ {
700
+ expect(slider({}))
701
+ .toBe('<input class="sk-slider" type="range" />');
702
+ });
703
+
704
+ it('passes through min, max, step, value', () =>
705
+ {
706
+ expect(slider({ min: '0', max: '100', step: '5', value: '50' }))
707
+ .toBe('<input class="sk-slider" type="range" min="0" max="100" step="5" value="50" />');
708
+ });
709
+ });
710
+
711
+ //----------------------------------------------------------------------------------------------------------------------
712
+
713
+ describe('colorPicker()', () =>
714
+ {
715
+ it('default emits type=color', () =>
716
+ {
717
+ expect(colorPicker({}))
718
+ .toBe('<input class="sk-color-picker" type="color" />');
719
+ });
720
+
721
+ it('passes through value', () =>
722
+ {
723
+ expect(colorPicker({ value: '#ff0000' }))
724
+ .toBe('<input class="sk-color-picker" type="color" value="#ff0000" />');
725
+ });
726
+ });
727
+
728
+ //----------------------------------------------------------------------------------------------------------------------
729
+ // Task 12: Compound form helpers
730
+ //----------------------------------------------------------------------------------------------------------------------
731
+
732
+ describe('checkbox()', () =>
733
+ {
734
+ it('default', () =>
735
+ {
736
+ expect(checkbox({}, 'Agree'))
737
+ .toBe('<label class="sk-checkbox"><input type="checkbox" /><span class="sk-checkbox-box"></span>'
738
+ + '<span class="sk-checkbox-label">Agree</span></label>');
739
+ });
740
+
741
+ it('checked + disabled', () =>
742
+ {
743
+ expect(checkbox({ checked: true, disabled: true }, 'x'))
744
+ .toBe('<label class="sk-checkbox"><input type="checkbox" checked disabled />'
745
+ + '<span class="sk-checkbox-box"></span><span class="sk-checkbox-label">x</span></label>');
746
+ });
747
+ });
748
+
749
+ //----------------------------------------------------------------------------------------------------------------------
750
+
751
+ describe('radio()', () =>
752
+ {
753
+ it('default with name and value', () =>
754
+ {
755
+ expect(radio({ name: 'flavor', value: 'chocolate' }, 'Chocolate'))
756
+ .toBe('<label class="sk-radio"><input type="radio" name="flavor" value="chocolate" />'
757
+ + '<span class="sk-radio-dot"></span><span class="sk-radio-label">Chocolate</span></label>');
758
+ });
759
+ });
760
+
761
+ //----------------------------------------------------------------------------------------------------------------------
762
+
763
+ describe('switchInput()', () =>
764
+ {
765
+ it('default', () =>
766
+ {
767
+ expect(switchInput({ name: 'notify' }, 'Notifications'))
768
+ .toBe('<label class="sk-switch"><input type="checkbox" name="notify" />'
769
+ + '<span class="sk-switch-track"><span class="sk-switch-thumb"></span></span>'
770
+ + '<span class="sk-switch-label">Notifications</span></label>');
771
+ });
772
+ });
773
+
774
+ //----------------------------------------------------------------------------------------------------------------------
775
+
776
+ describe('numberInput()', () =>
777
+ {
778
+ it('default', () =>
779
+ {
780
+ expect(numberInput({}))
781
+ .toBe('<div class="sk-number-input-wrapper">'
782
+ + '<input class="sk-number-input-field" type="number" />'
783
+ + '</div>');
784
+ });
785
+
786
+ it('with min, max, step, name', () =>
787
+ {
788
+ expect(numberInput({ min: '0', max: '100', step: '1', name: 'qty' }))
789
+ .toBe('<div class="sk-number-input-wrapper">'
790
+ + '<input class="sk-number-input-field" type="number" min="0" max="100" step="1" name="qty" />'
791
+ + '</div>');
792
+ });
793
+ });
794
+
795
+ //----------------------------------------------------------------------------------------------------------------------
796
+
797
+ describe('tagsInput()', () =>
798
+ {
799
+ it('default empty', () =>
800
+ {
801
+ expect(tagsInput({}))
802
+ .toBe('<div class="sk-tags-input"></div>');
803
+ });
804
+
805
+ it('with tags', () =>
806
+ {
807
+ expect(tagsInput({}, '<span class="sk-tags-input-tag">foo</span>'))
808
+ .toBe('<div class="sk-tags-input"><span class="sk-tags-input-tag">foo</span></div>');
809
+ });
810
+ });
811
+
812
+ //----------------------------------------------------------------------------------------------------------------------
813
+ // Task 13: Dropdown helper
814
+ //----------------------------------------------------------------------------------------------------------------------
815
+
816
+ describe('dropdown()', () =>
817
+ {
818
+ it('default', () =>
819
+ {
820
+ expect(dropdown({ summary: 'Actions' }, '<a href="#">Item 1</a>'))
821
+ .toBe('<details class="sk-dropdown"><summary>Actions</summary><a href="#">Item 1</a></details>');
822
+ });
823
+
824
+ it('open attribute when props.open is true', () =>
825
+ {
826
+ expect(dropdown({ summary: 'x', open: true }, 'content'))
827
+ .toBe('<details class="sk-dropdown" open><summary>x</summary>content</details>');
828
+ });
829
+
830
+ it('kind + size', () =>
831
+ {
832
+ expect(dropdown({ summary: 'x', kind: 'primary', size: 'md' }, 'content'))
833
+ .toBe('<details class="sk-dropdown sk-primary sk-md sk-size-md"><summary>x</summary>content</details>');
834
+ });
835
+ });
836
+
837
+ //----------------------------------------------------------------------------------------------------------------------