@happyvertical/smrt-ui 0.51.39 → 0.53.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 (392) hide show
  1. package/AGENTS.md +25 -3
  2. package/README.md +558 -8
  3. package/dist/actions/__tests__/swipe-dismiss.test.js +69 -0
  4. package/dist/actions/swipe-dismiss.d.ts +42 -0
  5. package/dist/actions/swipe-dismiss.d.ts.map +1 -0
  6. package/dist/actions/swipe-dismiss.js +69 -0
  7. package/dist/components/calendar/Calendar.svelte +5 -0
  8. package/dist/components/calendar/Calendar.svelte.d.ts.map +1 -1
  9. package/dist/components/calendar/CalendarView.svelte +1250 -0
  10. package/dist/components/calendar/CalendarView.svelte.d.ts +66 -0
  11. package/dist/components/calendar/CalendarView.svelte.d.ts.map +1 -0
  12. package/dist/components/calendar/DayView.svelte +5 -0
  13. package/dist/components/calendar/DayView.svelte.d.ts.map +1 -1
  14. package/dist/components/calendar/__tests__/CalendarView.test.js +226 -0
  15. package/dist/components/calendar/__tests__/calendar-model.test.js +138 -0
  16. package/dist/components/calendar/calendar-model.d.ts +178 -0
  17. package/dist/components/calendar/calendar-model.d.ts.map +1 -0
  18. package/dist/components/calendar/calendar-model.js +362 -0
  19. package/dist/components/calendar/index.d.ts +12 -1
  20. package/dist/components/calendar/index.d.ts.map +1 -1
  21. package/dist/components/calendar/index.js +11 -1
  22. package/dist/components/data/CollectionList.svelte +23 -5
  23. package/dist/components/data/CollectionList.svelte.d.ts +8 -2
  24. package/dist/components/data/CollectionList.svelte.d.ts.map +1 -1
  25. package/dist/components/data/DataTable.svelte +178 -55
  26. package/dist/components/data/DataTable.svelte.d.ts.map +1 -1
  27. package/dist/components/data/DataTableController.d.ts +4 -0
  28. package/dist/components/data/DataTableController.d.ts.map +1 -1
  29. package/dist/components/data/DataTableController.js +11 -4
  30. package/dist/components/data/DataTableResponsive.d.ts +4 -0
  31. package/dist/components/data/DataTableResponsive.d.ts.map +1 -0
  32. package/dist/components/data/DataTableResponsive.js +22 -0
  33. package/dist/components/data/ListSortSelect.svelte +121 -0
  34. package/dist/components/data/ListSortSelect.svelte.d.ts +22 -0
  35. package/dist/components/data/ListSortSelect.svelte.d.ts.map +1 -0
  36. package/dist/components/data/SortableHeader.svelte +169 -0
  37. package/dist/components/data/SortableHeader.svelte.d.ts +30 -0
  38. package/dist/components/data/SortableHeader.svelte.d.ts.map +1 -0
  39. package/dist/components/data/__tests__/CollectionList.test.js +37 -0
  40. package/dist/components/data/__tests__/DataTable.test.js +76 -0
  41. package/dist/components/data/__tests__/DataTableController.test.js +35 -0
  42. package/dist/components/data/__tests__/DataTableResponsive.test.js +80 -0
  43. package/dist/components/data/__tests__/SortableHeader.test.js +101 -0
  44. package/dist/components/data/__tests__/link-step-surface.test.js +172 -0
  45. package/dist/components/data/__tests__/list-sort-state.svelte.test.js +71 -0
  46. package/dist/components/data/__tests__/list-sort.test.js +91 -0
  47. package/dist/components/data/__tests__/responsive-table.fixture.svelte +29 -0
  48. package/dist/components/data/__tests__/responsive-table.fixture.svelte.d.ts +7 -0
  49. package/dist/components/data/__tests__/responsive-table.fixture.svelte.d.ts.map +1 -0
  50. package/dist/components/data/__tests__/surface-navigation.test.js +121 -0
  51. package/dist/components/data/data-surface.d.ts +1 -0
  52. package/dist/components/data/data-surface.d.ts.map +1 -1
  53. package/dist/components/data/data-surface.js +1 -0
  54. package/dist/components/data/index.d.ts +6 -0
  55. package/dist/components/data/index.d.ts.map +1 -1
  56. package/dist/components/data/index.js +6 -0
  57. package/dist/components/data/link-surface.d.ts +79 -0
  58. package/dist/components/data/link-surface.d.ts.map +1 -0
  59. package/dist/components/data/link-surface.js +155 -0
  60. package/dist/components/data/list-sort-state.svelte.d.ts +39 -0
  61. package/dist/components/data/list-sort-state.svelte.d.ts.map +1 -0
  62. package/dist/components/data/list-sort-state.svelte.js +47 -0
  63. package/dist/components/data/list-sort.d.ts +94 -0
  64. package/dist/components/data/list-sort.d.ts.map +1 -0
  65. package/dist/components/data/list-sort.js +182 -0
  66. package/dist/components/data/step-surface.d.ts +73 -0
  67. package/dist/components/data/step-surface.d.ts.map +1 -0
  68. package/dist/components/data/step-surface.js +177 -0
  69. package/dist/components/data/surface-navigation.d.ts +49 -0
  70. package/dist/components/data/surface-navigation.d.ts.map +1 -0
  71. package/dist/components/data/surface-navigation.js +100 -0
  72. package/dist/components/data/types.d.ts +27 -0
  73. package/dist/components/data/types.d.ts.map +1 -1
  74. package/dist/components/display/DateDisplay.svelte +76 -14
  75. package/dist/components/display/DateDisplay.svelte.d.ts +2 -0
  76. package/dist/components/display/DateDisplay.svelte.d.ts.map +1 -1
  77. package/dist/components/display/Icon.svelte +3 -16
  78. package/dist/components/display/Icon.svelte.d.ts +0 -9
  79. package/dist/components/display/Icon.svelte.d.ts.map +1 -1
  80. package/dist/components/display/StatusBadge.svelte +29 -2
  81. package/dist/components/display/StatusBadge.svelte.d.ts +4 -2
  82. package/dist/components/display/StatusBadge.svelte.d.ts.map +1 -1
  83. package/dist/components/display/__tests__/DateDisplay.test.js +82 -1
  84. package/dist/components/display/__tests__/Icon.test.js +132 -2
  85. package/dist/components/display/__tests__/StatusBadge.test.js +78 -0
  86. package/dist/components/display/icons.svelte.d.ts +11 -0
  87. package/dist/components/display/icons.svelte.d.ts.map +1 -0
  88. package/dist/components/display/icons.svelte.js +60 -0
  89. package/dist/components/display/index.d.ts +3 -1
  90. package/dist/components/display/index.d.ts.map +1 -1
  91. package/dist/components/display/index.js +1 -0
  92. package/dist/components/display/types.d.ts +2 -0
  93. package/dist/components/display/types.d.ts.map +1 -1
  94. package/dist/components/feedback/ConfirmDialog.svelte +59 -48
  95. package/dist/components/feedback/ConfirmDialog.svelte.d.ts +14 -2
  96. package/dist/components/feedback/ConfirmDialog.svelte.d.ts.map +1 -1
  97. package/dist/components/feedback/PhoneSheet.svelte +224 -0
  98. package/dist/components/feedback/PhoneSheet.svelte.d.ts +35 -0
  99. package/dist/components/feedback/PhoneSheet.svelte.d.ts.map +1 -0
  100. package/dist/components/feedback/ToastViewport.svelte +156 -8
  101. package/dist/components/feedback/ToastViewport.svelte.d.ts +5 -1
  102. package/dist/components/feedback/ToastViewport.svelte.d.ts.map +1 -1
  103. package/dist/components/feedback/WorkingStrip.svelte +426 -0
  104. package/dist/components/feedback/WorkingStrip.svelte.d.ts +37 -0
  105. package/dist/components/feedback/WorkingStrip.svelte.d.ts.map +1 -0
  106. package/dist/components/feedback/__tests__/ConfirmDialog.test.js +38 -20
  107. package/dist/components/feedback/__tests__/PhoneSheet.test.js +101 -0
  108. package/dist/components/feedback/__tests__/WorkingStrip.test.js +172 -0
  109. package/dist/components/feedback/__tests__/confirm-message.fixture.svelte +9 -0
  110. package/dist/components/feedback/__tests__/confirm-message.fixture.svelte.d.ts +19 -0
  111. package/dist/components/feedback/__tests__/confirm-message.fixture.svelte.d.ts.map +1 -0
  112. package/dist/components/feedback/__tests__/toast.test.js +142 -1
  113. package/dist/components/feedback/index.d.ts +5 -1
  114. package/dist/components/feedback/index.d.ts.map +1 -1
  115. package/dist/components/feedback/index.js +5 -0
  116. package/dist/components/feedback/toast.d.ts +13 -1
  117. package/dist/components/feedback/toast.d.ts.map +1 -1
  118. package/dist/components/feedback/toast.js +51 -10
  119. package/dist/components/feedback/working-status.d.ts +34 -0
  120. package/dist/components/feedback/working-status.d.ts.map +1 -0
  121. package/dist/components/feedback/working-status.js +5 -0
  122. package/dist/components/forms/CameraCapture.svelte +619 -0
  123. package/dist/components/forms/CameraCapture.svelte.d.ts +42 -0
  124. package/dist/components/forms/CameraCapture.svelte.d.ts.map +1 -0
  125. package/dist/components/forms/Checkbox.svelte +34 -3
  126. package/dist/components/forms/Checkbox.svelte.d.ts +2 -0
  127. package/dist/components/forms/Checkbox.svelte.d.ts.map +1 -1
  128. package/dist/components/forms/Combobox.svelte +108 -12
  129. package/dist/components/forms/Combobox.svelte.d.ts +6 -0
  130. package/dist/components/forms/Combobox.svelte.d.ts.map +1 -1
  131. package/dist/components/forms/DictationButton.svelte +128 -0
  132. package/dist/components/forms/DictationButton.svelte.d.ts +19 -0
  133. package/dist/components/forms/DictationButton.svelte.d.ts.map +1 -0
  134. package/dist/components/forms/DictationStatus.svelte +164 -0
  135. package/dist/components/forms/DictationStatus.svelte.d.ts +11 -0
  136. package/dist/components/forms/DictationStatus.svelte.d.ts.map +1 -0
  137. package/dist/components/forms/Fieldset.svelte +5 -1
  138. package/dist/components/forms/Fieldset.svelte.d.ts +2 -0
  139. package/dist/components/forms/Fieldset.svelte.d.ts.map +1 -1
  140. package/dist/components/forms/Form.svelte +5 -0
  141. package/dist/components/forms/Form.svelte.d.ts +1 -0
  142. package/dist/components/forms/Form.svelte.d.ts.map +1 -1
  143. package/dist/components/forms/FormActionBar.svelte +84 -0
  144. package/dist/components/forms/FormActionBar.svelte.d.ts +23 -0
  145. package/dist/components/forms/FormActionBar.svelte.d.ts.map +1 -0
  146. package/dist/components/forms/Input.svelte +17 -1
  147. package/dist/components/forms/Input.svelte.d.ts +2 -0
  148. package/dist/components/forms/Input.svelte.d.ts.map +1 -1
  149. package/dist/components/forms/Listbox.svelte +39 -1
  150. package/dist/components/forms/Listbox.svelte.d.ts.map +1 -1
  151. package/dist/components/forms/MultiSelect.svelte +41 -1
  152. package/dist/components/forms/MultiSelect.svelte.d.ts.map +1 -1
  153. package/dist/components/forms/Radio.svelte +16 -4
  154. package/dist/components/forms/RadioGroup.svelte +7 -1
  155. package/dist/components/forms/RadioGroup.svelte.d.ts +2 -0
  156. package/dist/components/forms/RadioGroup.svelte.d.ts.map +1 -1
  157. package/dist/components/forms/SearchInput.svelte +207 -0
  158. package/dist/components/forms/SearchInput.svelte.d.ts +22 -0
  159. package/dist/components/forms/SearchInput.svelte.d.ts.map +1 -0
  160. package/dist/components/forms/SegmentedControl.svelte +152 -11
  161. package/dist/components/forms/SegmentedControl.svelte.d.ts +4 -0
  162. package/dist/components/forms/SegmentedControl.svelte.d.ts.map +1 -1
  163. package/dist/components/forms/Select.svelte +17 -1
  164. package/dist/components/forms/Select.svelte.d.ts +2 -0
  165. package/dist/components/forms/Select.svelte.d.ts.map +1 -1
  166. package/dist/components/forms/SignaturePad.svelte +436 -0
  167. package/dist/components/forms/SignaturePad.svelte.d.ts +32 -0
  168. package/dist/components/forms/SignaturePad.svelte.d.ts.map +1 -0
  169. package/dist/components/forms/StagedControlReview.svelte +21 -12
  170. package/dist/components/forms/StagedControlReview.svelte.d.ts.map +1 -1
  171. package/dist/components/forms/Switch.svelte +18 -5
  172. package/dist/components/forms/Switch.svelte.d.ts +2 -0
  173. package/dist/components/forms/Switch.svelte.d.ts.map +1 -1
  174. package/dist/components/forms/TagsInput.svelte +8 -1
  175. package/dist/components/forms/Textarea.svelte +16 -1
  176. package/dist/components/forms/Textarea.svelte.d.ts +2 -0
  177. package/dist/components/forms/Textarea.svelte.d.ts.map +1 -1
  178. package/dist/components/forms/__tests__/CameraCapture.test.js +504 -0
  179. package/dist/components/forms/__tests__/Combobox.test.js +354 -0
  180. package/dist/components/forms/__tests__/ControlHitArea.test.js +73 -0
  181. package/dist/components/forms/__tests__/DictationButton.test.js +176 -0
  182. package/dist/components/forms/__tests__/Fieldset.test.js +37 -0
  183. package/dist/components/forms/__tests__/Form.test.js +46 -0
  184. package/dist/components/forms/__tests__/FormActionBar.test.js +22 -0
  185. package/dist/components/forms/__tests__/Listbox.test.js +303 -0
  186. package/dist/components/forms/__tests__/MultiSelect.test.js +281 -0
  187. package/dist/components/forms/__tests__/SearchInput.test.js +92 -0
  188. package/dist/components/forms/__tests__/SignaturePad.test.js +338 -0
  189. package/dist/components/forms/__tests__/StagedControlReview.test.js +37 -3
  190. package/dist/components/forms/__tests__/camera-capture-session.test.js +207 -0
  191. package/dist/components/forms/__tests__/capture-form-retry.test.js +166 -0
  192. package/dist/components/forms/__tests__/capture-form.fixture.svelte +38 -0
  193. package/dist/components/forms/__tests__/capture-form.fixture.svelte.d.ts +10 -0
  194. package/dist/components/forms/__tests__/capture-form.fixture.svelte.d.ts.map +1 -0
  195. package/dist/components/forms/__tests__/capture-test-env.d.ts +70 -0
  196. package/dist/components/forms/__tests__/capture-test-env.d.ts.map +1 -0
  197. package/dist/components/forms/__tests__/capture-test-env.js +122 -0
  198. package/dist/components/forms/__tests__/capture-touch.fixture.svelte +15 -0
  199. package/dist/components/forms/__tests__/capture-touch.fixture.svelte.d.ts +8 -0
  200. package/dist/components/forms/__tests__/capture-touch.fixture.svelte.d.ts.map +1 -0
  201. package/dist/components/forms/__tests__/combobox-form.fixture.svelte +29 -0
  202. package/dist/components/forms/__tests__/combobox-form.fixture.svelte.d.ts +14 -0
  203. package/dist/components/forms/__tests__/combobox-form.fixture.svelte.d.ts.map +1 -0
  204. package/dist/components/forms/__tests__/combobox-labels.fixture.svelte +19 -0
  205. package/dist/components/forms/__tests__/combobox-labels.fixture.svelte.d.ts +11 -0
  206. package/dist/components/forms/__tests__/combobox-labels.fixture.svelte.d.ts.map +1 -0
  207. package/dist/components/forms/__tests__/composite-registration.fixture.svelte +29 -0
  208. package/dist/components/forms/__tests__/composite-registration.fixture.svelte.d.ts +8 -0
  209. package/dist/components/forms/__tests__/composite-registration.fixture.svelte.d.ts.map +1 -0
  210. package/dist/components/forms/__tests__/control-interaction.test.js +19 -1
  211. package/dist/components/forms/__tests__/control-proposals.test.js +63 -0
  212. package/dist/components/forms/__tests__/dictation-recording.test.js +582 -0
  213. package/dist/components/forms/__tests__/dictation.test.js +486 -0
  214. package/dist/components/forms/__tests__/form-attachment.fixture.svelte +28 -0
  215. package/dist/components/forms/__tests__/form-attachment.fixture.svelte.d.ts +11 -0
  216. package/dist/components/forms/__tests__/form-attachment.fixture.svelte.d.ts.map +1 -0
  217. package/dist/components/forms/__tests__/form-retry-form.fixture.svelte +31 -0
  218. package/dist/components/forms/__tests__/form-retry-form.fixture.svelte.d.ts +13 -0
  219. package/dist/components/forms/__tests__/form-retry-form.fixture.svelte.d.ts.map +1 -0
  220. package/dist/components/forms/__tests__/form-retry-form.test.js +107 -0
  221. package/dist/components/forms/__tests__/hidden-controls-form-retry.test.js +69 -0
  222. package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte +29 -0
  223. package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte.d.ts +9 -0
  224. package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte.d.ts.map +1 -0
  225. package/dist/components/forms/__tests__/listbox-form.fixture.svelte +27 -0
  226. package/dist/components/forms/__tests__/listbox-form.fixture.svelte.d.ts +13 -0
  227. package/dist/components/forms/__tests__/listbox-form.fixture.svelte.d.ts.map +1 -0
  228. package/dist/components/forms/__tests__/long-press.test.js +175 -0
  229. package/dist/components/forms/__tests__/multiselect-form.fixture.svelte +27 -0
  230. package/dist/components/forms/__tests__/multiselect-form.fixture.svelte.d.ts +13 -0
  231. package/dist/components/forms/__tests__/multiselect-form.fixture.svelte.d.ts.map +1 -0
  232. package/dist/components/forms/__tests__/native-file-field.test.js +177 -0
  233. package/dist/components/forms/__tests__/place-picker.fixture.svelte +33 -0
  234. package/dist/components/forms/__tests__/place-picker.fixture.svelte.d.ts +12 -0
  235. package/dist/components/forms/__tests__/place-picker.fixture.svelte.d.ts.map +1 -0
  236. package/dist/components/forms/__tests__/segmented-posting.fixture.svelte +28 -0
  237. package/dist/components/forms/__tests__/segmented-posting.fixture.svelte.d.ts +12 -0
  238. package/dist/components/forms/__tests__/segmented-posting.fixture.svelte.d.ts.map +1 -0
  239. package/dist/components/forms/__tests__/segmented-posting.test.js +92 -0
  240. package/dist/components/forms/__tests__/signature-pad-logic.test.js +54 -0
  241. package/dist/components/forms/__tests__/touch-density.fixture.svelte +34 -0
  242. package/dist/components/forms/__tests__/touch-density.fixture.svelte.d.ts +8 -0
  243. package/dist/components/forms/__tests__/touch-density.fixture.svelte.d.ts.map +1 -0
  244. package/dist/components/forms/__tests__/touch-density.test.js +66 -0
  245. package/dist/components/forms/audio-capture.d.ts +69 -0
  246. package/dist/components/forms/audio-capture.d.ts.map +1 -0
  247. package/dist/components/forms/audio-capture.js +193 -0
  248. package/dist/components/forms/camera-capture-session.d.ts +81 -0
  249. package/dist/components/forms/camera-capture-session.d.ts.map +1 -0
  250. package/dist/components/forms/camera-capture-session.js +130 -0
  251. package/dist/components/forms/control-interaction.d.ts +6 -0
  252. package/dist/components/forms/control-interaction.d.ts.map +1 -1
  253. package/dist/components/forms/control-interaction.js +12 -1
  254. package/dist/components/forms/control-proposals.d.ts +55 -0
  255. package/dist/components/forms/control-proposals.d.ts.map +1 -0
  256. package/dist/components/forms/control-proposals.js +188 -0
  257. package/dist/components/forms/dictation-transcribe.d.ts +36 -0
  258. package/dist/components/forms/dictation-transcribe.d.ts.map +1 -0
  259. package/dist/components/forms/dictation-transcribe.js +65 -0
  260. package/dist/components/forms/dictation.svelte.d.ts +200 -0
  261. package/dist/components/forms/dictation.svelte.d.ts.map +1 -0
  262. package/dist/components/forms/dictation.svelte.js +686 -0
  263. package/dist/components/forms/form-retry/__tests__/boundaries.test.js +23 -0
  264. package/dist/components/forms/form-retry/__tests__/content-digest.test.js +70 -0
  265. package/dist/components/forms/form-retry/__tests__/controller.test.js +802 -0
  266. package/dist/components/forms/form-retry/__tests__/fake-kit.d.ts +82 -0
  267. package/dist/components/forms/form-retry/__tests__/fake-kit.d.ts.map +1 -0
  268. package/dist/components/forms/form-retry/__tests__/fake-kit.js +180 -0
  269. package/dist/components/forms/form-retry/__tests__/form-draft.test.js +169 -0
  270. package/dist/components/forms/form-retry/__tests__/ssr.test.js +37 -0
  271. package/dist/components/forms/form-retry/__tests__/storage.d.ts +26 -0
  272. package/dist/components/forms/form-retry/__tests__/storage.d.ts.map +1 -0
  273. package/dist/components/forms/form-retry/__tests__/storage.js +63 -0
  274. package/dist/components/forms/form-retry/__tests__/submission-key.test.js +236 -0
  275. package/dist/components/forms/form-retry/__tests__/submitted-draft.test.js +334 -0
  276. package/dist/components/forms/form-retry/content-digest.d.ts +54 -0
  277. package/dist/components/forms/form-retry/content-digest.d.ts.map +1 -0
  278. package/dist/components/forms/form-retry/content-digest.js +169 -0
  279. package/dist/components/forms/form-retry/controller.d.ts +214 -0
  280. package/dist/components/forms/form-retry/controller.d.ts.map +1 -0
  281. package/dist/components/forms/form-retry/controller.js +404 -0
  282. package/dist/components/forms/form-retry/form-draft.d.ts +68 -0
  283. package/dist/components/forms/form-retry/form-draft.d.ts.map +1 -0
  284. package/dist/components/forms/form-retry/form-draft.js +161 -0
  285. package/dist/components/forms/form-retry/index.d.ts +19 -0
  286. package/dist/components/forms/form-retry/index.d.ts.map +1 -0
  287. package/dist/components/forms/form-retry/index.js +16 -0
  288. package/dist/components/forms/form-retry/submission-key.d.ts +244 -0
  289. package/dist/components/forms/form-retry/submission-key.d.ts.map +1 -0
  290. package/dist/components/forms/form-retry/submission-key.js +342 -0
  291. package/dist/components/forms/form-retry/submitted-draft.d.ts +112 -0
  292. package/dist/components/forms/form-retry/submitted-draft.d.ts.map +1 -0
  293. package/dist/components/forms/form-retry/submitted-draft.js +270 -0
  294. package/dist/components/forms/form-retry/types.d.ts +84 -0
  295. package/dist/components/forms/form-retry/types.d.ts.map +1 -0
  296. package/dist/components/forms/form-retry/types.js +13 -0
  297. package/dist/components/forms/index.d.ts +21 -2
  298. package/dist/components/forms/index.d.ts.map +1 -1
  299. package/dist/components/forms/index.js +22 -1
  300. package/dist/components/forms/insert-text.d.ts +12 -0
  301. package/dist/components/forms/insert-text.d.ts.map +1 -0
  302. package/dist/components/forms/insert-text.js +38 -0
  303. package/dist/components/forms/long-press.d.ts +88 -0
  304. package/dist/components/forms/long-press.d.ts.map +1 -0
  305. package/dist/components/forms/long-press.js +213 -0
  306. package/dist/components/forms/native-file-field.d.ts +94 -0
  307. package/dist/components/forms/native-file-field.d.ts.map +1 -0
  308. package/dist/components/forms/native-file-field.js +147 -0
  309. package/dist/components/forms/ready-beep.d.ts +24 -0
  310. package/dist/components/forms/ready-beep.d.ts.map +1 -0
  311. package/dist/components/forms/ready-beep.js +82 -0
  312. package/dist/components/forms/signature-pad-logic.d.ts +50 -0
  313. package/dist/components/forms/signature-pad-logic.d.ts.map +1 -0
  314. package/dist/components/forms/signature-pad-logic.js +48 -0
  315. package/dist/components/forms/types.d.ts +41 -0
  316. package/dist/components/forms/types.d.ts.map +1 -1
  317. package/dist/components/layout/PageHeader.svelte +151 -17
  318. package/dist/components/layout/PageHeader.svelte.d.ts +47 -7
  319. package/dist/components/layout/PageHeader.svelte.d.ts.map +1 -1
  320. package/dist/components/layout/__tests__/PageHeader.test.js +87 -1
  321. package/dist/components/layout/index.d.ts +1 -0
  322. package/dist/components/layout/index.d.ts.map +1 -1
  323. package/dist/components/layout/index.js +1 -0
  324. package/dist/components/layout/page-header-context.d.ts +25 -0
  325. package/dist/components/layout/page-header-context.d.ts.map +1 -0
  326. package/dist/components/layout/page-header-context.js +16 -0
  327. package/dist/components/nav/FilterChips.svelte +8 -1
  328. package/dist/components/nav/FilterChips.svelte.d.ts +2 -0
  329. package/dist/components/nav/FilterChips.svelte.d.ts.map +1 -1
  330. package/dist/components/nav/Tabs.svelte +241 -0
  331. package/dist/components/nav/Tabs.svelte.d.ts +15 -0
  332. package/dist/components/nav/Tabs.svelte.d.ts.map +1 -1
  333. package/dist/components/nav/__tests__/LinkTabs.test.js +117 -0
  334. package/dist/components/nav/index.d.ts +1 -0
  335. package/dist/components/nav/index.d.ts.map +1 -1
  336. package/dist/components/nav/index.js +1 -0
  337. package/dist/components/nav/tabs-overflow.d.ts +12 -0
  338. package/dist/components/nav/tabs-overflow.d.ts.map +1 -0
  339. package/dist/components/nav/tabs-overflow.js +17 -0
  340. package/dist/components/nav/types.d.ts +8 -0
  341. package/dist/components/nav/types.d.ts.map +1 -1
  342. package/dist/components/ui/Button.svelte +16 -2
  343. package/dist/components/ui/Button.svelte.d.ts +2 -0
  344. package/dist/components/ui/Button.svelte.d.ts.map +1 -1
  345. package/dist/components/ui/Card.svelte +14 -5
  346. package/dist/components/ui/Dropdown.svelte +55 -1
  347. package/dist/components/ui/Dropdown.svelte.d.ts +8 -0
  348. package/dist/components/ui/Dropdown.svelte.d.ts.map +1 -1
  349. package/dist/components/ui/IconToggle.svelte +139 -0
  350. package/dist/components/ui/IconToggle.svelte.d.ts +32 -0
  351. package/dist/components/ui/IconToggle.svelte.d.ts.map +1 -0
  352. package/dist/components/ui/IconToggleGroup.svelte +56 -0
  353. package/dist/components/ui/IconToggleGroup.svelte.d.ts +21 -0
  354. package/dist/components/ui/IconToggleGroup.svelte.d.ts.map +1 -0
  355. package/dist/components/ui/Pagination.svelte +1 -1
  356. package/dist/components/ui/Popover.svelte +17 -1
  357. package/dist/components/ui/Popover.svelte.d.ts +8 -0
  358. package/dist/components/ui/Popover.svelte.d.ts.map +1 -1
  359. package/dist/components/ui/__tests__/IconToggle.test.js +59 -0
  360. package/dist/components/ui/__tests__/IconToggleGroup.test.js +30 -0
  361. package/dist/components/ui/__tests__/Popover.test.js +44 -0
  362. package/dist/components/ui/__tests__/dropdown-popover-icon.test.js +125 -0
  363. package/dist/components/ui/__tests__/phone-touch-targets.test.js +39 -0
  364. package/dist/components/ui/index.d.ts +2 -0
  365. package/dist/components/ui/index.d.ts.map +1 -1
  366. package/dist/components/ui/index.js +2 -0
  367. package/dist/i18n/strings.ui.d.ts +65 -0
  368. package/dist/i18n/strings.ui.d.ts.map +1 -1
  369. package/dist/i18n/strings.ui.js +72 -0
  370. package/dist/styles/tokens.css +2 -0
  371. package/dist/svelte/__tests__/playground.test.js +3 -1
  372. package/dist/svelte/playground/CalendarPreview.svelte +140 -0
  373. package/dist/svelte/playground/CalendarPreview.svelte.d.ts +4 -0
  374. package/dist/svelte/playground/CalendarPreview.svelte.d.ts.map +1 -0
  375. package/dist/svelte/playground/CapturePreview.svelte +56 -0
  376. package/dist/svelte/playground/CapturePreview.svelte.d.ts +4 -0
  377. package/dist/svelte/playground/CapturePreview.svelte.d.ts.map +1 -0
  378. package/dist/svelte/playground.d.ts.map +1 -1
  379. package/dist/svelte/playground.js +20 -0
  380. package/dist/themes/ThemeProvider.svelte +9 -0
  381. package/dist/themes/ThemeProvider.svelte.d.ts +2 -0
  382. package/dist/themes/ThemeProvider.svelte.d.ts.map +1 -1
  383. package/dist/themes/css-generator.d.ts.map +1 -1
  384. package/dist/themes/css-generator.js +3 -0
  385. package/dist/themes/styles/glass.css +4 -0
  386. package/dist/themes/styles/happyvertical.css +4 -0
  387. package/dist/themes/styles/material.css +4 -0
  388. package/dist/themes/styles/smrt.css +4 -0
  389. package/dist/themes/styles/studio.css +4 -0
  390. package/dist/types-generic.d.ts +2 -0
  391. package/dist/types-generic.d.ts.map +1 -1
  392. package/package.json +9 -3
@@ -14,6 +14,8 @@ import type { ButtonSize, ButtonVariant } from '../../types-generic';
14
14
 
15
15
  /** Props for Button component */
16
16
  export interface Props extends Omit<HTMLButtonAttributes, 'class' | 'href'> {
17
+ /** Target density; omit to inherit the ThemeProvider setting. */
18
+ density?: 'comfortable' | 'touch';
17
19
  /** Visual variant */
18
20
  variant?: ButtonVariant;
19
21
  /** Size variant */
@@ -62,6 +64,7 @@ const {
62
64
  target,
63
65
  rel,
64
66
  download,
67
+ density,
65
68
  ...rest
66
69
  }: Props = $props();
67
70
 
@@ -118,7 +121,7 @@ function handleClick(event: MouseEvent) {
118
121
  {target}
119
122
  {rel}
120
123
  {download}
121
- class="button {variant} {size} {className}"
124
+ data-density={density} class="button {variant} {size} {className}"
122
125
  class:disabled={isDisabled}
123
126
  class:full-width={fullWidth}
124
127
  class:loading
@@ -140,7 +143,7 @@ function handleClick(event: MouseEvent) {
140
143
  {type}
141
144
  disabled={isDisabled}
142
145
  aria-busy={loading}
143
- class="button {variant} {size} {className}"
146
+ data-density={density} class="button {variant} {size} {className}"
144
147
  class:full-width={fullWidth}
145
148
  class:loading
146
149
  onclick={handleClick}
@@ -295,10 +298,21 @@ function handleClick(event: MouseEvent) {
295
298
  outline-offset: 2px;
296
299
  }
297
300
 
301
+ /* Phones: every size is at least a 44px touch target. */
302
+ @media (max-width: 48rem) {
303
+ .button {
304
+ min-block-size: 2.75rem;
305
+ }
306
+ }
307
+
298
308
  /* Reduced motion */
299
309
  @media (prefers-reduced-motion: reduce) {
300
310
  .spinner {
301
311
  animation: none;
302
312
  }
303
313
  }
314
+
315
+ .button[data-density='touch'] { --smrt-control-target-min: var(--smrt-touch-target-min, 48px); }
316
+ .button[data-density='comfortable'] { --smrt-control-target-min: 0px; }
317
+ .button { min-height: var(--smrt-control-target-min, 0px); min-width: var(--smrt-control-target-min, 0px); }
304
318
  </style>
@@ -9,6 +9,8 @@ import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements
9
9
  import type { ButtonSize, ButtonVariant } from '../../types-generic';
10
10
  /** Props for Button component */
11
11
  export interface Props extends Omit<HTMLButtonAttributes, 'class' | 'href'> {
12
+ /** Target density; omit to inherit the ThemeProvider setting. */
13
+ density?: 'comfortable' | 'touch';
12
14
  /** Visual variant */
13
15
  variant?: ButtonVariant;
14
16
  /** Size variant */
@@ -1 +1 @@
1
- {"version":3,"file":"Button.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/ui/Button.svelte.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EACV,oBAAoB,EACpB,oBAAoB,EACrB,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGrE,iCAAiC;AACjC,MAAM,WAAW,KAAM,SAAQ,IAAI,CAAC,oBAAoB,EAAE,OAAO,GAAG,MAAM,CAAC;IACzE,qBAAqB;IACrB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,mBAAmB;IACnB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,wBAAwB;IACxB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,oBAAoB;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,MAAM,CAAC,EAAE,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IACxC,6EAA6E;IAC7E,GAAG,CAAC,EAAE,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAClC,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,oBAAoB,CAAC,UAAU,CAAC,CAAC;CAC7C;AAmGD,QAAA,MAAM,MAAM,2CAAwC,CAAC;AACrD,KAAK,MAAM,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC;AACxC,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"Button.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/ui/Button.svelte.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EACV,oBAAoB,EACpB,oBAAoB,EACrB,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGrE,iCAAiC;AACjC,MAAM,WAAW,KAAM,SAAQ,IAAI,CAAC,oBAAoB,EAAE,OAAO,GAAG,MAAM,CAAC;IACzE,iEAAiE;IACjE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC;IAClC,qBAAqB;IACrB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,mBAAmB;IACnB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,wBAAwB;IACxB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,oBAAoB;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,MAAM,CAAC,EAAE,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IACxC,6EAA6E;IAC7E,GAAG,CAAC,EAAE,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAClC,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,oBAAoB,CAAC,UAAU,CAAC,CAAC;CAC7C;AAoGD,QAAA,MAAM,MAAM,2CAAwC,CAAC;AACrD,KAAK,MAAM,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC;AACxC,eAAe,MAAM,CAAC"}
@@ -52,15 +52,24 @@ const {
52
52
  </div>
53
53
 
54
54
  <style>
55
+ /*
56
+ * Themeable look (app-level custom properties; defaults keep the stock look):
57
+ * --smrt-card-border edge of default and elevated cards
58
+ * --smrt-card-background card fill
59
+ * --smrt-card-shadow shadow of default cards (elevated keeps its elevation)
60
+ * --smrt-card-divider rule between the header/footer and the content
61
+ * The `outlined` variant always draws its outline.
62
+ */
55
63
  .card {
56
- background: var(--smrt-color-surface);
64
+ background: var(--smrt-card-background, var(--smrt-color-surface));
57
65
  border-radius: var(--smrt-radius-medium);
58
66
  transition: all var(--smrt-duration-short2) var(--smrt-easing-standard);
59
67
  }
60
68
 
61
69
  /* Variants */
62
70
  .default {
63
- border: 1px solid var(--smrt-color-outline-variant);
71
+ border: var(--smrt-card-border, 1px solid var(--smrt-color-outline-variant));
72
+ box-shadow: var(--smrt-card-shadow, none);
64
73
  }
65
74
 
66
75
  .outlined {
@@ -68,7 +77,7 @@ const {
68
77
  }
69
78
 
70
79
  .elevated {
71
- border: 1px solid var(--smrt-color-outline-variant);
80
+ border: var(--smrt-card-border, 1px solid var(--smrt-color-outline-variant));
72
81
  box-shadow: var(--smrt-elevation-2);
73
82
  }
74
83
 
@@ -98,7 +107,7 @@ const {
98
107
  /* Header */
99
108
  .card-header {
100
109
  padding: var(--smrt-spacing-6);
101
- border-bottom: 1px solid var(--smrt-color-outline-variant);
110
+ border-bottom: var(--smrt-card-divider, 1px solid var(--smrt-color-outline-variant));
102
111
  }
103
112
 
104
113
  .padding-sm .card-header {
@@ -112,7 +121,7 @@ const {
112
121
  /* Footer */
113
122
  .card-footer {
114
123
  padding: var(--smrt-spacing-6);
115
- border-top: 1px solid var(--smrt-color-outline-variant);
124
+ border-top: var(--smrt-card-divider, 1px solid var(--smrt-color-outline-variant));
116
125
  background: var(--smrt-color-surface-container-low);
117
126
  border-bottom-left-radius: var(--smrt-radius-medium);
118
127
  border-bottom-right-radius: var(--smrt-radius-medium);
@@ -18,6 +18,14 @@ export interface Props {
18
18
  label?: string;
19
19
  /** Custom trigger content. */
20
20
  trigger?: Snippet;
21
+ /** Accessible name of the trigger button when it shows only an icon (`aria-label`); omit to use its visible content (an icon trigger then falls back to `label`). */
22
+ triggerLabel?: string;
23
+ /**
24
+ * Trigger look: `button` (bordered, the default) or `icon`, a borderless 44px
25
+ * round icon button (a "more" or row-actions menu) that takes the surrounding
26
+ * text colour.
27
+ */
28
+ variant?: 'button' | 'icon';
21
29
  /** Menu items. */
22
30
  items: MenuItem[];
23
31
  /** Menu placement relative to the trigger. */
@@ -31,6 +39,8 @@ export interface Props {
31
39
  const {
32
40
  label,
33
41
  trigger,
42
+ triggerLabel,
43
+ variant = 'button',
34
44
  items,
35
45
  placement = 'bottom-start',
36
46
  onselect,
@@ -39,6 +49,16 @@ const {
39
49
 
40
50
  const instanceId = $props.id();
41
51
  const menuId = `smrt-menu-${instanceId}`;
52
+
53
+ // An icon trigger shows no text, so without triggerLabel or label the button
54
+ // has no accessible name.
55
+ $effect(() => {
56
+ if (variant === 'icon' && !triggerLabel && !label) {
57
+ console.warn(
58
+ '[Dropdown] variant="icon" needs triggerLabel (or label): the trigger button has no accessible name.',
59
+ );
60
+ }
61
+ });
42
62
  let open = $state(false);
43
63
  let wrapEl = $state<HTMLElement | null>(null);
44
64
  let triggerEl = $state<HTMLButtonElement | null>(null);
@@ -132,6 +152,8 @@ $effect(() => {
132
152
  bind:this={triggerEl}
133
153
  type="button"
134
154
  class="dropdown__trigger"
155
+ class:dropdown__trigger--icon={variant === 'icon'}
156
+ aria-label={triggerLabel ?? (variant === 'icon' ? label : undefined)}
135
157
  aria-haspopup="menu"
136
158
  aria-expanded={open}
137
159
  aria-controls={open ? menuId : undefined}
@@ -160,7 +182,10 @@ $effect(() => {
160
182
  tabindex={-1}
161
183
  onclick={() => selectItem(item)}
162
184
  >
163
- {item.label}
185
+ {#if item.icon}
186
+ <span class="dropdown__item-icon" aria-hidden="true">{@render item.icon()}</span>
187
+ {/if}
188
+ <span class="dropdown__item-label">{item.label}</span>
164
189
  </button>
165
190
  {/each}
166
191
  </div>
@@ -187,6 +212,24 @@ $effect(() => {
187
212
  outline: 2px solid var(--smrt-color-primary);
188
213
  outline-offset: 2px;
189
214
  }
215
+ /* Icon trigger: a 44px round "more" button in the surrounding text colour. */
216
+ .dropdown__trigger--icon {
217
+ display: inline-flex;
218
+ align-items: center;
219
+ justify-content: center;
220
+ box-sizing: border-box;
221
+ inline-size: 2.75rem;
222
+ block-size: 2.75rem;
223
+ padding: 0;
224
+ border: none;
225
+ border-radius: var(--smrt-radius-full, 999px);
226
+ background: transparent;
227
+ color: inherit;
228
+ }
229
+ .dropdown__trigger--icon:hover:not(:disabled),
230
+ .dropdown__trigger--icon[aria-expanded='true'] {
231
+ background: color-mix(in srgb, currentColor 10%, transparent);
232
+ }
190
233
  .dropdown__trigger:disabled {
191
234
  opacity: 0.5;
192
235
  cursor: not-allowed;
@@ -226,6 +269,12 @@ $effect(() => {
226
269
 
227
270
  .dropdown__item {
228
271
  appearance: none;
272
+ display: flex;
273
+ align-items: center;
274
+ gap: var(--smrt-spacing-2, 8px);
275
+ /* A touch target: menu rows are at least 44px tall. */
276
+ min-block-size: 2.75rem;
277
+ box-sizing: border-box;
229
278
  font: inherit;
230
279
  text-align: left;
231
280
  cursor: pointer;
@@ -236,6 +285,11 @@ $effect(() => {
236
285
  color: var(--smrt-color-on-surface);
237
286
  white-space: nowrap;
238
287
  }
288
+ .dropdown__item-icon {
289
+ display: inline-flex;
290
+ flex: none;
291
+ color: var(--smrt-color-on-surface-variant);
292
+ }
239
293
  .dropdown__item:hover:not(:disabled) {
240
294
  background: var(--smrt-color-surface-container-high);
241
295
  }
@@ -14,6 +14,14 @@ export interface Props {
14
14
  label?: string;
15
15
  /** Custom trigger content. */
16
16
  trigger?: Snippet;
17
+ /** Accessible name of the trigger button when it shows only an icon (`aria-label`); omit to use its visible content (an icon trigger then falls back to `label`). */
18
+ triggerLabel?: string;
19
+ /**
20
+ * Trigger look: `button` (bordered, the default) or `icon`, a borderless 44px
21
+ * round icon button (a "more" or row-actions menu) that takes the surrounding
22
+ * text colour.
23
+ */
24
+ variant?: 'button' | 'icon';
17
25
  /** Menu items. */
18
26
  items: MenuItem[];
19
27
  /** Menu placement relative to the trigger. */
@@ -1 +1 @@
1
- {"version":3,"file":"Dropdown.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/ui/Dropdown.svelte.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,KAAK,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAGvE,MAAM,WAAW,KAAK;IACpB,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8BAA8B;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,kBAAkB;IAClB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,8CAA8C;IAC9C,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,2BAA2B;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAgID,QAAA,MAAM,QAAQ,2CAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
1
+ {"version":3,"file":"Dropdown.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/ui/Dropdown.svelte.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,KAAK,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAGvE,MAAM,WAAW,KAAK;IACpB,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8BAA8B;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,qKAAqK;IACrK,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,OAAO,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC5B,kBAAkB;IAClB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,8CAA8C;IAC9C,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,2BAA2B;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA+ID,QAAA,MAAM,QAAQ,2CAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
@@ -0,0 +1,139 @@
1
+ <script lang="ts">
2
+ /**
3
+ * IconToggle — an icon-only toggle button for dense filter and view-switcher
4
+ * rows. The icon is the `children` snippet (any icon set); `label` is both the
5
+ * accessible name and the tooltip text. `pressed` is reflected with
6
+ * `aria-pressed`, so a row of them is a one-of-many (or a many) toggle group;
7
+ * the owner decides what pressing means (this component keeps no state). An
8
+ * optional `count` shows as a small badge and is part of the accessible name
9
+ * and tooltip. The hit area is at least 44px square.
10
+ */
11
+ import type { Snippet } from 'svelte';
12
+ import type { HTMLButtonAttributes } from 'svelte/elements';
13
+ import type { TooltipPlacement } from '../../types-generic';
14
+ import Tooltip from './Tooltip.svelte';
15
+
16
+ export interface Props
17
+ extends Omit<
18
+ HTMLButtonAttributes,
19
+ 'class' | 'children' | 'aria-label' | 'aria-pressed' | 'title'
20
+ > {
21
+ /** Accessible name and tooltip text (what the button does or filters). */
22
+ label: string;
23
+ /** Whether the toggle is on (`aria-pressed`). */
24
+ pressed?: boolean;
25
+ /** Optional count badge; included in the accessible name and tooltip. */
26
+ count?: number;
27
+ /** A CSS colour (a token such as `var(--status-draft)` or a value) shown when the toggle is pressed. */
28
+ tone?: string;
29
+ /** The icon. Decorative: it is hidden from assistive technology. */
30
+ children: Snippet;
31
+ /** Where the tooltip opens. */
32
+ tooltipPlacement?: TooltipPlacement;
33
+ /** Additional CSS class names. */
34
+ class?: string;
35
+ }
36
+
37
+ const {
38
+ label,
39
+ pressed = false,
40
+ count,
41
+ tone,
42
+ children,
43
+ tooltipPlacement = 'top',
44
+ disabled = false,
45
+ type = 'button',
46
+ class: className = '',
47
+ ...rest
48
+ }: Props = $props();
49
+
50
+ const name = $derived(count === undefined ? label : `${label} (${count})`);
51
+ </script>
52
+
53
+ <Tooltip text={name} placement={tooltipPlacement}>
54
+ <button
55
+ {...rest}
56
+ {type}
57
+ {disabled}
58
+ class="icon-toggle {className}"
59
+ class:pressed
60
+ class:toned={tone !== undefined}
61
+ style:--icon-toggle-tone={tone}
62
+ aria-pressed={pressed}
63
+ aria-label={name}
64
+ >
65
+ <span class="icon" aria-hidden="true">{@render children()}</span>
66
+ {#if count !== undefined}<span class="count" aria-hidden="true">{count}</span>{/if}
67
+ </button>
68
+ </Tooltip>
69
+
70
+ <style>
71
+ .icon-toggle {
72
+ position: relative;
73
+ display: inline-flex;
74
+ align-items: center;
75
+ justify-content: center;
76
+ box-sizing: border-box;
77
+ min-width: 2.75rem;
78
+ min-height: 2.75rem;
79
+ padding: 0;
80
+ border: 1px solid var(--smrt-color-outline-variant, var(--smrt-color-outline));
81
+ border-radius: var(--smrt-radius-full, 999px);
82
+ background: var(--smrt-color-surface-container, var(--smrt-color-surface));
83
+ color: var(--smrt-color-on-surface);
84
+ font: inherit;
85
+ cursor: pointer;
86
+ }
87
+ .icon-toggle:hover:not(:disabled) {
88
+ background: var(--smrt-color-surface-container-high, var(--smrt-color-surface-container));
89
+ }
90
+ /* Pressed is not told by colour alone (WCAG 1.4.1): it also draws a 2px ring
91
+ in its text colour, where unpressed has a 1px outline. */
92
+ .icon-toggle.pressed {
93
+ border-color: transparent;
94
+ background: var(--smrt-color-primary-container, var(--smrt-color-secondary-container));
95
+ color: var(--smrt-color-on-primary-container, var(--smrt-color-on-secondary-container));
96
+ box-shadow: inset 0 0 0 2px currentColor;
97
+ }
98
+ /* A toned toggle is neutral until pressed, then wears its tone. */
99
+ .icon-toggle.toned {
100
+ color: var(--smrt-color-on-surface-variant, var(--smrt-color-on-surface));
101
+ }
102
+ .icon-toggle.toned.pressed {
103
+ background: color-mix(in srgb, var(--icon-toggle-tone) 16%, var(--smrt-color-surface));
104
+ color: var(--icon-toggle-tone);
105
+ box-shadow: inset 0 0 0 2px var(--icon-toggle-tone);
106
+ }
107
+ .icon-toggle:focus-visible {
108
+ outline: 2px solid var(--smrt-color-primary);
109
+ outline-offset: 2px;
110
+ }
111
+ .icon-toggle:disabled {
112
+ opacity: 0.5;
113
+ cursor: not-allowed;
114
+ }
115
+ .icon {
116
+ display: inline-flex;
117
+ }
118
+ .count {
119
+ position: absolute;
120
+ z-index: 2;
121
+ top: -0.3rem;
122
+ right: -0.3rem;
123
+ box-sizing: border-box;
124
+ min-width: 1.15rem;
125
+ padding: 0 0.3rem;
126
+ border-radius: var(--smrt-radius-full, 999px);
127
+ /* Outlined, not filled: a surface-coloured face (so the button's edge does
128
+ not show through where the badge overlaps it), a 1px theme outline and
129
+ neutral text, readable on light and dark themes alike. */
130
+ border: 1px solid var(--smrt-color-outline);
131
+ background: var(--smrt-color-surface);
132
+ color: var(--smrt-color-on-surface-variant);
133
+ font-size: 0.68rem;
134
+ font-weight: 700;
135
+ line-height: 1.15rem;
136
+ text-align: center;
137
+ font-variant-numeric: tabular-nums;
138
+ }
139
+ </style>
@@ -0,0 +1,32 @@
1
+ /**
2
+ * IconToggle — an icon-only toggle button for dense filter and view-switcher
3
+ * rows. The icon is the `children` snippet (any icon set); `label` is both the
4
+ * accessible name and the tooltip text. `pressed` is reflected with
5
+ * `aria-pressed`, so a row of them is a one-of-many (or a many) toggle group;
6
+ * the owner decides what pressing means (this component keeps no state). An
7
+ * optional `count` shows as a small badge and is part of the accessible name
8
+ * and tooltip. The hit area is at least 44px square.
9
+ */
10
+ import type { Snippet } from 'svelte';
11
+ import type { HTMLButtonAttributes } from 'svelte/elements';
12
+ import type { TooltipPlacement } from '../../types-generic';
13
+ export interface Props extends Omit<HTMLButtonAttributes, 'class' | 'children' | 'aria-label' | 'aria-pressed' | 'title'> {
14
+ /** Accessible name and tooltip text (what the button does or filters). */
15
+ label: string;
16
+ /** Whether the toggle is on (`aria-pressed`). */
17
+ pressed?: boolean;
18
+ /** Optional count badge; included in the accessible name and tooltip. */
19
+ count?: number;
20
+ /** A CSS colour (a token such as `var(--status-draft)` or a value) shown when the toggle is pressed. */
21
+ tone?: string;
22
+ /** The icon. Decorative: it is hidden from assistive technology. */
23
+ children: Snippet;
24
+ /** Where the tooltip opens. */
25
+ tooltipPlacement?: TooltipPlacement;
26
+ /** Additional CSS class names. */
27
+ class?: string;
28
+ }
29
+ declare const IconToggle: import("svelte").Component<Props, {}, "">;
30
+ type IconToggle = ReturnType<typeof IconToggle>;
31
+ export default IconToggle;
32
+ //# sourceMappingURL=IconToggle.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IconToggle.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/ui/IconToggle.svelte.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAI5D,MAAM,WAAW,KACf,SAAQ,IAAI,CACV,oBAAoB,EACpB,OAAO,GAAG,UAAU,GAAG,YAAY,GAAG,cAAc,GAAG,OAAO,CAC/D;IACD,0EAA0E;IAC1E,KAAK,EAAE,MAAM,CAAC;IACd,iDAAiD;IACjD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,yEAAyE;IACzE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wGAAwG;IACxG,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oEAAoE;IACpE,QAAQ,EAAE,OAAO,CAAC;IAClB,+BAA+B;IAC/B,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,kCAAkC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAoCD,QAAA,MAAM,UAAU,2CAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
@@ -0,0 +1,56 @@
1
+ <script lang="ts">
2
+ /**
3
+ * IconToggleGroup — joins a row of `IconToggle`s into one labelled group: a
4
+ * single segmented strip with shared edges and rounded ends, exposed to
5
+ * assistive technology as `role="group"` named by `label` ("Filter by type").
6
+ * It adds no behaviour; each toggle keeps its own `pressed` state and
7
+ * `onclick`, so it works for one-of-many and many-of-many alike. Put one group
8
+ * per kind of filter and lay the groups out yourself.
9
+ */
10
+ import type { Snippet } from 'svelte';
11
+
12
+ export interface Props {
13
+ /** Names the group for assistive technology (what these toggles filter). */
14
+ label: string;
15
+ /** The `IconToggle`s. */
16
+ children: Snippet;
17
+ /** Additional CSS class names. */
18
+ class?: string;
19
+ }
20
+
21
+ const { label, children, class: className = '' }: Props = $props();
22
+ </script>
23
+
24
+ <div class="icon-toggle-group {className}" role="group" aria-label={label}>
25
+ {@render children()}
26
+ </div>
27
+
28
+ <style>
29
+ .icon-toggle-group {
30
+ display: inline-flex;
31
+ flex-wrap: wrap;
32
+ max-width: 100%;
33
+ isolation: isolate;
34
+ }
35
+ /* Shared edges: one border between neighbours, square inside, round ends.
36
+ Each toggle sits in tooltip wrappers, so the rules reach it by descendant. */
37
+ .icon-toggle-group :global(.icon-toggle) {
38
+ border-radius: 0;
39
+ }
40
+ .icon-toggle-group > :global(* + *) {
41
+ margin-inline-start: -1px;
42
+ }
43
+ .icon-toggle-group > :global(:first-child .icon-toggle) {
44
+ border-start-start-radius: var(--smrt-radius-full, 999px);
45
+ border-end-start-radius: var(--smrt-radius-full, 999px);
46
+ }
47
+ .icon-toggle-group > :global(:last-child .icon-toggle) {
48
+ border-start-end-radius: var(--smrt-radius-full, 999px);
49
+ border-end-end-radius: var(--smrt-radius-full, 999px);
50
+ }
51
+ /* The pressed one sits above its neighbours so its tint is not clipped by them. */
52
+ .icon-toggle-group :global(.icon-toggle.pressed),
53
+ .icon-toggle-group :global(.icon-toggle:focus-visible) {
54
+ z-index: 1;
55
+ }
56
+ </style>
@@ -0,0 +1,21 @@
1
+ /**
2
+ * IconToggleGroup — joins a row of `IconToggle`s into one labelled group: a
3
+ * single segmented strip with shared edges and rounded ends, exposed to
4
+ * assistive technology as `role="group"` named by `label` ("Filter by type").
5
+ * It adds no behaviour; each toggle keeps its own `pressed` state and
6
+ * `onclick`, so it works for one-of-many and many-of-many alike. Put one group
7
+ * per kind of filter and lay the groups out yourself.
8
+ */
9
+ import type { Snippet } from 'svelte';
10
+ export interface Props {
11
+ /** Names the group for assistive technology (what these toggles filter). */
12
+ label: string;
13
+ /** The `IconToggle`s. */
14
+ children: Snippet;
15
+ /** Additional CSS class names. */
16
+ class?: string;
17
+ }
18
+ declare const IconToggleGroup: import("svelte").Component<Props, {}, "">;
19
+ type IconToggleGroup = ReturnType<typeof IconToggleGroup>;
20
+ export default IconToggleGroup;
21
+ //# sourceMappingURL=IconToggleGroup.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IconToggleGroup.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/ui/IconToggleGroup.svelte.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAGtC,MAAM,WAAW,KAAK;IACpB,4EAA4E;IAC5E,KAAK,EAAE,MAAM,CAAC;IACd,yBAAyB;IACzB,QAAQ,EAAE,OAAO,CAAC;IAClB,kCAAkC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAiBD,QAAA,MAAM,eAAe,2CAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
@@ -240,7 +240,7 @@ const isLastPage = $derived(currentPage === totalPages);
240
240
 
241
241
  .page-link.current {
242
242
  background: var(--smrt-color-primary, #005ac1);
243
- color: white;
243
+ color: var(--smrt-color-on-primary, white);
244
244
  cursor: default;
245
245
  }
246
246
 
@@ -6,6 +6,14 @@ export interface Props {
6
6
  label: string;
7
7
  /** Content rendered inside the trigger button in place of the label text. */
8
8
  trigger?: Snippet;
9
+ /** Accessible name of the trigger button when it shows only an icon (`aria-label`); omit to use its visible content (an icon trigger then falls back to `label`). */
10
+ triggerLabel?: string;
11
+ /**
12
+ * Trigger look: `button` (bordered, the default) or `icon`, a borderless
13
+ * 44px round icon button that takes the surrounding text colour (set the
14
+ * colour on the popover's `class`).
15
+ */
16
+ variant?: 'button' | 'icon';
9
17
  /** The popover content. */
10
18
  children?: Snippet;
11
19
  /** Whether the popover is visible. */
@@ -25,6 +33,8 @@ export interface Props {
25
33
  let {
26
34
  label,
27
35
  trigger,
36
+ triggerLabel,
37
+ variant = 'button',
28
38
  children,
29
39
  open = $bindable(false),
30
40
  disabled = false,
@@ -74,7 +84,9 @@ $effect(() => {
74
84
  bind:this={triggerEl}
75
85
  type="button"
76
86
  class="popover__trigger"
87
+ class:popover__trigger--icon={variant === 'icon'}
77
88
  aria-haspopup="dialog"
89
+ aria-label={triggerLabel ?? (variant === 'icon' ? label : undefined)}
78
90
  aria-expanded={open}
79
91
  aria-controls={open ? panelId : undefined}
80
92
  {disabled}
@@ -103,7 +115,11 @@ $effect(() => {
103
115
  .popover__trigger { appearance: none; padding: var(--smrt-spacing-2) var(--smrt-spacing-3); border: 1px solid var(--smrt-color-outline); border-radius: var(--smrt-radius-medium); background: var(--smrt-color-surface); color: var(--smrt-color-on-surface); font: inherit; cursor: pointer; }
104
116
  .popover__trigger:focus-visible, .popover__panel:focus-visible { outline: 2px solid var(--smrt-color-primary); outline-offset: 2px; }
105
117
  .popover__trigger:disabled { opacity: .5; cursor: not-allowed; }
106
- .popover__panel { position: absolute; z-index: var(--smrt-z-index-popover, 1000); min-width: 16rem; padding: var(--smrt-spacing-4); border: 1px solid var(--smrt-color-outline-variant); border-radius: var(--smrt-radius-large); background: var(--smrt-color-surface-container); color: var(--smrt-color-on-surface); box-shadow: var(--smrt-elevation-3); }
118
+ /* Icon trigger: a 44px round glyph in the surrounding colour. Hover and open nudge the glyph; focus keeps the standard primary ring (a soft ring in the glyph's colour fell under 3:1 on ordinary surfaces). */
119
+ .popover__trigger--icon { display: inline-flex; align-items: center; justify-content: center; box-sizing: border-box; inline-size: 2.75rem; block-size: 2.75rem; padding: 0; border: none; border-radius: var(--smrt-radius-full, 999px); background: transparent; color: inherit; }
120
+ .popover__trigger--icon:hover:not(:disabled), .popover__trigger--icon[aria-expanded='true'] { background: transparent; color: color-mix(in srgb, currentColor 80%, var(--smrt-color-on-surface)); }
121
+ .popover__trigger--icon:focus-visible { background: transparent; }
122
+ .popover__panel { position: absolute; z-index: var(--smrt-z-index-popover, 1000); box-sizing: border-box; inline-size: var(--smrt-popover-panel-width, auto); min-width: min(16rem, calc(100vw - 2rem)); max-width: calc(100vw - 2rem); padding: var(--smrt-popover-panel-padding, var(--smrt-spacing-4)); border: 1px solid var(--smrt-color-outline-variant); border-radius: var(--smrt-radius-large); background: var(--smrt-color-surface-container); color: var(--smrt-color-on-surface); box-shadow: var(--smrt-elevation-3); }
107
123
  .popover__panel--bottom-start { top: calc(100% + var(--smrt-spacing-1)); left: 0; }
108
124
  .popover__panel--bottom-end { top: calc(100% + var(--smrt-spacing-1)); right: 0; }
109
125
  .popover__panel--top-start { bottom: calc(100% + var(--smrt-spacing-1)); left: 0; }
@@ -4,6 +4,14 @@ export interface Props {
4
4
  label: string;
5
5
  /** Content rendered inside the trigger button in place of the label text. */
6
6
  trigger?: Snippet;
7
+ /** Accessible name of the trigger button when it shows only an icon (`aria-label`); omit to use its visible content (an icon trigger then falls back to `label`). */
8
+ triggerLabel?: string;
9
+ /**
10
+ * Trigger look: `button` (bordered, the default) or `icon`, a borderless
11
+ * 44px round icon button that takes the surrounding text colour (set the
12
+ * colour on the popover's `class`).
13
+ */
14
+ variant?: 'button' | 'icon';
7
15
  /** The popover content. */
8
16
  children?: Snippet;
9
17
  /** Whether the popover is visible. */
@@ -1 +1 @@
1
- {"version":3,"file":"Popover.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/ui/Popover.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,OAAO,EAAQ,MAAM,QAAQ,CAAC;AAG5C,MAAM,WAAW,KAAK;IACpB,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAC;IACd,6EAA6E;IAC7E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,2BAA2B;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sCAAsC;IACtC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,kCAAkC;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uDAAuD;IACvD,SAAS,CAAC,EAAE,cAAc,GAAG,YAAY,GAAG,WAAW,GAAG,SAAS,CAAC;IACpE,sDAAsD;IACtD,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,yCAAyC;IACzC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,kCAAkC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAmED,QAAA,MAAM,OAAO,+CAAwC,CAAC;AACtD,KAAK,OAAO,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC;AAC1C,eAAe,OAAO,CAAC"}
1
+ {"version":3,"file":"Popover.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/ui/Popover.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,OAAO,EAAQ,MAAM,QAAQ,CAAC;AAG5C,MAAM,WAAW,KAAK;IACpB,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAC;IACd,6EAA6E;IAC7E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,qKAAqK;IACrK,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,OAAO,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC5B,2BAA2B;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sCAAsC;IACtC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,kCAAkC;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uDAAuD;IACvD,SAAS,CAAC,EAAE,cAAc,GAAG,YAAY,GAAG,WAAW,GAAG,SAAS,CAAC;IACpE,sDAAsD;IACtD,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,yCAAyC;IACzC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,kCAAkC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAqED,QAAA,MAAM,OAAO,+CAAwC,CAAC;AACtD,KAAK,OAAO,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC;AAC1C,eAAe,OAAO,CAAC"}
@@ -0,0 +1,59 @@
1
+ import { render, screen } from '@testing-library/svelte';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { createRawSnippet } from 'svelte';
4
+ import { describe, expect, it, vi } from 'vitest';
5
+ import { expectNoA11yViolations } from '../../../test-support/a11y';
6
+ import IconToggle from '../IconToggle.svelte';
7
+ const icon = createRawSnippet(() => ({
8
+ render: () => '<svg viewBox="0 0 24 24" width="18" height="18"></svg>',
9
+ }));
10
+ describe('IconToggle', () => {
11
+ it('is a labelled toggle button reflecting pressed', () => {
12
+ render(IconToggle, {
13
+ props: { label: 'Grid', pressed: true, children: icon },
14
+ });
15
+ const button = screen.getByRole('button', { name: 'Grid' });
16
+ expect(button).toHaveAttribute('aria-pressed', 'true');
17
+ expect(button).toHaveAttribute('type', 'button');
18
+ });
19
+ it('shows a count badge that is part of the name and the tooltip', () => {
20
+ render(IconToggle, {
21
+ props: { label: 'Draft', count: 12, children: icon },
22
+ });
23
+ const button = screen.getByRole('button', { name: 'Draft (12)' });
24
+ expect(button).toHaveAttribute('aria-pressed', 'false');
25
+ expect(screen.getByRole('tooltip')).toHaveTextContent('Draft (12)');
26
+ expect(button.querySelector('.count')).toHaveTextContent('12');
27
+ });
28
+ it('keeps no state of its own: it calls onclick and the owner decides', async () => {
29
+ const onclick = vi.fn();
30
+ render(IconToggle, {
31
+ props: { label: 'Cards', pressed: false, onclick, children: icon },
32
+ });
33
+ await userEvent.click(screen.getByRole('button', { name: 'Cards' }));
34
+ expect(onclick).toHaveBeenCalledTimes(1);
35
+ expect(screen.getByRole('button', { name: 'Cards' })).toHaveAttribute('aria-pressed', 'false');
36
+ });
37
+ it('is axe-clean', async () => {
38
+ const { container } = render(IconToggle, {
39
+ props: { label: 'Grid', count: 3, children: icon },
40
+ });
41
+ await expectNoA11yViolations(container);
42
+ });
43
+ });
44
+ describe('IconToggle tone', () => {
45
+ it('carries its tone as a custom property and a toned class', () => {
46
+ render(IconToggle, {
47
+ props: { label: 'Draft', tone: 'var(--status-draft)', children: icon },
48
+ });
49
+ const button = screen.getByRole('button', { name: 'Draft' });
50
+ expect(button).toHaveClass('toned');
51
+ expect(button.style.getPropertyValue('--icon-toggle-tone')).toBe('var(--status-draft)');
52
+ });
53
+ it('has no tone styling unless asked', () => {
54
+ render(IconToggle, { props: { label: 'Grid', children: icon } });
55
+ const button = screen.getByRole('button', { name: 'Grid' });
56
+ expect(button).not.toHaveClass('toned');
57
+ expect(button.style.getPropertyValue('--icon-toggle-tone')).toBe('');
58
+ });
59
+ });