@furystack/shades-common-components 12.4.0 → 12.6.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 (297) hide show
  1. package/CHANGELOG.md +119 -0
  2. package/esm/components/app-bar-link.spec.js +16 -19
  3. package/esm/components/app-bar-link.spec.js.map +1 -1
  4. package/esm/components/app-bar.spec.js +6 -4
  5. package/esm/components/app-bar.spec.js.map +1 -1
  6. package/esm/components/avatar.spec.js +9 -9
  7. package/esm/components/avatar.spec.js.map +1 -1
  8. package/esm/components/breadcrumb.spec.js +2 -2
  9. package/esm/components/breadcrumb.spec.js.map +1 -1
  10. package/esm/components/button-group.d.ts +32 -0
  11. package/esm/components/button-group.d.ts.map +1 -1
  12. package/esm/components/button-group.js +26 -2
  13. package/esm/components/button-group.js.map +1 -1
  14. package/esm/components/button-group.spec.js +127 -11
  15. package/esm/components/button-group.spec.js.map +1 -1
  16. package/esm/components/button.spec.js +4 -4
  17. package/esm/components/button.spec.js.map +1 -1
  18. package/esm/components/cache-view.spec.js +2 -3
  19. package/esm/components/cache-view.spec.js.map +1 -1
  20. package/esm/components/carousel.spec.js +47 -47
  21. package/esm/components/carousel.spec.js.map +1 -1
  22. package/esm/components/circular-progress.spec.js +2 -2
  23. package/esm/components/command-palette/command-palette-input.spec.js +23 -19
  24. package/esm/components/command-palette/command-palette-input.spec.js.map +1 -1
  25. package/esm/components/command-palette/command-palette-suggestion-list.spec.js +27 -27
  26. package/esm/components/command-palette/command-palette-suggestion-list.spec.js.map +1 -1
  27. package/esm/components/command-palette/index.spec.js +64 -51
  28. package/esm/components/command-palette/index.spec.js.map +1 -1
  29. package/esm/components/context-menu/context-menu.spec.js +33 -33
  30. package/esm/components/context-menu/context-menu.spec.js.map +1 -1
  31. package/esm/components/data-grid/body.spec.js +13 -13
  32. package/esm/components/data-grid/body.spec.js.map +1 -1
  33. package/esm/components/data-grid/data-grid-row.spec.js +8 -8
  34. package/esm/components/data-grid/data-grid-row.spec.js.map +1 -1
  35. package/esm/components/data-grid/data-grid.d.ts +47 -3
  36. package/esm/components/data-grid/data-grid.d.ts.map +1 -1
  37. package/esm/components/data-grid/data-grid.js +8 -11
  38. package/esm/components/data-grid/data-grid.js.map +1 -1
  39. package/esm/components/data-grid/data-grid.spec.js +71 -28
  40. package/esm/components/data-grid/data-grid.spec.js.map +1 -1
  41. package/esm/components/data-grid/filters/boolean-filter.d.ts +12 -0
  42. package/esm/components/data-grid/filters/boolean-filter.d.ts.map +1 -0
  43. package/esm/components/data-grid/filters/boolean-filter.js +27 -0
  44. package/esm/components/data-grid/filters/boolean-filter.js.map +1 -0
  45. package/esm/components/data-grid/filters/boolean-filter.spec.d.ts +2 -0
  46. package/esm/components/data-grid/filters/boolean-filter.spec.d.ts.map +1 -0
  47. package/esm/components/data-grid/filters/boolean-filter.spec.js +114 -0
  48. package/esm/components/data-grid/filters/boolean-filter.spec.js.map +1 -0
  49. package/esm/components/data-grid/filters/date-filter.d.ts +12 -0
  50. package/esm/components/data-grid/filters/date-filter.d.ts.map +1 -0
  51. package/esm/components/data-grid/filters/date-filter.js +109 -0
  52. package/esm/components/data-grid/filters/date-filter.js.map +1 -0
  53. package/esm/components/data-grid/filters/date-filter.spec.d.ts +2 -0
  54. package/esm/components/data-grid/filters/date-filter.spec.d.ts.map +1 -0
  55. package/esm/components/data-grid/filters/date-filter.spec.js +145 -0
  56. package/esm/components/data-grid/filters/date-filter.spec.js.map +1 -0
  57. package/esm/components/data-grid/filters/enum-filter.d.ts +16 -0
  58. package/esm/components/data-grid/filters/enum-filter.d.ts.map +1 -0
  59. package/esm/components/data-grid/filters/enum-filter.js +72 -0
  60. package/esm/components/data-grid/filters/enum-filter.js.map +1 -0
  61. package/esm/components/data-grid/filters/enum-filter.spec.d.ts +2 -0
  62. package/esm/components/data-grid/filters/enum-filter.spec.d.ts.map +1 -0
  63. package/esm/components/data-grid/filters/enum-filter.spec.js +136 -0
  64. package/esm/components/data-grid/filters/enum-filter.spec.js.map +1 -0
  65. package/esm/components/data-grid/filters/filter-dropdown.d.ts +6 -0
  66. package/esm/components/data-grid/filters/filter-dropdown.d.ts.map +1 -0
  67. package/esm/components/data-grid/filters/filter-dropdown.js +41 -0
  68. package/esm/components/data-grid/filters/filter-dropdown.js.map +1 -0
  69. package/esm/components/data-grid/filters/filter-dropdown.spec.d.ts +2 -0
  70. package/esm/components/data-grid/filters/filter-dropdown.spec.d.ts.map +1 -0
  71. package/esm/components/data-grid/filters/filter-dropdown.spec.js +69 -0
  72. package/esm/components/data-grid/filters/filter-dropdown.spec.js.map +1 -0
  73. package/esm/components/data-grid/filters/filter-styles.d.ts +24 -0
  74. package/esm/components/data-grid/filters/filter-styles.d.ts.map +1 -0
  75. package/esm/components/data-grid/filters/filter-styles.js +25 -0
  76. package/esm/components/data-grid/filters/filter-styles.js.map +1 -0
  77. package/esm/components/data-grid/filters/index.d.ts +7 -0
  78. package/esm/components/data-grid/filters/index.d.ts.map +1 -0
  79. package/esm/components/data-grid/filters/index.js +7 -0
  80. package/esm/components/data-grid/filters/index.js.map +1 -0
  81. package/esm/components/data-grid/filters/number-filter.d.ts +12 -0
  82. package/esm/components/data-grid/filters/number-filter.d.ts.map +1 -0
  83. package/esm/components/data-grid/filters/number-filter.js +65 -0
  84. package/esm/components/data-grid/filters/number-filter.js.map +1 -0
  85. package/esm/components/data-grid/filters/number-filter.spec.d.ts +2 -0
  86. package/esm/components/data-grid/filters/number-filter.spec.d.ts.map +1 -0
  87. package/esm/components/data-grid/filters/number-filter.spec.js +142 -0
  88. package/esm/components/data-grid/filters/number-filter.spec.js.map +1 -0
  89. package/esm/components/data-grid/filters/string-filter.d.ts +12 -0
  90. package/esm/components/data-grid/filters/string-filter.d.ts.map +1 -0
  91. package/esm/components/data-grid/filters/string-filter.js +63 -0
  92. package/esm/components/data-grid/filters/string-filter.js.map +1 -0
  93. package/esm/components/data-grid/filters/string-filter.spec.d.ts +2 -0
  94. package/esm/components/data-grid/filters/string-filter.spec.d.ts.map +1 -0
  95. package/esm/components/data-grid/filters/string-filter.spec.js +128 -0
  96. package/esm/components/data-grid/filters/string-filter.spec.js.map +1 -0
  97. package/esm/components/data-grid/footer.d.ts +1 -0
  98. package/esm/components/data-grid/footer.d.ts.map +1 -1
  99. package/esm/components/data-grid/footer.js +24 -16
  100. package/esm/components/data-grid/footer.js.map +1 -1
  101. package/esm/components/data-grid/footer.spec.js +111 -71
  102. package/esm/components/data-grid/footer.spec.js.map +1 -1
  103. package/esm/components/data-grid/header.d.ts +6 -9
  104. package/esm/components/data-grid/header.d.ts.map +1 -1
  105. package/esm/components/data-grid/header.js +51 -117
  106. package/esm/components/data-grid/header.js.map +1 -1
  107. package/esm/components/data-grid/header.spec.js +116 -187
  108. package/esm/components/data-grid/header.spec.js.map +1 -1
  109. package/esm/components/data-grid/index.d.ts +1 -0
  110. package/esm/components/data-grid/index.d.ts.map +1 -1
  111. package/esm/components/data-grid/index.js +1 -0
  112. package/esm/components/data-grid/index.js.map +1 -1
  113. package/esm/components/data-grid/selection-cell.spec.js +8 -8
  114. package/esm/components/data-grid/selection-cell.spec.js.map +1 -1
  115. package/esm/components/drawer/drawer-toggle-button.spec.js +22 -22
  116. package/esm/components/drawer/drawer-toggle-button.spec.js.map +1 -1
  117. package/esm/components/drawer/index.spec.js +36 -36
  118. package/esm/components/drawer/index.spec.js.map +1 -1
  119. package/esm/components/dropdown.spec.js +38 -30
  120. package/esm/components/dropdown.spec.js.map +1 -1
  121. package/esm/components/fab.spec.js +4 -4
  122. package/esm/components/fab.spec.js.map +1 -1
  123. package/esm/components/form.spec.js +37 -37
  124. package/esm/components/form.spec.js.map +1 -1
  125. package/esm/components/grid.d.ts +3 -0
  126. package/esm/components/grid.d.ts.map +1 -1
  127. package/esm/components/grid.js +3 -0
  128. package/esm/components/grid.js.map +1 -1
  129. package/esm/components/grid.spec.js +3 -3
  130. package/esm/components/grid.spec.js.map +1 -1
  131. package/esm/components/image.spec.js +55 -52
  132. package/esm/components/image.spec.js.map +1 -1
  133. package/esm/components/inputs/autocomplete.d.ts +3 -0
  134. package/esm/components/inputs/autocomplete.d.ts.map +1 -1
  135. package/esm/components/inputs/autocomplete.js +3 -0
  136. package/esm/components/inputs/autocomplete.js.map +1 -1
  137. package/esm/components/inputs/autocomplete.spec.js +7 -14
  138. package/esm/components/inputs/autocomplete.spec.js.map +1 -1
  139. package/esm/components/inputs/checkbox.spec.js +22 -22
  140. package/esm/components/inputs/checkbox.spec.js.map +1 -1
  141. package/esm/components/inputs/input-number.spec.js +47 -47
  142. package/esm/components/inputs/input-number.spec.js.map +1 -1
  143. package/esm/components/inputs/input.spec.js +53 -53
  144. package/esm/components/inputs/input.spec.js.map +1 -1
  145. package/esm/components/inputs/radio-group.spec.js +14 -14
  146. package/esm/components/inputs/radio-group.spec.js.map +1 -1
  147. package/esm/components/inputs/radio.spec.js +16 -16
  148. package/esm/components/inputs/radio.spec.js.map +1 -1
  149. package/esm/components/inputs/select.spec.js +74 -74
  150. package/esm/components/inputs/select.spec.js.map +1 -1
  151. package/esm/components/inputs/slider.spec.js +16 -16
  152. package/esm/components/inputs/slider.spec.js.map +1 -1
  153. package/esm/components/inputs/switch.spec.js +24 -24
  154. package/esm/components/inputs/switch.spec.js.map +1 -1
  155. package/esm/components/inputs/text-area.spec.js +17 -17
  156. package/esm/components/inputs/text-area.spec.js.map +1 -1
  157. package/esm/components/linear-progress.spec.js +2 -2
  158. package/esm/components/list/list.d.ts +10 -0
  159. package/esm/components/list/list.d.ts.map +1 -1
  160. package/esm/components/list/list.js +23 -2
  161. package/esm/components/list/list.js.map +1 -1
  162. package/esm/components/list/list.spec.js +137 -36
  163. package/esm/components/list/list.spec.js.map +1 -1
  164. package/esm/components/markdown/markdown-display.spec.js +15 -15
  165. package/esm/components/markdown/markdown-display.spec.js.map +1 -1
  166. package/esm/components/markdown/markdown-editor.spec.js +8 -8
  167. package/esm/components/markdown/markdown-editor.spec.js.map +1 -1
  168. package/esm/components/markdown/markdown-input.d.ts +14 -0
  169. package/esm/components/markdown/markdown-input.d.ts.map +1 -1
  170. package/esm/components/markdown/markdown-input.js +48 -2
  171. package/esm/components/markdown/markdown-input.js.map +1 -1
  172. package/esm/components/markdown/markdown-input.spec.js +114 -17
  173. package/esm/components/markdown/markdown-input.spec.js.map +1 -1
  174. package/esm/components/menu/menu.spec.js +28 -28
  175. package/esm/components/menu/menu.spec.js.map +1 -1
  176. package/esm/components/modal.spec.js +15 -18
  177. package/esm/components/modal.spec.js.map +1 -1
  178. package/esm/components/noty-list.spec.js +25 -23
  179. package/esm/components/noty-list.spec.js.map +1 -1
  180. package/esm/components/page-container/index.spec.js +16 -16
  181. package/esm/components/page-container/index.spec.js.map +1 -1
  182. package/esm/components/page-container/page-header.spec.js +16 -16
  183. package/esm/components/page-container/page-header.spec.js.map +1 -1
  184. package/esm/components/page-layout/index.spec.js +29 -29
  185. package/esm/components/page-layout/index.spec.js.map +1 -1
  186. package/esm/components/paper.spec.js +3 -3
  187. package/esm/components/paper.spec.js.map +1 -1
  188. package/esm/components/rating.spec.js +61 -61
  189. package/esm/components/rating.spec.js.map +1 -1
  190. package/esm/components/skeleton.spec.js +10 -6
  191. package/esm/components/skeleton.spec.js.map +1 -1
  192. package/esm/components/suggest/index.d.ts +10 -2
  193. package/esm/components/suggest/index.d.ts.map +1 -1
  194. package/esm/components/suggest/index.js +21 -1
  195. package/esm/components/suggest/index.js.map +1 -1
  196. package/esm/components/suggest/index.spec.js +50 -0
  197. package/esm/components/suggest/index.spec.js.map +1 -1
  198. package/esm/components/suggest/suggest-input.spec.js +4 -10
  199. package/esm/components/suggest/suggest-input.spec.js.map +1 -1
  200. package/esm/components/tabs.spec.js +30 -30
  201. package/esm/components/tabs.spec.js.map +1 -1
  202. package/esm/components/tree/tree.spec.js +27 -27
  203. package/esm/components/tree/tree.spec.js.map +1 -1
  204. package/esm/components/typography.spec.js +3 -3
  205. package/esm/components/typography.spec.js.map +1 -1
  206. package/esm/components/wizard/index.d.ts +8 -0
  207. package/esm/components/wizard/index.d.ts.map +1 -1
  208. package/esm/components/wizard/index.js +90 -0
  209. package/esm/components/wizard/index.js.map +1 -1
  210. package/esm/components/wizard/index.spec.js +84 -7
  211. package/esm/components/wizard/index.spec.js.map +1 -1
  212. package/esm/utils/promisify-animation.d.ts.map +1 -1
  213. package/esm/utils/promisify-animation.js +3 -0
  214. package/esm/utils/promisify-animation.js.map +1 -1
  215. package/package.json +3 -3
  216. package/src/components/app-bar-link.spec.tsx +16 -19
  217. package/src/components/app-bar.spec.tsx +6 -4
  218. package/src/components/avatar.spec.tsx +9 -9
  219. package/src/components/breadcrumb.spec.tsx +2 -2
  220. package/src/components/button-group.spec.tsx +155 -11
  221. package/src/components/button-group.tsx +49 -2
  222. package/src/components/button.spec.tsx +4 -4
  223. package/src/components/cache-view.spec.tsx +3 -3
  224. package/src/components/carousel.spec.tsx +47 -47
  225. package/src/components/circular-progress.spec.tsx +2 -2
  226. package/src/components/command-palette/command-palette-input.spec.tsx +23 -19
  227. package/src/components/command-palette/command-palette-suggestion-list.spec.tsx +27 -27
  228. package/src/components/command-palette/index.spec.tsx +64 -51
  229. package/src/components/context-menu/context-menu.spec.tsx +33 -33
  230. package/src/components/data-grid/body.spec.tsx +13 -13
  231. package/src/components/data-grid/data-grid-row.spec.tsx +8 -8
  232. package/src/components/data-grid/data-grid.spec.tsx +106 -28
  233. package/src/components/data-grid/data-grid.tsx +57 -13
  234. package/src/components/data-grid/filters/boolean-filter.spec.tsx +142 -0
  235. package/src/components/data-grid/filters/boolean-filter.tsx +45 -0
  236. package/src/components/data-grid/filters/date-filter.spec.tsx +181 -0
  237. package/src/components/data-grid/filters/date-filter.tsx +162 -0
  238. package/src/components/data-grid/filters/enum-filter.spec.tsx +168 -0
  239. package/src/components/data-grid/filters/enum-filter.tsx +119 -0
  240. package/src/components/data-grid/filters/filter-dropdown.spec.tsx +89 -0
  241. package/src/components/data-grid/filters/filter-dropdown.tsx +60 -0
  242. package/src/components/data-grid/filters/filter-styles.ts +26 -0
  243. package/src/components/data-grid/filters/index.ts +6 -0
  244. package/src/components/data-grid/filters/number-filter.spec.tsx +174 -0
  245. package/src/components/data-grid/filters/number-filter.tsx +115 -0
  246. package/src/components/data-grid/filters/string-filter.spec.tsx +157 -0
  247. package/src/components/data-grid/filters/string-filter.tsx +112 -0
  248. package/src/components/data-grid/footer.spec.tsx +130 -74
  249. package/src/components/data-grid/footer.tsx +41 -34
  250. package/src/components/data-grid/header.spec.tsx +128 -212
  251. package/src/components/data-grid/header.tsx +95 -183
  252. package/src/components/data-grid/index.tsx +1 -0
  253. package/src/components/data-grid/selection-cell.spec.tsx +8 -8
  254. package/src/components/drawer/drawer-toggle-button.spec.tsx +22 -22
  255. package/src/components/drawer/index.spec.tsx +36 -36
  256. package/src/components/dropdown.spec.tsx +38 -30
  257. package/src/components/fab.spec.tsx +4 -4
  258. package/src/components/form.spec.tsx +37 -37
  259. package/src/components/grid.spec.tsx +3 -3
  260. package/src/components/grid.tsx +3 -0
  261. package/src/components/image.spec.tsx +55 -52
  262. package/src/components/inputs/autocomplete.spec.tsx +7 -14
  263. package/src/components/inputs/autocomplete.tsx +3 -0
  264. package/src/components/inputs/checkbox.spec.tsx +22 -22
  265. package/src/components/inputs/input-number.spec.tsx +47 -47
  266. package/src/components/inputs/input.spec.tsx +53 -53
  267. package/src/components/inputs/radio-group.spec.tsx +14 -14
  268. package/src/components/inputs/radio.spec.tsx +16 -16
  269. package/src/components/inputs/select.spec.tsx +74 -74
  270. package/src/components/inputs/slider.spec.tsx +16 -16
  271. package/src/components/inputs/switch.spec.tsx +24 -24
  272. package/src/components/inputs/text-area.spec.tsx +17 -17
  273. package/src/components/linear-progress.spec.tsx +2 -2
  274. package/src/components/list/list.spec.tsx +209 -36
  275. package/src/components/list/list.tsx +56 -19
  276. package/src/components/markdown/markdown-display.spec.tsx +15 -15
  277. package/src/components/markdown/markdown-editor.spec.tsx +8 -8
  278. package/src/components/markdown/markdown-input.spec.tsx +159 -17
  279. package/src/components/markdown/markdown-input.tsx +65 -1
  280. package/src/components/menu/menu.spec.tsx +28 -28
  281. package/src/components/modal.spec.tsx +15 -18
  282. package/src/components/noty-list.spec.tsx +25 -23
  283. package/src/components/page-container/index.spec.tsx +16 -16
  284. package/src/components/page-container/page-header.spec.tsx +16 -16
  285. package/src/components/page-layout/index.spec.tsx +29 -29
  286. package/src/components/paper.spec.tsx +3 -3
  287. package/src/components/rating.spec.tsx +61 -61
  288. package/src/components/skeleton.spec.tsx +10 -6
  289. package/src/components/suggest/index.spec.tsx +83 -0
  290. package/src/components/suggest/index.tsx +36 -3
  291. package/src/components/suggest/suggest-input.spec.tsx +4 -10
  292. package/src/components/tabs.spec.tsx +30 -30
  293. package/src/components/tree/tree.spec.tsx +27 -27
  294. package/src/components/typography.spec.tsx +3 -3
  295. package/src/components/wizard/index.spec.tsx +123 -6
  296. package/src/components/wizard/index.tsx +125 -0
  297. package/src/utils/promisify-animation.ts +3 -0
@@ -1,7 +1,7 @@
1
1
  import { Injector } from '@furystack/inject'
2
2
  import type { ChildrenList } from '@furystack/shades'
3
- import { createComponent, initializeShadeRoot, Shade } from '@furystack/shades'
4
- import { sleepAsync, usingAsync } from '@furystack/utils'
3
+ import { createComponent, flushUpdates, initializeShadeRoot, Shade } from '@furystack/shades'
4
+ import { usingAsync } from '@furystack/utils'
5
5
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
6
6
  import type { WizardStepProps } from './index.js'
7
7
  import { Wizard } from './index.js'
@@ -72,15 +72,23 @@ describe('Wizard', () => {
72
72
  const renderWizard = async (
73
73
  steps: Array<(props: WizardStepProps, children: ChildrenList) => JSX.Element>,
74
74
  onFinish?: () => void,
75
+ options?: { stepLabels?: string[]; showProgress?: boolean },
75
76
  ) => {
76
77
  const injector = new Injector()
77
78
  const root = document.getElementById('root')!
78
79
  initializeShadeRoot({
79
80
  injector,
80
81
  rootElement: root,
81
- jsxElement: <Wizard steps={steps} onFinish={onFinish} />,
82
+ jsxElement: (
83
+ <Wizard
84
+ steps={steps}
85
+ onFinish={onFinish}
86
+ stepLabels={options?.stepLabels}
87
+ showProgress={options?.showProgress}
88
+ />
89
+ ),
82
90
  })
83
- await sleepAsync(50)
91
+ await flushUpdates()
84
92
 
85
93
  const wizard = root.querySelector('shades-wizard') as HTMLElement
86
94
 
@@ -96,12 +104,12 @@ describe('Wizard', () => {
96
104
  clickNext: async () => {
97
105
  const btn = getStepElement()?.querySelector('.next-btn') as HTMLButtonElement
98
106
  btn?.click()
99
- await sleepAsync(50)
107
+ await flushUpdates()
100
108
  },
101
109
  clickPrev: async () => {
102
110
  const btn = getStepElement()?.querySelector('.prev-btn') as HTMLButtonElement
103
111
  btn?.click()
104
- await sleepAsync(50)
112
+ await flushUpdates()
105
113
  },
106
114
  [Symbol.asyncDispose]: () => injector[Symbol.asyncDispose](),
107
115
  }
@@ -215,6 +223,115 @@ describe('Wizard', () => {
215
223
  })
216
224
  })
217
225
 
226
+ describe('step indicator', () => {
227
+ it('should not render step indicator when stepLabels is not provided', async () => {
228
+ await usingAsync(await renderWizard([Step1, Step2]), async ({ wizard }) => {
229
+ const indicator = wizard.querySelector('[data-testid="wizard-step-indicator"]')
230
+ expect(indicator).toBeNull()
231
+ })
232
+ })
233
+
234
+ it('should render step indicator when stepLabels is provided', async () => {
235
+ await usingAsync(
236
+ await renderWizard([Step1, Step2, Step3], undefined, {
237
+ stepLabels: ['First', 'Second', 'Third'],
238
+ }),
239
+ async ({ wizard }) => {
240
+ const indicator = wizard.querySelector('[data-testid="wizard-step-indicator"]')
241
+ expect(indicator).toBeTruthy()
242
+
243
+ const circles = indicator?.querySelectorAll('.wizard-step-circle')
244
+ expect(circles?.length).toBe(3)
245
+ },
246
+ )
247
+ })
248
+
249
+ it('should show step labels', async () => {
250
+ await usingAsync(
251
+ await renderWizard([Step1, Step2], undefined, {
252
+ stepLabels: ['Setup', 'Confirm'],
253
+ }),
254
+ async ({ wizard }) => {
255
+ const labels = wizard.querySelectorAll('.wizard-step-label')
256
+ expect(labels[0]?.textContent).toBe('Setup')
257
+ expect(labels[1]?.textContent).toBe('Confirm')
258
+ },
259
+ )
260
+ })
261
+
262
+ it('should mark the current step as active', async () => {
263
+ await usingAsync(
264
+ await renderWizard([Step1, Step2, Step3], undefined, {
265
+ stepLabels: ['A', 'B', 'C'],
266
+ }),
267
+ async ({ wizard }) => {
268
+ const circles = wizard.querySelectorAll('.wizard-step-circle')
269
+ expect(circles[0]?.hasAttribute('data-active')).toBe(true)
270
+ expect(circles[1]?.hasAttribute('data-active')).toBe(false)
271
+ },
272
+ )
273
+ })
274
+
275
+ it('should update active step on navigation', async () => {
276
+ await usingAsync(
277
+ await renderWizard([Step1, Step2, Step3], undefined, {
278
+ stepLabels: ['A', 'B', 'C'],
279
+ }),
280
+ async ({ wizard, clickNext }) => {
281
+ await clickNext()
282
+ const circles = wizard.querySelectorAll('.wizard-step-circle')
283
+ expect(circles[0]?.hasAttribute('data-completed')).toBe(true)
284
+ expect(circles[1]?.hasAttribute('data-active')).toBe(true)
285
+ expect(circles[2]?.hasAttribute('data-active')).toBe(false)
286
+ },
287
+ )
288
+ })
289
+ })
290
+
291
+ describe('progress bar', () => {
292
+ it('should not render progress bar when showProgress is false', async () => {
293
+ await usingAsync(await renderWizard([Step1, Step2]), async ({ wizard }) => {
294
+ const progressBar = wizard.querySelector('[data-testid="wizard-progress-bar"]')
295
+ expect(progressBar).toBeNull()
296
+ })
297
+ })
298
+
299
+ it('should render progress bar when showProgress is true', async () => {
300
+ await usingAsync(
301
+ await renderWizard([Step1, Step2, Step3], undefined, { showProgress: true }),
302
+ async ({ wizard }) => {
303
+ const progressBar = wizard.querySelector('[data-testid="wizard-progress-bar"]')
304
+ expect(progressBar).toBeTruthy()
305
+ },
306
+ )
307
+ })
308
+
309
+ it('should start at 0% on first step', async () => {
310
+ await usingAsync(
311
+ await renderWizard([Step1, Step2, Step3], undefined, { showProgress: true }),
312
+ async ({ wizard }) => {
313
+ const fill = wizard.querySelector('.wizard-progress-fill') as HTMLElement
314
+ expect(fill?.style.width).toBe('0%')
315
+ },
316
+ )
317
+ })
318
+
319
+ it('should update progress on navigation', async () => {
320
+ await usingAsync(
321
+ await renderWizard([Step1, Step2, Step3], undefined, { showProgress: true }),
322
+ async ({ wizard, clickNext }) => {
323
+ await clickNext()
324
+ const fill = wizard.querySelector('.wizard-progress-fill') as HTMLElement
325
+ expect(fill?.style.width).toBe('50%')
326
+
327
+ await clickNext()
328
+ const fill2 = wizard.querySelector('.wizard-progress-fill') as HTMLElement
329
+ expect(fill2?.style.width).toBe('100%')
330
+ },
331
+ )
332
+ })
333
+ })
334
+
218
335
  describe('Paper container', () => {
219
336
  it('should render step inside a Paper component', async () => {
220
337
  await usingAsync(await renderWizard([Step1]), async ({ wizard }) => {
@@ -1,5 +1,6 @@
1
1
  import type { ChildrenList } from '@furystack/shades'
2
2
  import { createComponent, Shade } from '@furystack/shades'
3
+ import { cssVariableTheme } from '../../services/css-variable-theme.js'
3
4
  import { Paper } from '../paper.js'
4
5
 
5
6
  export interface WizardStepProps {
@@ -30,6 +31,14 @@ export interface WizardProps {
30
31
  * A callback that will be executed when the wizard is completed
31
32
  */
32
33
  onFinish?: () => void
34
+ /**
35
+ * Optional labels for each step. When provided, a step indicator is shown above the content.
36
+ */
37
+ stepLabels?: string[]
38
+ /**
39
+ * When true, a progress bar is shown above the content.
40
+ */
41
+ showProgress?: boolean
33
42
  }
34
43
 
35
44
  export const Wizard = Shade<WizardProps>({
@@ -42,15 +51,131 @@ export const Wizard = Shade<WizardProps>({
42
51
  width: '100%',
43
52
  height: '100%',
44
53
  },
54
+ '& .wizard-step-indicator': {
55
+ display: 'flex',
56
+ alignItems: 'center',
57
+ justifyContent: 'center',
58
+ padding: '16px 24px 8px',
59
+ gap: '0',
60
+ },
61
+ '& .wizard-step-node': {
62
+ display: 'flex',
63
+ flexDirection: 'column',
64
+ alignItems: 'center',
65
+ gap: '4px',
66
+ zIndex: '1',
67
+ minWidth: '32px',
68
+ },
69
+ '& .wizard-step-circle': {
70
+ width: '28px',
71
+ height: '28px',
72
+ borderRadius: '50%',
73
+ display: 'flex',
74
+ alignItems: 'center',
75
+ justifyContent: 'center',
76
+ fontSize: cssVariableTheme.typography.fontSize.xs,
77
+ fontWeight: cssVariableTheme.typography.fontWeight.semibold,
78
+ border: `2px solid ${cssVariableTheme.action.subtleBorder}`,
79
+ background: cssVariableTheme.background.default,
80
+ color: cssVariableTheme.text.secondary,
81
+ transition: `all ${cssVariableTheme.transitions.duration.normal} ease`,
82
+ },
83
+ '& .wizard-step-circle[data-active]': {
84
+ borderColor: cssVariableTheme.palette.primary.main,
85
+ background: cssVariableTheme.palette.primary.main,
86
+ color: cssVariableTheme.palette.primary.mainContrast,
87
+ },
88
+ '& .wizard-step-circle[data-completed]': {
89
+ borderColor: cssVariableTheme.palette.primary.main,
90
+ background: cssVariableTheme.palette.primary.main,
91
+ color: cssVariableTheme.palette.primary.mainContrast,
92
+ opacity: '0.7',
93
+ },
94
+ '& .wizard-step-label': {
95
+ fontSize: cssVariableTheme.typography.fontSize.xs,
96
+ color: cssVariableTheme.text.secondary,
97
+ textAlign: 'center',
98
+ maxWidth: '80px',
99
+ overflow: 'hidden',
100
+ textOverflow: 'ellipsis',
101
+ whiteSpace: 'nowrap',
102
+ },
103
+ '& .wizard-step-label[data-active]': {
104
+ color: cssVariableTheme.palette.primary.main,
105
+ fontWeight: cssVariableTheme.typography.fontWeight.semibold,
106
+ },
107
+ '& .wizard-step-connector': {
108
+ flex: '1',
109
+ height: '2px',
110
+ background: cssVariableTheme.action.subtleBorder,
111
+ minWidth: '24px',
112
+ alignSelf: 'flex-start',
113
+ marginTop: '14px',
114
+ transition: `background ${cssVariableTheme.transitions.duration.normal} ease`,
115
+ },
116
+ '& .wizard-step-connector[data-completed]': {
117
+ background: cssVariableTheme.palette.primary.main,
118
+ },
119
+ '& .wizard-progress-bar': {
120
+ height: '4px',
121
+ background: cssVariableTheme.action.subtleBorder,
122
+ margin: '12px 24px 4px',
123
+ borderRadius: '2px',
124
+ overflow: 'hidden',
125
+ },
126
+ '& .wizard-progress-fill': {
127
+ height: '100%',
128
+ background: cssVariableTheme.palette.primary.main,
129
+ borderRadius: '2px',
130
+ transition: `width ${cssVariableTheme.transitions.duration.normal} ease`,
131
+ },
45
132
  },
46
133
  render: ({ props, useState }) => {
47
134
  const [currentPage, setCurrentPage] = useState('currentPage', 0)
48
135
 
136
+ if (props.stepLabels && props.stepLabels.length !== props.steps.length) {
137
+ console.warn(
138
+ `[Wizard] stepLabels length (${props.stepLabels.length}) does not match steps length (${props.steps.length}).`,
139
+ )
140
+ }
141
+
49
142
  const CurrentPage = props.steps[currentPage]
143
+ const progressPercent = props.steps.length > 1 ? (currentPage / (props.steps.length - 1)) * 100 : 100
50
144
 
51
145
  return (
52
146
  <div className="wizard-container">
53
147
  <Paper style={{ maxWidth: '100%', maxHeight: '100%' }} elevation={3} onclick={(ev) => ev.stopPropagation()}>
148
+ {props.stepLabels && props.stepLabels.length > 0 && (
149
+ <div className="wizard-step-indicator" data-testid="wizard-step-indicator">
150
+ {props.steps.map((_, index) => (
151
+ <>
152
+ {index > 0 && (
153
+ <div
154
+ className="wizard-step-connector"
155
+ {...(index <= currentPage ? { 'data-completed': '' } : {})}
156
+ />
157
+ )}
158
+ <div className="wizard-step-node">
159
+ <div
160
+ className="wizard-step-circle"
161
+ {...(index === currentPage ? { 'data-active': '' } : {})}
162
+ {...(index < currentPage ? { 'data-completed': '' } : {})}
163
+ >
164
+ {(index + 1).toString()}
165
+ </div>
166
+ <span className="wizard-step-label" {...(index === currentPage ? { 'data-active': '' } : {})}>
167
+ {props.stepLabels?.[index] ?? ''}
168
+ </span>
169
+ </div>
170
+ </>
171
+ ))}
172
+ </div>
173
+ )}
174
+ {props.showProgress && (
175
+ <div className="wizard-progress-bar" data-testid="wizard-progress-bar">
176
+ <div className="wizard-progress-fill" style={{ width: `${progressPercent}%` }} />
177
+ </div>
178
+ )}
54
179
  <CurrentPage
55
180
  currentPage={currentPage}
56
181
  maxPages={props.steps.length}
@@ -13,6 +13,9 @@ export const promisifyAnimation = async (
13
13
  if (!el) {
14
14
  return reject(new Error('No element provided'))
15
15
  }
16
+ if (typeof el.animate !== 'function') {
17
+ return resolve({} as AnimationPlaybackEvent)
18
+ }
16
19
  const prefersReducedMotion = window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches
17
20
  const resolvedOptions: KeyframeAnimationOptions =
18
21
  typeof options === 'number' ? { duration: options } : { ...options }