@lifesg/react-design-system 4.0.0-alpha.12 → 4.0.0-alpha.14

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 (323) hide show
  1. package/breadcrumb/breadcrumb.js +1 -1
  2. package/breadcrumb/breadcrumb.js.map +1 -1
  3. package/breadcrumb/breadcrumb.styles.d.ts +0 -8
  4. package/breadcrumb/breadcrumb.styles.js +1 -1
  5. package/breadcrumb/breadcrumb.styles.js.map +1 -1
  6. package/breadcrumb/breadcrumb.styles_1blzn8n.css +7 -0
  7. package/breadcrumb/types.d.ts +2 -13
  8. package/cjs/breadcrumb/breadcrumb.js +1 -1
  9. package/cjs/breadcrumb/breadcrumb.js.map +1 -1
  10. package/cjs/breadcrumb/breadcrumb.styles.js +1 -1
  11. package/cjs/breadcrumb/breadcrumb.styles.js.map +1 -1
  12. package/cjs/breadcrumb/breadcrumb.styles_1blzn8n.css +7 -0
  13. package/cjs/drawer/drawer.js +1 -1
  14. package/cjs/drawer/drawer.js.map +1 -1
  15. package/cjs/drawer/drawer.styles.js +1 -1
  16. package/cjs/drawer/drawer.styles.js.map +1 -1
  17. package/{drawer/drawer.styles_94hx3y.css → cjs/drawer/drawer.styles_a4bwe0.css} +6 -3
  18. package/cjs/external/dompurify/dist/purify.es.js +4 -2
  19. package/cjs/external/dompurify/dist/purify.es.js.map +1 -1
  20. package/cjs/file-upload/file-list/file-list.js +1 -1
  21. package/cjs/file-upload/file-list/file-list.js.map +1 -1
  22. package/cjs/file-upload/file-list/file-list.styles.js +1 -1
  23. package/cjs/file-upload/file-list/file-list.styles.js.map +1 -1
  24. package/cjs/file-upload/file-list/file-list.styles_1xbypnh.css +2 -0
  25. package/cjs/file-upload/file-list-item/file-item-actions.js +2 -0
  26. package/cjs/file-upload/file-list-item/file-item-actions.js.map +1 -0
  27. package/cjs/file-upload/file-list-item/file-item-actions.styles.js +2 -0
  28. package/cjs/file-upload/file-list-item/file-item-actions.styles.js.map +1 -0
  29. package/cjs/file-upload/file-list-item/file-item-actions.styles_1quulgy.css +8 -0
  30. package/cjs/file-upload/file-list-item/file-item-details.js +2 -0
  31. package/cjs/file-upload/file-list-item/file-item-details.js.map +1 -0
  32. package/cjs/file-upload/file-list-item/file-item-details.styles.js +2 -0
  33. package/cjs/file-upload/file-list-item/file-item-details.styles.js.map +1 -0
  34. package/cjs/file-upload/file-list-item/file-item-details.styles_1ce9akx.css +7 -0
  35. package/cjs/file-upload/file-list-item/file-list-item.js +1 -1
  36. package/cjs/file-upload/file-list-item/file-list-item.js.map +1 -1
  37. package/cjs/file-upload/file-list-item/file-list-item.styles.js +1 -1
  38. package/cjs/file-upload/file-list-item/file-list-item.styles.js.map +1 -1
  39. package/cjs/file-upload/file-list-item/file-list-item.styles_x3a500.css +10 -0
  40. package/cjs/file-upload/file-list-item/use-truncated-name.js +2 -0
  41. package/cjs/file-upload/file-list-item/use-truncated-name.js.map +1 -0
  42. package/cjs/local-nav/local-nav-dropdown/local-nav-dropdown.js +1 -1
  43. package/cjs/local-nav/local-nav-dropdown/local-nav-dropdown.js.map +1 -1
  44. package/cjs/local-nav/local-nav-dropdown/local-nav-dropdown.styles.js +1 -1
  45. package/cjs/local-nav/local-nav-dropdown/local-nav-dropdown.styles.js.map +1 -1
  46. package/{local-nav/local-nav-dropdown/local-nav-dropdown.styles_uhd4xl.css → cjs/local-nav/local-nav-dropdown/local-nav-dropdown.styles_kpel1m.css} +9 -6
  47. package/cjs/local-nav/local-nav-menu/local-nav-menu.js +1 -1
  48. package/cjs/local-nav/local-nav-menu/local-nav-menu.js.map +1 -1
  49. package/cjs/local-nav/local-nav-menu/local-nav-menu.styles.js +1 -1
  50. package/cjs/local-nav/local-nav-menu/local-nav-menu.styles.js.map +1 -1
  51. package/cjs/local-nav/local-nav-menu/local-nav-menu.styles_67fcyc.css +8 -0
  52. package/cjs/menu/menu-content.js +1 -1
  53. package/cjs/menu/menu-content.js.map +1 -1
  54. package/cjs/menu/menu-content.styles.js +1 -1
  55. package/cjs/menu/menu-content.styles.js.map +1 -1
  56. package/cjs/menu/menu-content.styles_1r8oxtk.css +5 -0
  57. package/cjs/menu/menu-item.styles.js +1 -1
  58. package/cjs/menu/menu-item.styles.js.map +1 -1
  59. package/cjs/menu/{menu-item.styles_liyed8.css → menu-item.styles_eut2j9.css} +1 -1
  60. package/cjs/menu/menu-link.styles.js +1 -1
  61. package/cjs/menu/menu-link.styles.js.map +1 -1
  62. package/cjs/menu/menu-link.styles_rng7n7.css +2 -0
  63. package/cjs/menu/menu-section.js +1 -1
  64. package/cjs/menu/menu-section.js.map +1 -1
  65. package/cjs/menu/menu-section.styles.js +1 -1
  66. package/cjs/menu/menu-section.styles.js.map +1 -1
  67. package/cjs/menu/menu-section.styles_1n0ppxl.css +5 -0
  68. package/cjs/modal-v2/slots/slot-styles.js +1 -1
  69. package/cjs/modal-v2/slots/slot-styles.js.map +1 -1
  70. package/cjs/modal-v2/slots/{slot-styles_ayb9il.css → slot-styles_ygu6zc.css} +1 -1
  71. package/cjs/navbar/navbar-items.js +1 -1
  72. package/cjs/navbar/navbar-items.js.map +1 -1
  73. package/cjs/notification-banner/notification-banner-hoc.js +1 -1
  74. package/cjs/notification-banner/notification-banner-hoc.js.map +1 -1
  75. package/cjs/pagination/pagination.js +1 -1
  76. package/cjs/pagination/pagination.js.map +1 -1
  77. package/cjs/shared/dropdown-list/dropdown-label.js +1 -1
  78. package/cjs/shared/dropdown-list/dropdown-label.js.map +1 -1
  79. package/cjs/shared/fade-wrapper/fade-wrapper.js +1 -1
  80. package/cjs/shared/fade-wrapper/fade-wrapper.js.map +1 -1
  81. package/cjs/theme/tokens/colour.js +1 -1
  82. package/cjs/theme/tokens/colour.js.map +1 -1
  83. package/cjs/theme/tokens/radius.js +1 -1
  84. package/cjs/theme/tokens/radius.js.map +1 -1
  85. package/codemods/run-codemod.js +8 -2
  86. package/codemods/run-codemod.js.map +1 -1
  87. package/codemods/run-codemod.ts +13 -4
  88. package/drawer/drawer.d.ts +1 -1
  89. package/drawer/drawer.js +1 -1
  90. package/drawer/drawer.js.map +1 -1
  91. package/drawer/drawer.styles.d.ts +3 -0
  92. package/drawer/drawer.styles.js +1 -1
  93. package/drawer/drawer.styles.js.map +1 -1
  94. package/{cjs/drawer/drawer.styles_94hx3y.css → drawer/drawer.styles_a4bwe0.css} +6 -3
  95. package/drawer/types.d.ts +7 -0
  96. package/external/dompurify/dist/purify.es.js +4 -2
  97. package/external/dompurify/dist/purify.es.js.map +1 -1
  98. package/file-upload/file-list/file-list.js +1 -1
  99. package/file-upload/file-list/file-list.js.map +1 -1
  100. package/file-upload/file-list/file-list.styles.d.ts +0 -1
  101. package/file-upload/file-list/file-list.styles.js +1 -1
  102. package/file-upload/file-list/file-list.styles.js.map +1 -1
  103. package/file-upload/file-list/file-list.styles_1xbypnh.css +2 -0
  104. package/file-upload/file-list-item/file-item-actions.d.ts +30 -0
  105. package/file-upload/file-list-item/file-item-actions.js +2 -0
  106. package/file-upload/file-list-item/file-item-actions.js.map +1 -0
  107. package/file-upload/file-list-item/file-item-actions.styles.d.ts +6 -0
  108. package/file-upload/file-list-item/file-item-actions.styles.js +2 -0
  109. package/file-upload/file-list-item/file-item-actions.styles.js.map +1 -0
  110. package/file-upload/file-list-item/file-item-actions.styles_1quulgy.css +8 -0
  111. package/file-upload/file-list-item/file-item-details.d.ts +15 -0
  112. package/file-upload/file-list-item/file-item-details.js +2 -0
  113. package/file-upload/file-list-item/file-item-details.js.map +1 -0
  114. package/file-upload/file-list-item/file-item-details.styles.d.ts +7 -0
  115. package/file-upload/file-list-item/file-item-details.styles.js +2 -0
  116. package/file-upload/file-list-item/file-item-details.styles.js.map +1 -0
  117. package/file-upload/file-list-item/file-item-details.styles_1ce9akx.css +7 -0
  118. package/file-upload/file-list-item/file-list-item.d.ts +1 -7
  119. package/file-upload/file-list-item/file-list-item.js +1 -1
  120. package/file-upload/file-list-item/file-list-item.js.map +1 -1
  121. package/file-upload/file-list-item/file-list-item.styles.d.ts +6 -13
  122. package/file-upload/file-list-item/file-list-item.styles.js +1 -1
  123. package/file-upload/file-list-item/file-list-item.styles.js.map +1 -1
  124. package/file-upload/file-list-item/file-list-item.styles_x3a500.css +10 -0
  125. package/file-upload/file-list-item/types.d.ts +10 -3
  126. package/file-upload/file-list-item/use-truncated-name.d.ts +1 -0
  127. package/file-upload/file-list-item/use-truncated-name.js +2 -0
  128. package/file-upload/file-list-item/use-truncated-name.js.map +1 -0
  129. package/input-group/types.d.ts +4 -1
  130. package/input-select/types.d.ts +1 -0
  131. package/local-nav/local-nav-dropdown/local-nav-dropdown.js +1 -1
  132. package/local-nav/local-nav-dropdown/local-nav-dropdown.js.map +1 -1
  133. package/local-nav/local-nav-dropdown/local-nav-dropdown.styles.d.ts +3 -0
  134. package/local-nav/local-nav-dropdown/local-nav-dropdown.styles.js +1 -1
  135. package/local-nav/local-nav-dropdown/local-nav-dropdown.styles.js.map +1 -1
  136. package/{cjs/local-nav/local-nav-dropdown/local-nav-dropdown.styles_uhd4xl.css → local-nav/local-nav-dropdown/local-nav-dropdown.styles_kpel1m.css} +9 -6
  137. package/local-nav/local-nav-menu/local-nav-menu.js +1 -1
  138. package/local-nav/local-nav-menu/local-nav-menu.js.map +1 -1
  139. package/local-nav/local-nav-menu/local-nav-menu.styles.d.ts +3 -0
  140. package/local-nav/local-nav-menu/local-nav-menu.styles.js +1 -1
  141. package/local-nav/local-nav-menu/local-nav-menu.styles.js.map +1 -1
  142. package/local-nav/local-nav-menu/local-nav-menu.styles_67fcyc.css +8 -0
  143. package/local-nav/types.d.ts +17 -0
  144. package/masked-input/types.d.ts +1 -2
  145. package/menu/menu-content.js +1 -1
  146. package/menu/menu-content.js.map +1 -1
  147. package/menu/menu-content.styles.d.ts +1 -0
  148. package/menu/menu-content.styles.js +1 -1
  149. package/menu/menu-content.styles.js.map +1 -1
  150. package/menu/menu-content.styles_1r8oxtk.css +5 -0
  151. package/menu/menu-item.styles.js +1 -1
  152. package/menu/menu-item.styles.js.map +1 -1
  153. package/menu/{menu-item.styles_liyed8.css → menu-item.styles_eut2j9.css} +1 -1
  154. package/menu/menu-link.styles.js +1 -1
  155. package/menu/menu-link.styles.js.map +1 -1
  156. package/menu/menu-link.styles_rng7n7.css +2 -0
  157. package/menu/menu-section.d.ts +1 -1
  158. package/menu/menu-section.js +1 -1
  159. package/menu/menu-section.js.map +1 -1
  160. package/menu/menu-section.styles.d.ts +7 -0
  161. package/menu/menu-section.styles.js +1 -1
  162. package/menu/menu-section.styles.js.map +1 -1
  163. package/menu/menu-section.styles_1n0ppxl.css +5 -0
  164. package/menu/menu.d.ts +1 -1
  165. package/menu/types.d.ts +10 -2
  166. package/modal-v2/slots/slot-styles.js +1 -1
  167. package/modal-v2/slots/slot-styles.js.map +1 -1
  168. package/modal-v2/slots/{slot-styles_ayb9il.css → slot-styles_ygu6zc.css} +1 -1
  169. package/navbar/navbar-items.js +1 -1
  170. package/navbar/navbar-items.js.map +1 -1
  171. package/navbar/types.d.ts +9 -0
  172. package/notification-banner/notification-banner-hoc.js +1 -1
  173. package/notification-banner/notification-banner-hoc.js.map +1 -1
  174. package/package.json +3 -4
  175. package/pagination/pagination.js +1 -1
  176. package/pagination/pagination.js.map +1 -1
  177. package/pagination/types.d.ts +8 -1
  178. package/scripts/cli.js +18 -0
  179. package/select-histogram/types.d.ts +1 -0
  180. package/shared/dropdown-list/dropdown-label.d.ts +2 -2
  181. package/shared/dropdown-list/dropdown-label.js +1 -1
  182. package/shared/dropdown-list/dropdown-label.js.map +1 -1
  183. package/shared/dropdown-list/types.d.ts +6 -2
  184. package/shared/fade-wrapper/fade-wrapper.js +1 -1
  185. package/shared/fade-wrapper/fade-wrapper.js.map +1 -1
  186. package/shared/fade-wrapper/types.d.ts +1 -0
  187. package/shared/styles/index.d.ts +5 -0
  188. package/skills/fds-build/SKILL.md +167 -0
  189. package/skills/fds-build/catalogue.md +181 -0
  190. package/skills/fds-build/evals/evals.json +519 -0
  191. package/skills/fds-build/resources/components/accordion.md +128 -0
  192. package/skills/fds-build/resources/components/alert.md +54 -0
  193. package/skills/fds-build/resources/components/animation.md +18 -0
  194. package/skills/fds-build/resources/components/avatar.md +10 -0
  195. package/skills/fds-build/resources/components/badge.md +18 -0
  196. package/skills/fds-build/resources/components/box-container.md +61 -0
  197. package/skills/fds-build/resources/components/breadcrumb.md +60 -0
  198. package/skills/fds-build/resources/components/button.md +74 -0
  199. package/skills/fds-build/resources/components/calendar.md +113 -0
  200. package/skills/fds-build/resources/components/card.md +43 -0
  201. package/skills/fds-build/resources/components/checkbox.md +53 -0
  202. package/skills/fds-build/resources/components/countdown-timer.md +20 -0
  203. package/skills/fds-build/resources/components/data-table.md +142 -0
  204. package/skills/fds-build/resources/components/date-navigator.md +25 -0
  205. package/skills/fds-build/resources/components/divider.md +23 -0
  206. package/skills/fds-build/resources/components/drawer.md +78 -0
  207. package/skills/fds-build/resources/components/error-display.md +137 -0
  208. package/skills/fds-build/resources/components/feedback-rating.md +61 -0
  209. package/skills/fds-build/resources/components/file-download.md +54 -0
  210. package/skills/fds-build/resources/components/file-upload.md +105 -0
  211. package/skills/fds-build/resources/components/filter.md +228 -0
  212. package/skills/fds-build/resources/components/footer.md +94 -0
  213. package/skills/fds-build/resources/components/form-date-input.md +85 -0
  214. package/skills/fds-build/resources/components/form-date-range-input.md +146 -0
  215. package/skills/fds-build/resources/components/form-e-signature.md +55 -0
  216. package/skills/fds-build/resources/components/form-input.md +65 -0
  217. package/skills/fds-build/resources/components/form-masked-input.md +69 -0
  218. package/skills/fds-build/resources/components/form-multi-select.md +100 -0
  219. package/skills/fds-build/resources/components/form-nested-multi-select.md +159 -0
  220. package/skills/fds-build/resources/components/form-nested-select.md +151 -0
  221. package/skills/fds-build/resources/components/form-otp-verification.md +175 -0
  222. package/skills/fds-build/resources/components/form-phone-number-input.md +87 -0
  223. package/skills/fds-build/resources/components/form-predictive-text-input.md +108 -0
  224. package/skills/fds-build/resources/components/form-select.md +103 -0
  225. package/skills/fds-build/resources/components/form-slider.md +116 -0
  226. package/skills/fds-build/resources/components/form-textarea.md +116 -0
  227. package/skills/fds-build/resources/components/form-time-range-picker.md +98 -0
  228. package/skills/fds-build/resources/components/form-timepicker.md +77 -0
  229. package/skills/fds-build/resources/components/form-unit-number-input.md +138 -0
  230. package/skills/fds-build/resources/components/fullscreen-image-carousel.md +159 -0
  231. package/skills/fds-build/resources/components/histogram-slider.md +41 -0
  232. package/skills/fds-build/resources/components/icon.md +167 -0
  233. package/skills/fds-build/resources/components/image-button.md +12 -0
  234. package/skills/fds-build/resources/components/input.md +51 -0
  235. package/skills/fds-build/resources/components/language-switcher.md +15 -0
  236. package/skills/fds-build/resources/components/layout.md +156 -0
  237. package/skills/fds-build/resources/components/link-list.md +120 -0
  238. package/skills/fds-build/resources/components/local-nav.md +119 -0
  239. package/skills/fds-build/resources/components/masthead.md +14 -0
  240. package/skills/fds-build/resources/components/menu.md +109 -0
  241. package/skills/fds-build/resources/components/modal.md +150 -0
  242. package/skills/fds-build/resources/components/navbar.md +270 -0
  243. package/skills/fds-build/resources/components/notification-banner.md +106 -0
  244. package/skills/fds-build/resources/components/pagination.md +104 -0
  245. package/skills/fds-build/resources/components/pill.md +55 -0
  246. package/skills/fds-build/resources/components/popover.md +80 -0
  247. package/skills/fds-build/resources/components/progress-indicator.md +47 -0
  248. package/skills/fds-build/resources/components/radio-button.md +66 -0
  249. package/skills/fds-build/resources/components/schedule.md +78 -0
  250. package/skills/fds-build/resources/components/sidenav.md +183 -0
  251. package/skills/fds-build/resources/components/singpass-button.md +14 -0
  252. package/skills/fds-build/resources/components/smart-app-banner.md +28 -0
  253. package/skills/fds-build/resources/components/tab.md +107 -0
  254. package/skills/fds-build/resources/components/table.md +65 -0
  255. package/skills/fds-build/resources/components/tag.md +68 -0
  256. package/skills/fds-build/resources/components/text-list.md +44 -0
  257. package/skills/fds-build/resources/components/time-slot-bar-week.md +66 -0
  258. package/skills/fds-build/resources/components/time-slot-bar.md +57 -0
  259. package/skills/fds-build/resources/components/time-slot-week-view.md +70 -0
  260. package/skills/fds-build/resources/components/timeline.md +69 -0
  261. package/skills/fds-build/resources/components/timetable.md +123 -0
  262. package/skills/fds-build/resources/components/toast.md +72 -0
  263. package/skills/fds-build/resources/components/toggle.md +119 -0
  264. package/skills/fds-build/resources/components/typography.md +86 -0
  265. package/skills/fds-build/resources/components/uneditable-section.md +84 -0
  266. package/skills/fds-build/resources/foundations/layout-composition-patterns.md +53 -0
  267. package/skills/fds-build/resources/foundations/tokens.md +547 -0
  268. package/skills/fds-build/resources/patterns/states.md +300 -0
  269. package/skills/fds-build/resources/setup/setup.md +202 -0
  270. package/skills/fds-build/resources/templates/form.md +427 -0
  271. package/skills/fds-build/resources/templates/listing.md +435 -0
  272. package/theme/styles/a11y-playground.css +52 -40
  273. package/theme/styles/bookingsg.css +52 -40
  274. package/theme/styles/careercompass.css +52 -40
  275. package/theme/styles/ccube.css +52 -40
  276. package/theme/styles/imda.css +52 -40
  277. package/theme/styles/lifesg.css +52 -40
  278. package/theme/styles/mylegacy.css +52 -40
  279. package/theme/styles/oneservice.css +52 -40
  280. package/theme/styles/pa.css +28 -22
  281. package/theme/styles/rbs.css +52 -40
  282. package/theme/styles/sgw-digital-lobby.css +52 -40
  283. package/theme/styles/smgs.css +52 -40
  284. package/theme/styles/spf.css +28 -27
  285. package/theme/styles/sportsg-orange.css +52 -40
  286. package/theme/styles/supportgowhere.css +52 -40
  287. package/theme/styles/tote-board.css +52 -40
  288. package/theme/styles/vica.css +52 -40
  289. package/theme/styles/websg.css +52 -40
  290. package/theme/styles/wise.css +52 -40
  291. package/theme/styles/wogaa.css +52 -40
  292. package/theme/tokens/colour.d.ts +6 -0
  293. package/theme/tokens/colour.js +1 -1
  294. package/theme/tokens/colour.js.map +1 -1
  295. package/theme/tokens/radius.d.ts +3 -0
  296. package/theme/tokens/radius.js +1 -1
  297. package/theme/tokens/radius.js.map +1 -1
  298. package/breadcrumb/breadcrumb.styles_1nl4aua.css +0 -18
  299. package/cjs/breadcrumb/breadcrumb.styles_1nl4aua.css +0 -18
  300. package/cjs/file-upload/file-item-edit.js +0 -2
  301. package/cjs/file-upload/file-item-edit.js.map +0 -1
  302. package/cjs/file-upload/file-item-edit.styles.js +0 -2
  303. package/cjs/file-upload/file-item-edit.styles.js.map +0 -1
  304. package/cjs/file-upload/file-item-edit.styles_19bvztn.css +0 -11
  305. package/cjs/file-upload/file-list/file-list.styles_1w7so98.css +0 -3
  306. package/cjs/file-upload/file-list-item/file-list-item.styles_kgtfke.css +0 -17
  307. package/cjs/local-nav/local-nav-menu/local-nav-menu.styles_pq19dh.css +0 -5
  308. package/cjs/menu/menu-content.styles_p7ijll.css +0 -2
  309. package/cjs/menu/menu-link.styles_hfxoez.css +0 -2
  310. package/cjs/menu/menu-section.styles_g7my4d.css +0 -3
  311. package/file-upload/file-item-edit.d.ts +0 -14
  312. package/file-upload/file-item-edit.js +0 -2
  313. package/file-upload/file-item-edit.js.map +0 -1
  314. package/file-upload/file-item-edit.styles.d.ts +0 -9
  315. package/file-upload/file-item-edit.styles.js +0 -2
  316. package/file-upload/file-item-edit.styles.js.map +0 -1
  317. package/file-upload/file-item-edit.styles_19bvztn.css +0 -11
  318. package/file-upload/file-list/file-list.styles_1w7so98.css +0 -3
  319. package/file-upload/file-list-item/file-list-item.styles_kgtfke.css +0 -17
  320. package/local-nav/local-nav-menu/local-nav-menu.styles_pq19dh.css +0 -5
  321. package/menu/menu-content.styles_p7ijll.css +0 -2
  322. package/menu/menu-link.styles_hfxoez.css +0 -2
  323. package/menu/menu-section.styles_g7my4d.css +0 -3
@@ -1 +1 @@
1
- {"version":3,"file":"pagination.js","sources":["../../src/pagination/pagination.tsx"],"sourcesContent":["import { Chevron2LeftIcon } from \"@lifesg/react-icons/chevron-2-left\";\nimport { Chevron2RightIcon } from \"@lifesg/react-icons/chevron-2-right\";\nimport { ChevronLeftIcon } from \"@lifesg/react-icons/chevron-left\";\nimport { ChevronLineLeftIcon } from \"@lifesg/react-icons/chevron-line-left\";\nimport { ChevronLineRightIcon } from \"@lifesg/react-icons/chevron-line-right\";\nimport { ChevronRightIcon } from \"@lifesg/react-icons/chevron-right\";\nimport { EllipsisHorizontalIcon } from \"@lifesg/react-icons/ellipsis-horizontal\";\nimport clsx from \"clsx\";\nimport React, { useEffect, useState } from \"react\";\n\nimport { Input } from \"../input\";\nimport { InputSelect } from \"../input-select\";\nimport { VisuallyHidden } from \"../shared/accessibility\";\nimport { ClickableIcon } from \"../shared/clickable-icon\";\nimport { useMaxWidthMediaQuery } from \"../theme\";\nimport { Typography } from \"../typography\";\nimport { useId, useIsMounted } from \"../util\";\nimport * as styles from \"./pagination.styles\";\nimport type { PageSizeItemProps, PaginationProps } from \"./types\";\n\nconst Component = (\n {\n id,\n \"data-testid\": dataTestId,\n className,\n pageSize = 10,\n totalItems,\n activePage,\n pageSizeOptions = DEFAULT_OPTIONS,\n showFirstAndLastNav,\n showPageSizeChanger = false,\n onPageChange,\n onPageSizeChange,\n }: PaginationProps,\n ref: React.Ref<HTMLDivElement>\n) => {\n // =============================================================================\n // CONST, STATE, REF\n // =============================================================================\n\n const isMounted = useIsMounted();\n const isMobile = useMaxWidthMediaQuery(\"sm\");\n const [hoverRightButton, setHoverRightButton] = useState(false);\n const [hoverLeftButton, setHoverLeftButton] = useState(false);\n const [inputText, setInputText] = useState<string>(\"\");\n\n const [selectedOption, setSelectedOption] = useState<\n PageSizeItemProps | undefined\n >(pageSizeOptions[0]);\n const [pageSizeLocal, setPageSize] = useState<number>(\n !isMobile && showPageSizeChanger\n ? selectedOption\n ? selectedOption.value\n : pageSize\n : pageSize\n );\n\n const boundaryRange = 1;\n const siblingRange = 1;\n\n const internalId = useId();\n const paginationId = `${internalId}-pagination`;\n\n const totalPages = Math.ceil(totalItems / pageSizeLocal);\n const isFirstPage = activePage === 1;\n const isLastPage = activePage === totalPages;\n\n const firstPaginationItem =\n activePage > 1 ? () => handlePaginationItemClick(1) : undefined;\n const lastPaginationItem =\n activePage < totalPages\n ? () => handlePaginationItemClick(totalPages)\n : undefined;\n const prevPaginationItem =\n activePage > 1\n ? () => handlePaginationItemClick(activePage - 1)\n : undefined;\n const nextPaginationItem =\n activePage < totalPages\n ? () =>\n handlePaginationItemClick(parseInt(activePage.toString()) + 1)\n : undefined;\n\n // =============================================================================\n // EFFECTS\n // =============================================================================\n useEffect(() => {\n if (activePage) {\n setInputValue(activePage);\n }\n }, [activePage]);\n\n useEffect(() => {\n setPageSize(pageSize);\n setSelectedOption(\n pageSizeOptions.find((option) => option.value === pageSize)\n );\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [pageSize]);\n\n // =============================================================================\n // HELPER FUNCTIONS\n // =============================================================================\n const setInputValue = (value: number) => {\n setInputText(value.toString());\n };\n\n const closeAllTooltips = () => {\n setHoverRightButton(false);\n setHoverLeftButton(false);\n };\n\n // =============================================================================\n // EVENT HANDLERS\n // =============================================================================\n const handlePaginationItemClick = (pageIndex: number) => {\n if (onPageChange) {\n onPageChange(pageIndex);\n setInputValue(pageIndex);\n }\n };\n\n const handleFastForwardClick = () => {\n const currentIndex = Math.min(totalPages, activePage + 5);\n handlePaginationItemClick(currentIndex);\n setInputValue(currentIndex);\n setHoverRightButton(true);\n setHoverLeftButton(false);\n };\n\n const handleFastBackwardClick = () => {\n const currentIndex = Math.max(1, activePage - 5);\n handlePaginationItemClick(currentIndex);\n setInputValue(currentIndex);\n setHoverRightButton(false);\n setHoverLeftButton(true);\n };\n\n const handleInput = (event: React.ChangeEvent<HTMLInputElement>) => {\n const value = event.target.value;\n const numericRegex = /^[0-9]+$/;\n if (value === undefined || value.length === 0) {\n setInputText(\"\");\n } else if (!numericRegex.test(value)) {\n setInputText(value.replace(/[^0-9]/g, \"\"));\n } else {\n const newPage = parseInt(value.replace(/[^0-9]/g, \"\"));\n setInputValue(Math.max(1, Math.min(totalPages, newPage)));\n }\n };\n\n const handleInputSubmit = (event: React.FormEvent) => {\n event.preventDefault();\n if (inputText) {\n onPageChange?.(parseInt(inputText));\n }\n };\n\n const onHoverRightButton = () => {\n setHoverRightButton(true);\n };\n\n const onBlurRightButton = () => {\n setHoverRightButton(false);\n };\n\n const onHoverLeftButton = () => {\n setHoverLeftButton(true);\n };\n\n const onBlurLeftButton = () => {\n setHoverLeftButton(false);\n };\n\n const handleListItemClick = (item: PageSizeItemProps) => {\n setSelectedOption(item);\n const pagesize = item.value;\n const totalPage = Math.ceil(totalItems / pagesize);\n\n setPageSize(pagesize);\n\n const page = activePage >= totalPage ? totalPage : activePage;\n\n if (onPageSizeChange) {\n onPageSizeChange(page, pagesize);\n }\n };\n\n // =============================================================================\n // RENDER FUNCTIONS\n // =============================================================================\n const renderPaginationItems = () => {\n return [...Array(totalPages)].map((e, i) => {\n const pageIndex = i + 1;\n const startRange = boundaryRange + 2 + siblingRange * 2;\n const endRange = totalPages - startRange;\n const totalRange = (boundaryRange + siblingRange) * 2 + 3;\n const active = activePage === pageIndex;\n if (totalPages <= totalRange) {\n return (\n <button\n key={pageIndex}\n onClick={() => handlePaginationItemClick(pageIndex)}\n className={clsx(\n styles.pageItem,\n active && styles.pageItemSelected\n )}\n aria-label={`page ${pageIndex} of ${totalPages}`}\n aria-current={active ? \"page\" : false}\n onMouseOver={closeAllTooltips}\n onFocus={closeAllTooltips}\n >\n {pageIndex}\n </button>\n );\n }\n\n const ellipsisStart =\n activePage + siblingRange > startRange &&\n pageIndex === boundaryRange + 1;\n if (ellipsisStart) {\n return renderStartEllipsis(pageIndex);\n }\n\n const ellipsisEnd =\n activePage - siblingRange <= endRange &&\n pageIndex === totalPages - boundaryRange - 1;\n if (ellipsisEnd) {\n return renderEndEllipsis(pageIndex);\n }\n\n const paginationStart =\n (pageIndex <= startRange &&\n activePage + siblingRange <= startRange) ||\n pageIndex <= boundaryRange;\n const paginationMiddle =\n pageIndex === activePage ||\n (pageIndex <= activePage + siblingRange &&\n pageIndex >= activePage - siblingRange - 1);\n const paginationEnd =\n (pageIndex > endRange &&\n activePage - siblingRange > endRange) ||\n pageIndex > totalPages - boundaryRange;\n if (paginationStart || paginationMiddle || paginationEnd) {\n return (\n <button\n key={pageIndex}\n onClick={() => handlePaginationItemClick(pageIndex)}\n className={clsx(\n styles.pageItem,\n active && styles.pageItemSelected\n )}\n aria-label={`page ${pageIndex} of ${totalPages}`}\n aria-current={active ? \"page\" : false}\n onMouseOver={closeAllTooltips}\n onFocus={closeAllTooltips}\n >\n {pageIndex}\n </button>\n );\n }\n\n return null;\n });\n };\n\n const renderStartEllipsis = (pageIndex: number) => (\n <div key={pageIndex} className={styles.ellipsisContainer}>\n <ClickableIcon\n className={styles.iconButton}\n focusHighlight={false}\n focusOutline=\"browser\"\n aria-label={\"Previous 5 pages\"}\n onMouseEnter={onHoverLeftButton}\n onMouseLeave={onBlurLeftButton}\n onFocus={onHoverLeftButton}\n onBlur={onBlurLeftButton}\n onClick={handleFastBackwardClick}\n >\n {hoverLeftButton ? (\n <Chevron2LeftIcon aria-hidden />\n ) : (\n <EllipsisHorizontalIcon aria-hidden />\n )}\n </ClickableIcon>\n {hoverLeftButton && (\n <div className={styles.hover}>Previous 5 pages</div>\n )}\n </div>\n );\n\n const renderEndEllipsis = (pageIndex: number) => (\n <div key={pageIndex} className={styles.ellipsisContainer}>\n <ClickableIcon\n className={styles.iconButton}\n focusHighlight={false}\n focusOutline=\"browser\"\n aria-label={\"Next 5 pages\"}\n onMouseEnter={onHoverRightButton}\n onMouseLeave={onBlurRightButton}\n onFocus={onHoverRightButton}\n onBlur={onBlurRightButton}\n onClick={handleFastForwardClick}\n >\n {hoverRightButton ? (\n <Chevron2RightIcon aria-hidden />\n ) : (\n <EllipsisHorizontalIcon aria-hidden />\n )}\n </ClickableIcon>\n {hoverRightButton && (\n <div className={styles.hover}>Next 5 pages</div>\n )}\n </div>\n );\n\n const renderMobile = () => (\n <div className={styles.paginationMobileInput}>\n <form onSubmit={handleInputSubmit}>\n <Input\n className={styles.inputView}\n value={inputText}\n onChange={handleInput}\n autoComplete=\"off\"\n type=\"numeric\"\n id={(id || \"pagination\") + \"-input\"}\n data-testid={(dataTestId || \"pagination\") + \"-input\"}\n aria-label={`Page ${activePage} of ${totalPages}`}\n />\n </form>\n <Typography.BodyBL className={styles.labelDivider}>\n /\n </Typography.BodyBL>\n <Typography.BodyBL className={styles.label}>\n {totalPages}\n </Typography.BodyBL>\n </div>\n );\n\n return (\n <nav\n className={clsx(styles.paginationWrapper, className)}\n ref={ref}\n id={id || \"pagination-wrapper\"}\n data-testid={dataTestId || \"pagination\"}\n aria-labelledby={paginationId}\n >\n <VisuallyHidden id={paginationId} aria-hidden>\n pagination\n </VisuallyHidden>\n <div className={styles.paginationList}>\n <div className={styles.paginationMenu}>\n {showFirstAndLastNav && (\n <ClickableIcon\n className={clsx(\n styles.iconButton,\n styles.iconButtonNavigation\n )}\n onClick={firstPaginationItem}\n disabled={isFirstPage}\n focusHighlight={false}\n aria-label=\"First page\"\n focusOutline=\"browser\"\n >\n <ChevronLineLeftIcon aria-hidden />\n </ClickableIcon>\n )}\n <ClickableIcon\n className={clsx(\n styles.iconButton,\n styles.iconButtonNavigation\n )}\n onClick={prevPaginationItem}\n disabled={isFirstPage}\n focusHighlight={false}\n aria-label=\"Previous page\"\n focusOutline=\"browser\"\n >\n <ChevronLeftIcon aria-hidden />\n </ClickableIcon>\n {isMounted && isMobile\n ? renderMobile()\n : renderPaginationItems()}\n <ClickableIcon\n className={clsx(\n styles.iconButton,\n styles.iconButtonNavigation\n )}\n onClick={nextPaginationItem}\n disabled={isLastPage}\n focusHighlight={false}\n aria-label=\"Next page\"\n focusOutline=\"browser\"\n >\n <ChevronRightIcon aria-hidden />\n </ClickableIcon>\n {showFirstAndLastNav && (\n <ClickableIcon\n className={clsx(\n styles.iconButton,\n styles.iconButtonNavigation\n )}\n onClick={lastPaginationItem}\n disabled={isLastPage}\n focusHighlight={false}\n aria-label=\"Last page\"\n focusOutline=\"browser\"\n >\n <ChevronLineRightIcon aria-hidden />\n </ClickableIcon>\n )}\n </div>\n </div>\n {showPageSizeChanger && !isMobile && (\n <div className={styles.inputSelectWrapper}>\n <VisuallyHidden id={`${paginationId}-page-size`}>\n Items per page\n </VisuallyHidden>\n <InputSelect\n aria-labelledby={`${paginationId}-page-size`}\n options={pageSizeOptions}\n valueExtractor={(item) => item.value}\n listExtractor={(item) => item.label}\n displayValueExtractor={(item) => item.label}\n selectedOption={selectedOption}\n onSelectOption={handleListItemClick}\n />\n </div>\n )}\n </nav>\n );\n};\n// @catalog\n/**\n * A navigation control for moving through paged content.\n *\n * Use `Pagination` to let users navigate between pages of a data set.\n * @keywords navigation, pager, pages\n */\nexport const Pagination = React.forwardRef(Component);\n\nconst DEFAULT_OPTIONS: PageSizeItemProps[] = [\n { value: 10, label: \"10 per page\" },\n { value: 20, label: \"20 per page\" },\n { value: 30, label: \"30 per page\" },\n];\n"],"names":["Pagination","React","forwardRef","id","dataTestId","className","pageSize","totalItems","activePage","pageSizeOptions","DEFAULT_OPTIONS","showFirstAndLastNav","showPageSizeChanger","onPageChange","onPageSizeChange","ref","isMounted","useIsMounted","isMobile","useMaxWidthMediaQuery","hoverRightButton","setHoverRightButton","useState","hoverLeftButton","setHoverLeftButton","inputText","setInputText","selectedOption","setSelectedOption","pageSizeLocal","setPageSize","value","paginationId","useId","totalPages","Math","ceil","isFirstPage","isLastPage","firstPaginationItem","handlePaginationItemClick","undefined","lastPaginationItem","prevPaginationItem","nextPaginationItem","parseInt","toString","useEffect","setInputValue","find","option","closeAllTooltips","pageIndex","handleFastForwardClick","currentIndex","min","handleFastBackwardClick","max","handleInput","event","target","length","test","newPage","replace","handleInputSubmit","preventDefault","onHoverRightButton","onBlurRightButton","onHoverLeftButton","onBlurLeftButton","renderStartEllipsis","_jsxs","styles.ellipsisContainer","children","_jsx","ClickableIcon","styles.iconButton","focusHighlight","focusOutline","onMouseEnter","onMouseLeave","onFocus","onBlur","onClick","Chevron2LeftIcon","EllipsisHorizontalIcon","styles.hover","renderEndEllipsis","Chevron2RightIcon","clsx","styles.paginationWrapper","VisuallyHidden","styles.paginationList","styles.paginationMenu","styles.iconButtonNavigation","disabled","ChevronLineLeftIcon","ChevronLeftIcon","styles.paginationMobileInput","onSubmit","Input","styles.inputView","onChange","autoComplete","type","Typography","BodyBL","styles.labelDivider","styles.label","Array","map","e","i","endRange","boundaryRange","active","styles.pageItem","styles.pageItemSelected","onMouseOver","ChevronRightIcon","ChevronLineRightIcon","styles.inputSelectWrapper","InputSelect","options","valueExtractor","item","listExtractor","label","displayValueExtractor","onSelectOption","pagesize","totalPage"],"mappings":"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"}
1
+ {"version":3,"file":"pagination.js","sources":["../../src/pagination/pagination.tsx"],"sourcesContent":["import { Chevron2LeftIcon } from \"@lifesg/react-icons/chevron-2-left\";\nimport { Chevron2RightIcon } from \"@lifesg/react-icons/chevron-2-right\";\nimport { ChevronLeftIcon } from \"@lifesg/react-icons/chevron-left\";\nimport { ChevronLineLeftIcon } from \"@lifesg/react-icons/chevron-line-left\";\nimport { ChevronLineRightIcon } from \"@lifesg/react-icons/chevron-line-right\";\nimport { ChevronRightIcon } from \"@lifesg/react-icons/chevron-right\";\nimport { EllipsisHorizontalIcon } from \"@lifesg/react-icons/ellipsis-horizontal\";\nimport clsx from \"clsx\";\nimport React, { useEffect, useState } from \"react\";\n\nimport { Input } from \"../input\";\nimport { InputSelect } from \"../input-select\";\nimport { VisuallyHidden } from \"../shared/accessibility\";\nimport { ClickableIcon } from \"../shared/clickable-icon\";\nimport { useMaxWidthMediaQuery } from \"../theme\";\nimport { Typography } from \"../typography\";\nimport { useId, useIsMounted } from \"../util\";\nimport * as styles from \"./pagination.styles\";\nimport type { PageSizeItemProps, PaginationProps } from \"./types\";\n\nconst getPageSizeOption = (\n pageSizeOptions: PageSizeItemProps[],\n value: number\n) =>\n pageSizeOptions.find((option) => option.value === value) ??\n pageSizeOptions[0];\n\nconst Component = (\n {\n id,\n \"data-testid\": dataTestId,\n className,\n variant = \"default\",\n pageSize = 10,\n totalItems,\n activePage,\n pageSizeOptions = DEFAULT_OPTIONS,\n showFirstAndLastNav,\n showPageSizeChanger = false,\n onPageChange,\n onPageSizeChange,\n }: PaginationProps,\n ref: React.Ref<HTMLDivElement>\n) => {\n // =============================================================================\n // CONST, STATE, REF\n // =============================================================================\n\n const isMounted = useIsMounted();\n const isMobile = useMaxWidthMediaQuery(\"sm\");\n const resolvedVariant =\n variant === \"default\"\n ? isMounted && isMobile\n ? \"compact\"\n : \"full\"\n : variant;\n const useInputLayout = resolvedVariant === \"compact\";\n const usesPageSizeChanger =\n showPageSizeChanger && resolvedVariant === \"full\";\n const [hoverRightButton, setHoverRightButton] = useState(false);\n const [hoverLeftButton, setHoverLeftButton] = useState(false);\n const [inputText, setInputText] = useState<string>(\"\");\n const [pageSizeLocal, setPageSize] = useState<number>(pageSize);\n const selectedOption = getPageSizeOption(pageSizeOptions, pageSizeLocal);\n\n const boundaryRange = 1;\n const siblingRange = 1;\n\n const internalId = useId();\n const paginationId = `${internalId}-pagination`;\n\n const totalPages = Math.ceil(totalItems / pageSizeLocal);\n const isFirstPage = activePage === 1;\n const isLastPage = activePage === totalPages;\n\n const firstPaginationItem =\n activePage > 1 ? () => handlePaginationItemClick(1) : undefined;\n const lastPaginationItem =\n activePage < totalPages\n ? () => handlePaginationItemClick(totalPages)\n : undefined;\n const prevPaginationItem =\n activePage > 1\n ? () => handlePaginationItemClick(activePage - 1)\n : undefined;\n const nextPaginationItem =\n activePage < totalPages\n ? () =>\n handlePaginationItemClick(parseInt(activePage.toString()) + 1)\n : undefined;\n\n // =============================================================================\n // EFFECTS\n // =============================================================================\n useEffect(() => {\n if (activePage) {\n setInputValue(activePage);\n }\n }, [activePage]);\n\n useEffect(() => {\n setPageSize(pageSize);\n }, [pageSize]);\n\n // =============================================================================\n // HELPER FUNCTIONS\n // =============================================================================\n const setInputValue = (value: number) => {\n setInputText(value.toString());\n };\n\n const closeAllTooltips = () => {\n setHoverRightButton(false);\n setHoverLeftButton(false);\n };\n\n // =============================================================================\n // EVENT HANDLERS\n // =============================================================================\n const handlePaginationItemClick = (pageIndex: number) => {\n if (onPageChange) {\n onPageChange(pageIndex);\n setInputValue(pageIndex);\n }\n };\n\n const handleFastForwardClick = () => {\n const currentIndex = Math.min(totalPages, activePage + 5);\n handlePaginationItemClick(currentIndex);\n setInputValue(currentIndex);\n setHoverRightButton(true);\n setHoverLeftButton(false);\n };\n\n const handleFastBackwardClick = () => {\n const currentIndex = Math.max(1, activePage - 5);\n handlePaginationItemClick(currentIndex);\n setInputValue(currentIndex);\n setHoverRightButton(false);\n setHoverLeftButton(true);\n };\n\n const handleInput = (event: React.ChangeEvent<HTMLInputElement>) => {\n const value = event.target.value;\n const numericRegex = /^[0-9]+$/;\n if (value === undefined || value.length === 0) {\n setInputText(\"\");\n } else if (!numericRegex.test(value)) {\n setInputText(value.replace(/[^0-9]/g, \"\"));\n } else {\n const newPage = parseInt(value.replace(/[^0-9]/g, \"\"));\n setInputValue(Math.max(1, Math.min(totalPages, newPage)));\n }\n };\n\n const handleInputSubmit = (event: React.FormEvent) => {\n event.preventDefault();\n if (inputText) {\n onPageChange?.(parseInt(inputText));\n }\n };\n\n const onHoverRightButton = () => {\n setHoverRightButton(true);\n };\n\n const onBlurRightButton = () => {\n setHoverRightButton(false);\n };\n\n const onHoverLeftButton = () => {\n setHoverLeftButton(true);\n };\n\n const onBlurLeftButton = () => {\n setHoverLeftButton(false);\n };\n\n const handleListItemClick = (item: PageSizeItemProps) => {\n const pagesize = item.value;\n const totalPage = Math.ceil(totalItems / pagesize);\n\n setPageSize(pagesize);\n\n const page = activePage >= totalPage ? totalPage : activePage;\n\n if (onPageSizeChange) {\n onPageSizeChange(page, pagesize);\n }\n };\n\n // =============================================================================\n // RENDER FUNCTIONS\n // =============================================================================\n const renderPaginationItems = () => {\n return [...Array(totalPages)].map((e, i) => {\n const pageIndex = i + 1;\n const startRange = boundaryRange + 2 + siblingRange * 2;\n const endRange = totalPages - startRange;\n const totalRange = (boundaryRange + siblingRange) * 2 + 3;\n const active = activePage === pageIndex;\n if (totalPages <= totalRange) {\n return (\n <button\n key={pageIndex}\n onClick={() => handlePaginationItemClick(pageIndex)}\n className={clsx(\n styles.pageItem,\n active && styles.pageItemSelected\n )}\n aria-label={`page ${pageIndex} of ${totalPages}`}\n aria-current={active ? \"page\" : false}\n onMouseOver={closeAllTooltips}\n onFocus={closeAllTooltips}\n >\n {pageIndex}\n </button>\n );\n }\n\n const ellipsisStart =\n activePage + siblingRange > startRange &&\n pageIndex === boundaryRange + 1;\n if (ellipsisStart) {\n return renderStartEllipsis(pageIndex);\n }\n\n const ellipsisEnd =\n activePage - siblingRange <= endRange &&\n pageIndex === totalPages - boundaryRange - 1;\n if (ellipsisEnd) {\n return renderEndEllipsis(pageIndex);\n }\n\n const paginationStart =\n (pageIndex <= startRange &&\n activePage + siblingRange <= startRange) ||\n pageIndex <= boundaryRange;\n const paginationMiddle =\n pageIndex === activePage ||\n (pageIndex <= activePage + siblingRange &&\n pageIndex >= activePage - siblingRange - 1);\n const paginationEnd =\n (pageIndex > endRange &&\n activePage - siblingRange > endRange) ||\n pageIndex > totalPages - boundaryRange;\n if (paginationStart || paginationMiddle || paginationEnd) {\n return (\n <button\n key={pageIndex}\n onClick={() => handlePaginationItemClick(pageIndex)}\n className={clsx(\n styles.pageItem,\n active && styles.pageItemSelected\n )}\n aria-label={`page ${pageIndex} of ${totalPages}`}\n aria-current={active ? \"page\" : false}\n onMouseOver={closeAllTooltips}\n onFocus={closeAllTooltips}\n >\n {pageIndex}\n </button>\n );\n }\n\n return null;\n });\n };\n\n const renderStartEllipsis = (pageIndex: number) => (\n <div key={pageIndex} className={styles.ellipsisContainer}>\n <ClickableIcon\n className={styles.iconButton}\n focusHighlight={false}\n focusOutline=\"browser\"\n aria-label={\"Previous 5 pages\"}\n onMouseEnter={onHoverLeftButton}\n onMouseLeave={onBlurLeftButton}\n onFocus={onHoverLeftButton}\n onBlur={onBlurLeftButton}\n onClick={handleFastBackwardClick}\n >\n {hoverLeftButton ? (\n <Chevron2LeftIcon aria-hidden />\n ) : (\n <EllipsisHorizontalIcon aria-hidden />\n )}\n </ClickableIcon>\n {hoverLeftButton && (\n <div className={styles.hover}>Previous 5 pages</div>\n )}\n </div>\n );\n\n const renderEndEllipsis = (pageIndex: number) => (\n <div key={pageIndex} className={styles.ellipsisContainer}>\n <ClickableIcon\n className={styles.iconButton}\n focusHighlight={false}\n focusOutline=\"browser\"\n aria-label={\"Next 5 pages\"}\n onMouseEnter={onHoverRightButton}\n onMouseLeave={onBlurRightButton}\n onFocus={onHoverRightButton}\n onBlur={onBlurRightButton}\n onClick={handleFastForwardClick}\n >\n {hoverRightButton ? (\n <Chevron2RightIcon aria-hidden />\n ) : (\n <EllipsisHorizontalIcon aria-hidden />\n )}\n </ClickableIcon>\n {hoverRightButton && (\n <div className={styles.hover}>Next 5 pages</div>\n )}\n </div>\n );\n\n const renderMobile = () => (\n <div className={styles.paginationMobileInput}>\n <form onSubmit={handleInputSubmit}>\n <Input\n className={styles.inputView}\n value={inputText}\n onChange={handleInput}\n autoComplete=\"off\"\n type=\"numeric\"\n id={(id || \"pagination\") + \"-input\"}\n data-testid={(dataTestId || \"pagination\") + \"-input\"}\n aria-label={`Page ${activePage} of ${totalPages}`}\n />\n </form>\n <Typography.BodyBL className={styles.labelDivider}>\n /\n </Typography.BodyBL>\n <Typography.BodyBL className={styles.label}>\n {totalPages}\n </Typography.BodyBL>\n </div>\n );\n\n return (\n <nav\n className={clsx(styles.paginationWrapper, className)}\n ref={ref}\n id={id || \"pagination-wrapper\"}\n data-testid={dataTestId || \"pagination\"}\n aria-labelledby={paginationId}\n >\n <VisuallyHidden id={paginationId} aria-hidden>\n pagination\n </VisuallyHidden>\n <div className={styles.paginationList}>\n <div className={styles.paginationMenu}>\n {showFirstAndLastNav && (\n <ClickableIcon\n className={clsx(\n styles.iconButton,\n styles.iconButtonNavigation\n )}\n onClick={firstPaginationItem}\n disabled={isFirstPage}\n focusHighlight={false}\n aria-label=\"First page\"\n focusOutline=\"browser\"\n >\n <ChevronLineLeftIcon aria-hidden />\n </ClickableIcon>\n )}\n <ClickableIcon\n className={clsx(\n styles.iconButton,\n styles.iconButtonNavigation\n )}\n onClick={prevPaginationItem}\n disabled={isFirstPage}\n focusHighlight={false}\n aria-label=\"Previous page\"\n focusOutline=\"browser\"\n >\n <ChevronLeftIcon aria-hidden />\n </ClickableIcon>\n {useInputLayout ? renderMobile() : renderPaginationItems()}\n <ClickableIcon\n className={clsx(\n styles.iconButton,\n styles.iconButtonNavigation\n )}\n onClick={nextPaginationItem}\n disabled={isLastPage}\n focusHighlight={false}\n aria-label=\"Next page\"\n focusOutline=\"browser\"\n >\n <ChevronRightIcon aria-hidden />\n </ClickableIcon>\n {showFirstAndLastNav && (\n <ClickableIcon\n className={clsx(\n styles.iconButton,\n styles.iconButtonNavigation\n )}\n onClick={lastPaginationItem}\n disabled={isLastPage}\n focusHighlight={false}\n aria-label=\"Last page\"\n focusOutline=\"browser\"\n >\n <ChevronLineRightIcon aria-hidden />\n </ClickableIcon>\n )}\n </div>\n </div>\n {usesPageSizeChanger && (\n <div className={styles.inputSelectWrapper}>\n <VisuallyHidden id={`${paginationId}-page-size`}>\n Items per page\n </VisuallyHidden>\n <InputSelect\n aria-labelledby={`${paginationId}-page-size`}\n options={pageSizeOptions}\n valueExtractor={(item) => item.value}\n listExtractor={(item) => item.label}\n displayValueExtractor={(item) => item.label}\n selectedOption={selectedOption}\n onSelectOption={handleListItemClick}\n />\n </div>\n )}\n </nav>\n );\n};\n// @catalog\n/**\n * A navigation control for moving through paged content.\n *\n * Use `Pagination` to let users navigate between pages of a data set.\n * @keywords navigation, pager, pages\n */\nexport const Pagination = React.forwardRef(Component);\n\nconst DEFAULT_OPTIONS: PageSizeItemProps[] = [\n { value: 10, label: \"10 per page\" },\n { value: 20, label: \"20 per page\" },\n { value: 30, label: \"30 per page\" },\n];\n"],"names":["Pagination","React","forwardRef","id","dataTestId","className","variant","pageSize","totalItems","activePage","pageSizeOptions","DEFAULT_OPTIONS","showFirstAndLastNav","showPageSizeChanger","onPageChange","onPageSizeChange","ref","isMounted","useIsMounted","isMobile","useMaxWidthMediaQuery","resolvedVariant","useInputLayout","usesPageSizeChanger","hoverRightButton","setHoverRightButton","useState","hoverLeftButton","setHoverLeftButton","inputText","setInputText","pageSizeLocal","setPageSize","selectedOption","value","_a","find","option","getPageSizeOption","paginationId","useId","totalPages","Math","ceil","isFirstPage","isLastPage","firstPaginationItem","handlePaginationItemClick","undefined","lastPaginationItem","prevPaginationItem","nextPaginationItem","parseInt","toString","useEffect","setInputValue","closeAllTooltips","pageIndex","handleFastForwardClick","currentIndex","min","handleFastBackwardClick","max","handleInput","event","target","length","test","newPage","replace","handleInputSubmit","preventDefault","onHoverRightButton","onBlurRightButton","onHoverLeftButton","onBlurLeftButton","renderStartEllipsis","_jsxs","styles.ellipsisContainer","children","_jsx","ClickableIcon","styles.iconButton","focusHighlight","focusOutline","onMouseEnter","onMouseLeave","onFocus","onBlur","onClick","Chevron2LeftIcon","EllipsisHorizontalIcon","styles.hover","renderEndEllipsis","Chevron2RightIcon","clsx","styles.paginationWrapper","VisuallyHidden","styles.paginationList","styles.paginationMenu","styles.iconButtonNavigation","disabled","ChevronLineLeftIcon","ChevronLeftIcon","styles.paginationMobileInput","onSubmit","Input","styles.inputView","onChange","autoComplete","type","Typography","BodyBL","styles.labelDivider","styles.label","Array","map","e","i","endRange","boundaryRange","active","styles.pageItem","styles.pageItemSelected","onMouseOver","ChevronRightIcon","ChevronLineRightIcon","styles.inputSelectWrapper","InputSelect","options","valueExtractor","item","listExtractor","label","displayValueExtractor","onSelectOption","pagesize","totalPage"],"mappings":"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"}
@@ -5,6 +5,13 @@ export interface PaginationProps {
5
5
  id?: string | undefined;
6
6
  "data-testid"?: string | undefined;
7
7
  className?: string | undefined;
8
+ /**
9
+ * Controls whether the pagination follows the responsive layout
10
+ * or always renders the full or compact layout.
11
+ *
12
+ * @default "default"
13
+ */
14
+ variant?: "default" | "full" | "compact" | undefined;
8
15
  /**
9
16
  * Number of items displayed per page.
10
17
  *
@@ -34,7 +41,7 @@ export interface PaginationProps {
34
41
  */
35
42
  showFirstAndLastNav?: boolean | undefined;
36
43
  /**
37
- * When `true`, renders a dropdown on desktop viewports to change the `pageSize`.
44
+ * When `true`, renders a dropdown in the full layout to change the `pageSize`.
38
45
  *
39
46
  * @default false
40
47
  */
package/scripts/cli.js ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ const { execSync, spawnSync } = require("child_process");
3
+ const path = require("path");
4
+
5
+ const [, , subcommand, ...rest] = process.argv;
6
+
7
+ if (subcommand === "skills") {
8
+ const packageRoot = path.resolve(__dirname, "..");
9
+ execSync(`npx skills add ${packageRoot}`, { stdio: "inherit" });
10
+ } else {
11
+ const codemodBin = path.resolve(__dirname, "../codemods/run-codemod.js");
12
+ const result = spawnSync(
13
+ process.execPath,
14
+ [codemodBin, subcommand, ...rest].filter(Boolean),
15
+ { stdio: "inherit" }
16
+ );
17
+ process.exit(result.status ?? 1);
18
+ }
@@ -29,6 +29,7 @@ export interface SelectHistogramProps extends Pick<AriaAttributes, "aria-labelle
29
29
  histogramSlider: SelectHistogramSliderProps;
30
30
  /**
31
31
  * Controls how long option labels are truncated when they overflow.
32
+ * Specify `"none"` to display option labels in full.
32
33
  *
33
34
  * @default "end"
34
35
  */
@@ -1,4 +1,4 @@
1
- import type { DropdownVariantType, LabelDisplayType } from "./types";
1
+ import type { DropdownVariantType, LabelDisplayType, TruncateType } from "./types";
2
2
  interface DropdownLabelProps {
3
3
  bold?: boolean | undefined;
4
4
  displayType?: LabelDisplayType | undefined;
@@ -8,7 +8,7 @@ interface DropdownLabelProps {
8
8
  selected?: boolean | undefined;
9
9
  disabled?: boolean | undefined;
10
10
  sublabel?: string | undefined;
11
- truncationType?: "middle" | "end" | undefined;
11
+ truncationType?: TruncateType | undefined;
12
12
  variant?: DropdownVariantType | undefined;
13
13
  }
14
14
  export declare const DropdownLabel: ({ bold, displayType, label, searchTerm, maxLines, selected, disabled, sublabel, truncationType, variant, }: DropdownLabelProps) => JSX.Element;
@@ -1,2 +1,2 @@
1
- import{jsxs as e,jsx as i,Fragment as r}from"react/jsx-runtime";import t from"clsx";import{useRef as l,useCallback as s,useMemo as n}from"react";import{useResizeDetector as m}from"../../external/react-resize-detector/build/index.esm.js";import"../../theme/theme-provider/index.js";import"../../theme/theme-provider/context.js";import"../../theme/tokens/border.js";import"../../theme/tokens/colour.js";import{Font as d}from"../../theme/tokens/font.js";import"../../theme/tokens/media-query.js";import{useApplyStyle as o}from"../../theme/utils/use-apply-styles.js";import"../../theme/utils/use-media-query.js";import{StringHelper as a}from"../../util/string-helper.js";import{tokens as c,primaryText as p,primaryTextBold as h,primaryTextDisabled as u,primaryTextSelected as f,primaryTextLabelInline as x,primaryTextTruncateEnd as b,secondaryText as y,secondaryTextLabelInline as j,secondaryTextTruncateEnd as v,secondaryTextNextLine as N,secondaryTextInline as T,label as g,labelVariantSmall as L,labelNextLine as S,baseTruncateStyle as k,truncateFirstLine as w,truncateLineSingle as z,truncateSecondLine as q,matchedText as C}from"./dropdown-label.styles.js";const $=({bold:$,displayType:O="inline",label:W,searchTerm:A,maxLines:B=2,selected:D,disabled:E,sublabel:F,truncationType:G="middle",variant:H="default"})=>{const I="small"===H?d.Spec["body-size-md"]:d.Spec["body-size-baseline"],J=d.Spec["font-family-body"],{ref:K,width:M}=m(),P=l(null),Q=l(null);o(P,{[c.primaryText.maxLines]:String(B)}),o(Q,{[c.secondaryText.maxLines]:String(B)});const R=s((e=>{if("inline"!==O||!M)return!1;return a.getTextWidth(e,`${I} '${J}'`)>M-50}),[M,O,I,J]),U=n((()=>R(W)),[R,W]),V=n((()=>F&&R(F)),[R,F]),X="middle"===G&&(U||V)?"next-line":O,Y=t=>{if(!A)return t;const l=A.toLowerCase().trim(),s=t.toLowerCase().indexOf(l),n=s+A.length;return-1===s?t:e(r,{children:[W.slice(0,s),i("span",{className:C,children:W.slice(s,n)}),W.slice(n)]})},Z=l=>e(r,{children:[i("div",{className:t(k,w,1===B&&z),"aria-hidden":!0,children:Y(l)}),i("div",{className:t(k,q,1===B&&z),"aria-hidden":!0,children:Y(l)})]});return e("div",{ref:K,className:t(g,"small"===H&&L,"next-line"===X&&S),children:[i("div",{ref:P,"aria-label":W,className:t(p,$&&h,E&&u,!E&&D&&f,"inline"===X&&x,"end"===G&&b),children:"middle"===G&&U?Z(W):Y(W)}),F&&i("div",{ref:Q,"aria-label":F,className:t(y,"inline"===X&&j,"end"===G&&v,"next-line"===O?N:T),children:"middle"===G&&V?Z(F):F})]})};export{$ as DropdownLabel};
1
+ import{jsxs as e,jsx as i,Fragment as r}from"react/jsx-runtime";import t from"clsx";import{useRef as l,useCallback as n,useMemo as s}from"react";import{useResizeDetector as m}from"../../external/react-resize-detector/build/index.esm.js";import"../../theme/theme-provider/index.js";import"../../theme/theme-provider/context.js";import"../../theme/tokens/border.js";import"../../theme/tokens/colour.js";import{Font as o}from"../../theme/tokens/font.js";import"../../theme/tokens/media-query.js";import{useApplyStyle as d}from"../../theme/utils/use-apply-styles.js";import"../../theme/utils/use-media-query.js";import{StringHelper as a}from"../../util/string-helper.js";import{tokens as c,primaryText as p,primaryTextBold as h,primaryTextDisabled as u,primaryTextSelected as f,primaryTextLabelInline as x,primaryTextTruncateEnd as b,secondaryText as y,secondaryTextLabelInline as j,secondaryTextTruncateEnd as v,secondaryTextNextLine as N,secondaryTextInline as T,label as g,labelVariantSmall as L,labelNextLine as S,baseTruncateStyle as k,truncateFirstLine as w,truncateLineSingle as z,truncateSecondLine as q,matchedText as C}from"./dropdown-label.styles.js";const $=({bold:$,displayType:O="inline",label:W,searchTerm:A,maxLines:B=2,selected:D,disabled:E,sublabel:F,truncationType:G="middle",variant:H="default"})=>{const I="small"===H?o.Spec["body-size-md"]:o.Spec["body-size-baseline"],J=o.Spec["font-family-body"],{ref:K,width:M}=m(),P="none"===G,Q=l(null),R=l(null);d(Q,{[c.primaryText.maxLines]:P?null:String(B)}),d(R,{[c.secondaryText.maxLines]:P?null:String(B)});const U=n((e=>{if(P||"inline"!==O||!M)return!1;return a.getTextWidth(e,`${I} '${J}'`)>M-50}),[P,M,O,I,J]),V=s((()=>U(W)),[U,W]),X=s((()=>F&&U(F)),[U,F]),Y="middle"===G&&(V||X)?"next-line":O,Z=t=>{if(!A)return t;const l=A.toLowerCase().trim(),n=t.toLowerCase().indexOf(l),s=n+A.length;return-1===n?t:e(r,{children:[W.slice(0,n),i("span",{className:C,children:W.slice(n,s)}),W.slice(s)]})},_=l=>e(r,{children:[i("div",{className:t(k,w,1===B&&z),"aria-hidden":!0,children:Z(l)}),i("div",{className:t(k,q,1===B&&z),"aria-hidden":!0,children:Z(l)})]});return e("div",{ref:K,className:t(g,"small"===H&&L,"next-line"===Y&&S),children:[i("div",{ref:Q,"aria-label":W,className:t(p,$&&h,E&&u,!E&&D&&f,"inline"===Y&&x,"end"===G&&b),children:"middle"===G&&V?_(W):Z(W)}),F&&i("div",{ref:R,"aria-label":F,className:t(y,"inline"===Y&&j,"end"===G&&v,"next-line"===O?N:T),children:"middle"===G&&X?_(F):F})]})};export{$ as DropdownLabel};
2
2
  //# sourceMappingURL=dropdown-label.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"dropdown-label.js","sources":["../../../src/shared/dropdown-list/dropdown-label.tsx"],"sourcesContent":["import clsx from \"clsx\";\nimport { useCallback, useMemo, useRef } from \"react\";\nimport { useResizeDetector } from \"react-resize-detector\";\n\nimport { Font } from \"../../theme\";\nimport { useApplyStyle } from \"../../theme/utils/use-apply-styles\";\nimport { StringHelper } from \"../../util/string-helper\";\nimport * as styles from \"./dropdown-label.styles\";\nimport type { DropdownVariantType, LabelDisplayType } from \"./types\";\n\ninterface DropdownLabelProps {\n bold?: boolean | undefined;\n displayType?: LabelDisplayType | undefined;\n label: string;\n maxLines?: number | undefined;\n searchTerm?: string | undefined;\n selected?: boolean | undefined;\n disabled?: boolean | undefined;\n sublabel?: string | undefined;\n truncationType?: \"middle\" | \"end\" | undefined;\n variant?: DropdownVariantType | undefined;\n}\n\nexport const DropdownLabel = ({\n bold,\n displayType = \"inline\",\n label,\n searchTerm,\n maxLines = 2,\n selected,\n disabled,\n sublabel,\n truncationType = \"middle\",\n variant = \"default\",\n}: DropdownLabelProps): JSX.Element => {\n const fontSize =\n variant === \"small\"\n ? Font.Spec[\"body-size-md\"]\n : Font.Spec[\"body-size-baseline\"];\n const fontFamily = Font.Spec[\"font-family-body\"];\n const { ref, width } = useResizeDetector();\n\n const primaryTextRef = useRef<HTMLDivElement>(null);\n const secondaryTextRef = useRef<HTMLDivElement>(null);\n\n useApplyStyle(primaryTextRef, {\n [styles.tokens.primaryText.maxLines]: String(maxLines),\n });\n\n useApplyStyle(secondaryTextRef, {\n [styles.tokens.secondaryText.maxLines]: String(maxLines),\n });\n\n // =========================================================================\n // HELPER FUNCTIONS\n // =========================================================================\n const hasExceededContainer = useCallback(\n (displayText: string) => {\n if (displayType !== \"inline\" || !width) {\n return false;\n }\n\n // best-effort attempt to check if multi-line text will overflow,\n // rendering an actual element in the DOM might be more accurate\n // but might not be performant for large lists\n const textWidth = StringHelper.getTextWidth(\n displayText,\n `${fontSize} '${fontFamily}'`\n );\n\n // there's less space than expected due to word breaks, so an\n // arbitary offset is applied\n return textWidth > width - 50;\n },\n [width, displayType, fontSize, fontFamily]\n );\n\n // =========================================================================\n // CONST, STATE, REFS\n // =========================================================================\n const shouldTruncateTitle = useMemo(\n () => hasExceededContainer(label),\n [hasExceededContainer, label]\n );\n const shouldTruncateLabel = useMemo(\n () => sublabel && hasExceededContainer(sublabel),\n [hasExceededContainer, sublabel]\n );\n\n // css cannot truncate inline elements so force the display to render\n // them separately\n const itemDisplayType =\n truncationType === \"middle\" &&\n (shouldTruncateTitle || shouldTruncateLabel)\n ? \"next-line\"\n : displayType;\n\n // =========================================================================\n // RENDER FUNCTIONS\n // =========================================================================\n const renderMatchInBold = (displayText: string) => {\n if (!searchTerm) {\n return displayText;\n }\n\n const match = searchTerm.toLowerCase().trim();\n const startIndex = displayText.toLowerCase().indexOf(match);\n const endIndex = startIndex + searchTerm.length;\n\n if (startIndex === -1) {\n return displayText;\n }\n\n return (\n <>\n {label.slice(0, startIndex)}\n <span className={styles.matchedText}>\n {label.slice(startIndex, endIndex)}\n </span>\n {label.slice(endIndex)}\n </>\n );\n };\n\n const renderTruncatedText = (displayText: string): JSX.Element => {\n return (\n <>\n <div\n className={clsx(\n styles.baseTruncateStyle,\n styles.truncateFirstLine,\n maxLines === 1 && styles.truncateLineSingle\n )}\n aria-hidden\n >\n {renderMatchInBold(displayText)}\n </div>\n <div\n className={clsx(\n styles.baseTruncateStyle,\n styles.truncateSecondLine,\n maxLines === 1 && styles.truncateLineSingle\n )}\n aria-hidden\n >\n {renderMatchInBold(displayText)}\n </div>\n </>\n );\n };\n\n return (\n <div\n ref={ref}\n className={clsx(\n styles.label,\n variant === \"small\" && styles.labelVariantSmall,\n itemDisplayType === \"next-line\" && styles.labelNextLine\n )}\n >\n <div\n ref={primaryTextRef}\n aria-label={label}\n className={clsx(\n styles.primaryText,\n bold && styles.primaryTextBold,\n disabled && styles.primaryTextDisabled,\n !disabled && selected && styles.primaryTextSelected,\n itemDisplayType === \"inline\" &&\n styles.primaryTextLabelInline,\n truncationType === \"end\" && styles.primaryTextTruncateEnd\n )}\n >\n {truncationType === \"middle\" && shouldTruncateTitle\n ? renderTruncatedText(label)\n : renderMatchInBold(label)}\n </div>\n {sublabel && (\n <div\n ref={secondaryTextRef}\n aria-label={sublabel}\n className={clsx(\n styles.secondaryText,\n itemDisplayType === \"inline\" &&\n styles.secondaryTextLabelInline,\n truncationType === \"end\" &&\n styles.secondaryTextTruncateEnd,\n displayType === \"next-line\"\n ? styles.secondaryTextNextLine\n : styles.secondaryTextInline\n )}\n >\n {truncationType === \"middle\" && shouldTruncateLabel\n ? renderTruncatedText(sublabel)\n : sublabel}\n </div>\n )}\n </div>\n );\n};\n"],"names":["DropdownLabel","bold","displayType","label","searchTerm","maxLines","selected","disabled","sublabel","truncationType","variant","fontSize","Font","Spec","fontFamily","ref","width","useResizeDetector","primaryTextRef","useRef","secondaryTextRef","useApplyStyle","styles.tokens","primaryText","String","secondaryText","hasExceededContainer","useCallback","displayText","StringHelper","getTextWidth","shouldTruncateTitle","useMemo","shouldTruncateLabel","itemDisplayType","renderMatchInBold","match","toLowerCase","trim","startIndex","indexOf","endIndex","length","_jsxs","_Fragment","children","slice","_jsx","className","styles.matchedText","renderTruncatedText","clsx","styles.baseTruncateStyle","styles.truncateFirstLine","styles.truncateLineSingle","styles.truncateSecondLine","styles.label","styles.labelVariantSmall","styles.labelNextLine","styles.primaryText","styles.primaryTextBold","styles.primaryTextDisabled","styles.primaryTextSelected","styles.primaryTextLabelInline","styles.primaryTextTruncateEnd","styles.secondaryText","styles.secondaryTextLabelInline","styles.secondaryTextTruncateEnd","styles.secondaryTextNextLine","styles.secondaryTextInline"],"mappings":"soCAuBO,MAAMA,EAAgB,EACzBC,OACAC,cAAc,SAAQC,MACtBA,EACAC,aACAC,WAAW,EACXC,WACAC,WACAC,WACAC,iBAAiB,SACjBC,UAAU,cAEV,MAAMC,EACU,UAAZD,EACME,EAAKC,KAAK,gBACVD,EAAKC,KAAK,sBACdC,EAAaF,EAAKC,KAAK,qBACvBE,IAAEA,EAAGC,MAAEA,GAAUC,IAEjBC,EAAiBC,EAAuB,MACxCC,EAAmBD,EAAuB,MAEhDE,EAAcH,EAAgB,CAC1B,CAACI,EAAcC,YAAYlB,UAAWmB,OAAOnB,KAGjDgB,EAAcD,EAAkB,CAC5B,CAACE,EAAcG,cAAcpB,UAAWmB,OAAOnB,KAMnD,MAAMqB,EAAuBC,GACxBC,IACG,GAAoB,WAAhB1B,IAA6Bc,EAC7B,OAAO,EAaX,OAPkBa,EAAaC,aAC3BF,EACA,GAAGjB,MAAaG,MAKDE,EAAQ,EAAE,GAEjC,CAACA,EAAOd,EAAaS,EAAUG,IAM7BiB,EAAsBC,GACxB,IAAMN,EAAqBvB,IAC3B,CAACuB,EAAsBvB,IAErB8B,EAAsBD,GACxB,IAAMxB,GAAYkB,EAAqBlB,IACvC,CAACkB,EAAsBlB,IAKrB0B,EACiB,WAAnBzB,IACCsB,GAAuBE,GAClB,YACA/B,EAKJiC,EAAqBP,IACvB,IAAKxB,EACD,OAAOwB,EAGX,MAAMQ,EAAQhC,EAAWiC,cAAcC,OACjCC,EAAaX,EAAYS,cAAcG,QAAQJ,GAC/CK,EAAWF,EAAanC,EAAWsC,OAEzC,OAAmB,IAAfH,EACOX,EAIPe,EAAAC,EAAA,CAAAC,SAAA,CACK1C,EAAM2C,MAAM,EAAGP,GAChBQ,EAAA,OAAA,CAAMC,UAAWC,EAAkBJ,SAC9B1C,EAAM2C,MAAMP,EAAYE,KAE5BtC,EAAM2C,MAAML,KACd,EAILS,EAAuBtB,GAErBe,EAAAC,EAAA,CAAAC,SAAA,CACIE,EAAA,MAAA,CACIC,UAAWG,EACPC,EACAC,EACa,IAAbhD,GAAkBiD,GACrB,eAAA,EAAAT,SAGAV,EAAkBP,KAEvBmB,EAAA,MAAA,CACIC,UAAWG,EACPC,EACAG,EACa,IAAblD,GAAkBiD,GACrB,eAAA,EAAAT,SAGAV,EAAkBP,QAMnC,OACIe,EAAA,MAAA,CACI5B,IAAKA,EACLiC,UAAWG,EACPK,EACY,UAAZ9C,GAAuB+C,EACH,cAApBvB,GAAmCwB,GACtCb,SAAA,CAEDE,SACIhC,IAAKG,EAAc,aACPf,EACZ6C,UAAWG,EACPQ,EACA1D,GAAQ2D,EACRrD,GAAYsD,GACXtD,GAAYD,GAAYwD,EACL,WAApB5B,GACI6B,EACe,QAAnBtD,GAA4BuD,GAC/BnB,SAEmB,WAAnBpC,GAA+BsB,EAC1BmB,EAAoB/C,GACpBgC,EAAkBhC,KAE3BK,GACGuC,EAAA,MAAA,CACIhC,IAAKK,EAAgB,aACTZ,EACZwC,UAAWG,EACPc,EACoB,WAApB/B,GACIgC,EACe,QAAnBzD,GACI0D,EACY,cAAhBjE,EACMkE,EACAC,YAGU,WAAnB5D,GAA+BwB,EAC1BiB,EAAoB1C,GACpBA,MAGZ"}
1
+ {"version":3,"file":"dropdown-label.js","sources":["../../../src/shared/dropdown-list/dropdown-label.tsx"],"sourcesContent":["import clsx from \"clsx\";\nimport { useCallback, useMemo, useRef } from \"react\";\nimport { useResizeDetector } from \"react-resize-detector\";\n\nimport { Font } from \"../../theme\";\nimport { useApplyStyle } from \"../../theme/utils/use-apply-styles\";\nimport { StringHelper } from \"../../util/string-helper\";\nimport * as styles from \"./dropdown-label.styles\";\nimport type {\n DropdownVariantType,\n LabelDisplayType,\n TruncateType,\n} from \"./types\";\n\ninterface DropdownLabelProps {\n bold?: boolean | undefined;\n displayType?: LabelDisplayType | undefined;\n label: string;\n maxLines?: number | undefined;\n searchTerm?: string | undefined;\n selected?: boolean | undefined;\n disabled?: boolean | undefined;\n sublabel?: string | undefined;\n truncationType?: TruncateType | undefined;\n variant?: DropdownVariantType | undefined;\n}\n\nexport const DropdownLabel = ({\n bold,\n displayType = \"inline\",\n label,\n searchTerm,\n maxLines = 2,\n selected,\n disabled,\n sublabel,\n truncationType = \"middle\",\n variant = \"default\",\n}: DropdownLabelProps): JSX.Element => {\n const fontSize =\n variant === \"small\"\n ? Font.Spec[\"body-size-md\"]\n : Font.Spec[\"body-size-baseline\"];\n const fontFamily = Font.Spec[\"font-family-body\"];\n const { ref, width } = useResizeDetector();\n\n const noTruncation = truncationType === \"none\";\n\n const primaryTextRef = useRef<HTMLDivElement>(null);\n const secondaryTextRef = useRef<HTMLDivElement>(null);\n\n useApplyStyle(primaryTextRef, {\n [styles.tokens.primaryText.maxLines]: noTruncation\n ? null\n : String(maxLines),\n });\n\n useApplyStyle(secondaryTextRef, {\n [styles.tokens.secondaryText.maxLines]: noTruncation\n ? null\n : String(maxLines),\n });\n\n // =========================================================================\n // HELPER FUNCTIONS\n // =========================================================================\n const hasExceededContainer = useCallback(\n (displayText: string) => {\n if (noTruncation || displayType !== \"inline\" || !width) {\n return false;\n }\n\n // best-effort attempt to check if multi-line text will overflow,\n // rendering an actual element in the DOM might be more accurate\n // but might not be performant for large lists\n const textWidth = StringHelper.getTextWidth(\n displayText,\n `${fontSize} '${fontFamily}'`\n );\n\n // there's less space than expected due to word breaks, so an\n // arbitary offset is applied\n return textWidth > width - 50;\n },\n [noTruncation, width, displayType, fontSize, fontFamily]\n );\n\n // =========================================================================\n // CONST, STATE, REFS\n // =========================================================================\n const shouldTruncateTitle = useMemo(\n () => hasExceededContainer(label),\n [hasExceededContainer, label]\n );\n const shouldTruncateLabel = useMemo(\n () => sublabel && hasExceededContainer(sublabel),\n [hasExceededContainer, sublabel]\n );\n\n // css cannot truncate inline elements so force the display to render\n // them separately\n const itemDisplayType =\n truncationType === \"middle\" &&\n (shouldTruncateTitle || shouldTruncateLabel)\n ? \"next-line\"\n : displayType;\n\n // =========================================================================\n // RENDER FUNCTIONS\n // =========================================================================\n const renderMatchInBold = (displayText: string) => {\n if (!searchTerm) {\n return displayText;\n }\n\n const match = searchTerm.toLowerCase().trim();\n const startIndex = displayText.toLowerCase().indexOf(match);\n const endIndex = startIndex + searchTerm.length;\n\n if (startIndex === -1) {\n return displayText;\n }\n\n return (\n <>\n {label.slice(0, startIndex)}\n <span className={styles.matchedText}>\n {label.slice(startIndex, endIndex)}\n </span>\n {label.slice(endIndex)}\n </>\n );\n };\n\n const renderTruncatedText = (displayText: string): JSX.Element => {\n return (\n <>\n <div\n className={clsx(\n styles.baseTruncateStyle,\n styles.truncateFirstLine,\n maxLines === 1 && styles.truncateLineSingle\n )}\n aria-hidden\n >\n {renderMatchInBold(displayText)}\n </div>\n <div\n className={clsx(\n styles.baseTruncateStyle,\n styles.truncateSecondLine,\n maxLines === 1 && styles.truncateLineSingle\n )}\n aria-hidden\n >\n {renderMatchInBold(displayText)}\n </div>\n </>\n );\n };\n\n return (\n <div\n ref={ref}\n className={clsx(\n styles.label,\n variant === \"small\" && styles.labelVariantSmall,\n itemDisplayType === \"next-line\" && styles.labelNextLine\n )}\n >\n <div\n ref={primaryTextRef}\n aria-label={label}\n className={clsx(\n styles.primaryText,\n bold && styles.primaryTextBold,\n disabled && styles.primaryTextDisabled,\n !disabled && selected && styles.primaryTextSelected,\n itemDisplayType === \"inline\" &&\n styles.primaryTextLabelInline,\n truncationType === \"end\" && styles.primaryTextTruncateEnd\n )}\n >\n {truncationType === \"middle\" && shouldTruncateTitle\n ? renderTruncatedText(label)\n : renderMatchInBold(label)}\n </div>\n {sublabel && (\n <div\n ref={secondaryTextRef}\n aria-label={sublabel}\n className={clsx(\n styles.secondaryText,\n itemDisplayType === \"inline\" &&\n styles.secondaryTextLabelInline,\n truncationType === \"end\" &&\n styles.secondaryTextTruncateEnd,\n displayType === \"next-line\"\n ? styles.secondaryTextNextLine\n : styles.secondaryTextInline\n )}\n >\n {truncationType === \"middle\" && shouldTruncateLabel\n ? renderTruncatedText(sublabel)\n : sublabel}\n </div>\n )}\n </div>\n );\n};\n"],"names":["DropdownLabel","bold","displayType","label","searchTerm","maxLines","selected","disabled","sublabel","truncationType","variant","fontSize","Font","Spec","fontFamily","ref","width","useResizeDetector","noTruncation","primaryTextRef","useRef","secondaryTextRef","useApplyStyle","styles.tokens","primaryText","String","secondaryText","hasExceededContainer","useCallback","displayText","StringHelper","getTextWidth","shouldTruncateTitle","useMemo","shouldTruncateLabel","itemDisplayType","renderMatchInBold","match","toLowerCase","trim","startIndex","indexOf","endIndex","length","_jsxs","_Fragment","children","slice","_jsx","className","styles.matchedText","renderTruncatedText","clsx","styles.baseTruncateStyle","styles.truncateFirstLine","styles.truncateLineSingle","styles.truncateSecondLine","styles.label","styles.labelVariantSmall","styles.labelNextLine","styles.primaryText","styles.primaryTextBold","styles.primaryTextDisabled","styles.primaryTextSelected","styles.primaryTextLabelInline","styles.primaryTextTruncateEnd","styles.secondaryText","styles.secondaryTextLabelInline","styles.secondaryTextTruncateEnd","styles.secondaryTextNextLine","styles.secondaryTextInline"],"mappings":"soCA2BO,MAAMA,EAAgB,EACzBC,OACAC,cAAc,SAAQC,MACtBA,EACAC,aACAC,WAAW,EACXC,WACAC,WACAC,WACAC,iBAAiB,SACjBC,UAAU,cAEV,MAAMC,EACU,UAAZD,EACME,EAAKC,KAAK,gBACVD,EAAKC,KAAK,sBACdC,EAAaF,EAAKC,KAAK,qBACvBE,IAAEA,EAAGC,MAAEA,GAAUC,IAEjBC,EAAkC,SAAnBT,EAEfU,EAAiBC,EAAuB,MACxCC,EAAmBD,EAAuB,MAEhDE,EAAcH,EAAgB,CAC1B,CAACI,EAAcC,YAAYnB,UAAWa,EAChC,KACAO,OAAOpB,KAGjBiB,EAAcD,EAAkB,CAC5B,CAACE,EAAcG,cAAcrB,UAAWa,EAClC,KACAO,OAAOpB,KAMjB,MAAMsB,EAAuBC,GACxBC,IACG,GAAIX,GAAgC,WAAhBhB,IAA6Bc,EAC7C,OAAO,EAaX,OAPkBc,EAAaC,aAC3BF,EACA,GAAGlB,MAAaG,MAKDE,EAAQ,EAAE,GAEjC,CAACE,EAAcF,EAAOd,EAAaS,EAAUG,IAM3CkB,EAAsBC,GACxB,IAAMN,EAAqBxB,IAC3B,CAACwB,EAAsBxB,IAErB+B,EAAsBD,GACxB,IAAMzB,GAAYmB,EAAqBnB,IACvC,CAACmB,EAAsBnB,IAKrB2B,EACiB,WAAnB1B,IACCuB,GAAuBE,GAClB,YACAhC,EAKJkC,EAAqBP,IACvB,IAAKzB,EACD,OAAOyB,EAGX,MAAMQ,EAAQjC,EAAWkC,cAAcC,OACjCC,EAAaX,EAAYS,cAAcG,QAAQJ,GAC/CK,EAAWF,EAAapC,EAAWuC,OAEzC,OAAmB,IAAfH,EACOX,EAIPe,EAAAC,EAAA,CAAAC,SAAA,CACK3C,EAAM4C,MAAM,EAAGP,GAChBQ,EAAA,OAAA,CAAMC,UAAWC,EAAkBJ,SAC9B3C,EAAM4C,MAAMP,EAAYE,KAE5BvC,EAAM4C,MAAML,KACd,EAILS,EAAuBtB,GAErBe,EAAAC,EAAA,CAAAC,SAAA,CACIE,EAAA,MAAA,CACIC,UAAWG,EACPC,EACAC,EACa,IAAbjD,GAAkBkD,GACrB,eAAA,EAAAT,SAGAV,EAAkBP,KAEvBmB,EAAA,MAAA,CACIC,UAAWG,EACPC,EACAG,EACa,IAAbnD,GAAkBkD,GACrB,eAAA,EAAAT,SAGAV,EAAkBP,QAMnC,OACIe,EAAA,MAAA,CACI7B,IAAKA,EACLkC,UAAWG,EACPK,EACY,UAAZ/C,GAAuBgD,EACH,cAApBvB,GAAmCwB,GACtCb,SAAA,CAEDE,SACIjC,IAAKI,EAAc,aACPhB,EACZ8C,UAAWG,EACPQ,EACA3D,GAAQ4D,EACRtD,GAAYuD,GACXvD,GAAYD,GAAYyD,EACL,WAApB5B,GACI6B,EACe,QAAnBvD,GAA4BwD,GAC/BnB,SAEmB,WAAnBrC,GAA+BuB,EAC1BmB,EAAoBhD,GACpBiC,EAAkBjC,KAE3BK,GACGwC,EAAA,MAAA,CACIjC,IAAKM,EAAgB,aACTb,EACZyC,UAAWG,EACPc,EACoB,WAApB/B,GACIgC,EACe,QAAnB1D,GACI2D,EACY,cAAhBlE,EACMmE,EACAC,YAGU,WAAnB7D,GAA+ByB,EAC1BiB,EAAoB3C,GACpBA,MAGZ"}
@@ -3,8 +3,9 @@
3
3
  *
4
4
  * - `"middle"` trims the centre of the text.
5
5
  * - `"end"` trims from the right.
6
+ * - `"none"` disables truncation, the text wraps and is displayed in full.
6
7
  */
7
- export type TruncateType = "middle" | "end";
8
+ export type TruncateType = "middle" | "end" | "none";
8
9
  /**
9
10
  * Async load state for dropdown items.
10
11
  *
@@ -84,7 +85,10 @@ export interface DropdownConfigProps {
84
85
  itemsLoadState?: ItemsLoadStateType | undefined;
85
86
  /** How overflowing item text is truncated. */
86
87
  itemTruncationType?: TruncateType | undefined;
87
- /** Maximum visible lines before the label is truncated (applies to `"end"` truncation). */
88
+ /**
89
+ * Maximum visible lines before the label is truncated (applies to `"end"`
90
+ * truncation). Ignored when `itemTruncationType` is `"none"`.
91
+ */
88
92
  itemMaxLines?: number | undefined;
89
93
  /** Visual size variant for the dropdown items. */
90
94
  variant?: DropdownVariantType | undefined;
@@ -1,2 +1,2 @@
1
- import{__rest as e}from"../../_virtual/_tslib.js";import{jsxs as r,jsx as t,Fragment as o}from"react/jsx-runtime";import{ChevronLeftIcon as i}from"@lifesg/react-icons/chevron-left";import{ChevronRightIcon as s}from"@lifesg/react-icons/chevron-right";import l from"clsx";import n from"../../external/lodash/throttle.js";import{forwardRef as c,useState as a,useRef as d,useImperativeHandle as m,useCallback as f,useMemo as h,useEffect as p}from"react";import{useResizeDetector as u}from"../../external/react-resize-detector/build/index.esm.js";import"../../theme/theme-provider/index.js";import"../../theme/theme-provider/context.js";import"../../theme/tokens/border.js";import"../../theme/tokens/colour.js";import"../../theme/tokens/font.js";import"../../theme/tokens/media-query.js";import{useApplyStyle as j}from"../../theme/utils/use-apply-styles.js";import"../../theme/utils/use-media-query.js";import{ClickableIcon as b}from"../clickable-icon/clickable-icon.js";import{tokens as v,wrapper as g,content as k,fadeIndicatorButton as x,indicatorLeft as N,fade as y,fadeLeft as z,indicatorRight as R,fadeRight as w}from"./fade-wrapper.styles.js";import{getFadeColorSet as C,getFadeBackgroundColorValue as L}from"./helpers.js";const W=c(((c,W)=>{var{children:q,className:E,fadeColor:I,fadePosition:M="both",showIndicator:P=!1,onResize:_}=c,O=e(c,["children","className","fadeColor","fadePosition","showIndicator","onResize"]);const[A,B]=a("left"===M||"both"===M),[D,F]=a("right"===M||"both"===M),G=d(null),H=d(null),J=d(null),K=d(null),Q=C(I);j(J,{[v.backgroundColor]:L(Q.left,P)}),j(K,{[v.backgroundColor]:L(Q.right,P)}),u({onResize:U,targetRef:G,refreshMode:"debounce",refreshRate:50}),m(W,(()=>({resize(){U()}})));const S=f((()=>{const e=G.current,r=H.current;e&&r&&r.scrollWidth>e.offsetWidth?(F(Math.round(r.scrollLeft)<r.scrollWidth-r.offsetWidth),B(r.scrollLeft>=1)):(F(!1),B(!1))}),[]),T=h((()=>n(S,50)),[S]);function U(){S(),_&&_({content:H.current,wrapper:G.current})}p((()=>{const e=H.current;return S(),e&&e.addEventListener("scroll",T),()=>{e&&e.removeEventListener("scroll",T),T.cancel()}}),[S,T]);return r("div",Object.assign({ref:G,className:l(g,E)},O,{children:[t("div",{ref:H,className:k,children:q}),r(o,{children:[A&&t("div",{ref:J,className:l(y,z),"data-id":"left-fade",children:P&&t(b,{className:l(x,N),"data-id":"left-fade-indicator-button",children:t(i,{})})}),D&&t("div",{ref:K,className:l(y,w),"data-id":"right-fade",children:P&&t(b,{className:l(x,R),"data-id":"right-fade-indicator-button",children:t(s,{})})})]})]}))}));export{W as FadeWrapper};
1
+ import{__rest as e}from"../../_virtual/_tslib.js";import{jsxs as r,jsx as t,Fragment as o}from"react/jsx-runtime";import{ChevronLeftIcon as i}from"@lifesg/react-icons/chevron-left";import{ChevronRightIcon as s}from"@lifesg/react-icons/chevron-right";import l from"clsx";import n from"../../external/lodash/throttle.js";import{forwardRef as c,useState as a,useRef as d,useImperativeHandle as m,useCallback as f,useMemo as h,useEffect as u}from"react";import{useResizeDetector as p}from"../../external/react-resize-detector/build/index.esm.js";import"../../theme/theme-provider/index.js";import"../../theme/theme-provider/context.js";import"../../theme/tokens/border.js";import"../../theme/tokens/colour.js";import"../../theme/tokens/font.js";import"../../theme/tokens/media-query.js";import{useApplyStyle as j}from"../../theme/utils/use-apply-styles.js";import"../../theme/utils/use-media-query.js";import{ClickableIcon as b}from"../clickable-icon/clickable-icon.js";import{tokens as v,wrapper as g,content as k,fadeIndicatorButton as x,indicatorLeft as N,fade as W,fadeLeft as y,indicatorRight as z,fadeRight as L}from"./fade-wrapper.styles.js";import{getFadeColorSet as R,getFadeBackgroundColorValue as w}from"./helpers.js";const C=c(((c,C)=>{var{children:E,className:q,fadeColor:I,fadePosition:M="both",showIndicator:P=!1,onResize:_}=c,O=e(c,["children","className","fadeColor","fadePosition","showIndicator","onResize"]);const[T,A]=a("left"===M||"both"===M),[B,D]=a("right"===M||"both"===M),F=d(null),G=d(null),H=d(null),J=d(null),K=R(I);j(H,{[v.backgroundColor]:w(K.left,P)}),j(J,{[v.backgroundColor]:w(K.right,P)}),p({onResize:U,targetRef:F,refreshMode:"debounce",refreshRate:50}),m(C,(()=>({resize(){U()},scrollToEnd(){const e=G.current,r=F.current;e&&r&&(e.scrollLeft=e.scrollWidth-r.offsetWidth)}})));const Q=f((()=>{const e=F.current,r=G.current;e&&r&&r.scrollWidth>e.offsetWidth?(D(Math.round(r.scrollLeft)<r.scrollWidth-r.offsetWidth),A(r.scrollLeft>=1)):(D(!1),A(!1))}),[]),S=h((()=>n(Q,50)),[Q]);function U(){Q(),_&&_({content:G.current,wrapper:F.current})}u((()=>{const e=G.current;return Q(),e&&e.addEventListener("scroll",S),()=>{e&&e.removeEventListener("scroll",S),S.cancel()}}),[Q,S]);return r("div",Object.assign({ref:F,className:l(g,q)},O,{children:[t("div",{ref:G,className:k,children:E}),r(o,{children:[T&&t("div",{ref:H,className:l(W,y),"data-id":"left-fade",children:P&&t(b,{className:l(x,N),"data-id":"left-fade-indicator-button",children:t(i,{})})}),B&&t("div",{ref:J,className:l(W,L),"data-id":"right-fade",children:P&&t(b,{className:l(x,z),"data-id":"right-fade-indicator-button",children:t(s,{})})})]})]}))}));export{C as FadeWrapper};
2
2
  //# sourceMappingURL=fade-wrapper.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"fade-wrapper.js","sources":["../../../src/shared/fade-wrapper/fade-wrapper.tsx"],"sourcesContent":["import { ChevronLeftIcon } from \"@lifesg/react-icons/chevron-left\";\nimport { ChevronRightIcon } from \"@lifesg/react-icons/chevron-right\";\nimport clsx from \"clsx\";\nimport throttle from \"lodash/throttle\";\nimport type React from \"react\";\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { useResizeDetector } from \"react-resize-detector\";\n\nimport { useApplyStyle } from \"../../theme\";\nimport { ClickableIcon } from \"../clickable-icon\";\nimport * as styles from \"./fade-wrapper.styles\";\nimport { getFadeBackgroundColorValue, getFadeColorSet } from \"./helpers\";\nimport type { FadeWrapperProps, FadeWrapperRef } from \"./types\";\n\nconst Component = (\n {\n children,\n className,\n fadeColor,\n fadePosition = \"both\",\n showIndicator = false,\n onResize,\n ...otherProps\n }: FadeWrapperProps,\n ref: React.Ref<FadeWrapperRef>\n) => {\n // =========================================================================\n // CONST, STATE, REFS\n // =========================================================================\n const [showFadeLeft, setShowFadeLeft] = useState<boolean>(\n fadePosition === \"left\" || fadePosition === \"both\"\n );\n const [showFadeRight, setShowFadeRight] = useState<boolean>(\n fadePosition === \"right\" || fadePosition === \"both\"\n );\n\n const wrapperRef = useRef<HTMLDivElement>(null);\n const contentRef = useRef<HTMLDivElement>(null);\n const fadeLeftRef = useRef<HTMLDivElement>(null);\n const fadeRightRef = useRef<HTMLDivElement>(null);\n\n const fadeColorSet = getFadeColorSet(fadeColor);\n\n useApplyStyle(fadeLeftRef, {\n [styles.tokens.backgroundColor]: getFadeBackgroundColorValue(\n fadeColorSet.left,\n showIndicator\n ),\n });\n useApplyStyle(fadeRightRef, {\n [styles.tokens.backgroundColor]: getFadeBackgroundColorValue(\n fadeColorSet.right,\n showIndicator\n ),\n });\n\n // To scroll left when wrapper resizes\n useResizeDetector({\n onResize: handleResize,\n targetRef: wrapperRef,\n refreshMode: \"debounce\",\n refreshRate: 50,\n });\n\n useImperativeHandle(ref, () => {\n return {\n resize() {\n handleResize();\n },\n };\n });\n\n // =========================================================================\n // EFFECTS\n // =========================================================================\n const handleScroll = useCallback(() => {\n const wrapper = wrapperRef.current;\n const content = contentRef.current;\n\n if (wrapper && content && content.scrollWidth > wrapper.offsetWidth) {\n /**\n * NOTE: Used Math.round because we observe a 0.5 difference\n * even when we scroll to the end. This causes the fade right\n * to not be removed\n */\n setShowFadeRight(\n Math.round(content.scrollLeft) <\n content.scrollWidth - content.offsetWidth\n );\n setShowFadeLeft(content.scrollLeft >= 1);\n } else {\n setShowFadeRight(false);\n setShowFadeLeft(false);\n }\n }, []);\n\n const throttledScrollHandler = useMemo(() => {\n return throttle(handleScroll, 50);\n }, [handleScroll]);\n\n useEffect(() => {\n const content = contentRef.current;\n\n handleScroll();\n\n if (content) {\n content.addEventListener(\"scroll\", throttledScrollHandler);\n }\n\n return () => {\n if (content) {\n content.removeEventListener(\"scroll\", throttledScrollHandler);\n }\n\n throttledScrollHandler.cancel();\n };\n }, [handleScroll, throttledScrollHandler]);\n\n // =========================================================================\n // EVENT HANDLERS\n // =========================================================================\n\n function handleResize() {\n handleScroll();\n\n if (onResize) {\n onResize({\n content: contentRef.current!,\n wrapper: wrapperRef.current!,\n });\n }\n\n // TODO: Will move this out to Breadcrumb when it uses this component\n // if (content && wrapper && window.innerWidth <= MediaWidths.tablet) {\n // content.scrollLeft = content.scrollWidth - wrapper.offsetWidth;\n // }\n }\n\n // =========================================================================\n // RENDER FUNCTIONS\n // =========================================================================\n const renderFade = () => {\n return (\n <>\n {showFadeLeft && (\n <div\n ref={fadeLeftRef}\n className={clsx(styles.fade, styles.fadeLeft)}\n data-id=\"left-fade\"\n >\n {showIndicator && (\n <ClickableIcon\n className={clsx(\n styles.fadeIndicatorButton,\n styles.indicatorLeft\n )}\n data-id=\"left-fade-indicator-button\"\n >\n <ChevronLeftIcon />\n </ClickableIcon>\n )}\n </div>\n )}\n {showFadeRight && (\n <div\n ref={fadeRightRef}\n className={clsx(styles.fade, styles.fadeRight)}\n data-id=\"right-fade\"\n >\n {showIndicator && (\n <ClickableIcon\n className={clsx(\n styles.fadeIndicatorButton,\n styles.indicatorRight\n )}\n data-id=\"right-fade-indicator-button\"\n >\n <ChevronRightIcon />\n </ClickableIcon>\n )}\n </div>\n )}\n </>\n );\n };\n\n return (\n <div\n ref={wrapperRef}\n className={clsx(styles.wrapper, className)}\n {...otherProps}\n >\n <div ref={contentRef} className={styles.content}>\n {children}\n </div>\n {renderFade()}\n </div>\n );\n};\n\nexport const FadeWrapper = forwardRef(Component);\n"],"names":["FadeWrapper","forwardRef","_a","ref","children","className","fadeColor","fadePosition","showIndicator","onResize","otherProps","__rest","showFadeLeft","setShowFadeLeft","useState","showFadeRight","setShowFadeRight","wrapperRef","useRef","contentRef","fadeLeftRef","fadeRightRef","fadeColorSet","getFadeColorSet","useApplyStyle","styles.tokens","backgroundColor","getFadeBackgroundColorValue","left","right","useResizeDetector","handleResize","targetRef","refreshMode","refreshRate","useImperativeHandle","resize","handleScroll","useCallback","wrapper","current","content","scrollWidth","offsetWidth","Math","round","scrollLeft","throttledScrollHandler","useMemo","throttle","useEffect","addEventListener","removeEventListener","cancel","_jsxs","Object","assign","clsx","styles.wrapper","_jsx","styles.content","styles.fade","styles.fadeLeft","ClickableIcon","styles.fadeIndicatorButton","styles.indicatorLeft","ChevronLeftIcon","styles.fadeRight","styles.indicatorRight","ChevronRightIcon"],"mappings":"ysCAsBA,MA0LaA,EAAcC,GA1LT,CACdC,EASAC,SATAC,SACIA,EAAQC,UACRA,EAASC,UACTA,EAASC,aACTA,EAAe,OAAMC,cACrBA,GAAgB,EAAKC,SACrBA,GAAQP,EACLQ,EAAUC,EAAAT,EAPjB,gFAcA,MAAOU,EAAcC,GAAmBC,EACnB,SAAjBP,GAA4C,SAAjBA,IAExBQ,EAAeC,GAAoBF,EACrB,UAAjBP,GAA6C,SAAjBA,GAG1BU,EAAaC,EAAuB,MACpCC,EAAaD,EAAuB,MACpCE,EAAcF,EAAuB,MACrCG,EAAeH,EAAuB,MAEtCI,EAAeC,EAAgBjB,GAErCkB,EAAcJ,EAAa,CACvB,CAACK,EAAcC,iBAAkBC,EAC7BL,EAAaM,KACbpB,KAGRgB,EAAcH,EAAc,CACxB,CAACI,EAAcC,iBAAkBC,EAC7BL,EAAaO,MACbrB,KAKRsB,EAAkB,CACdrB,SAAUsB,EACVC,UAAWf,EACXgB,YAAa,WACbC,YAAa,KAGjBC,EAAoBhC,GAAK,KACd,CACH,MAAAiC,GACIL,GACJ,MAOR,MAAMM,EAAeC,GAAY,KAC7B,MAAMC,EAAUtB,EAAWuB,QACrBC,EAAUtB,EAAWqB,QAEvBD,GAAWE,GAAWA,EAAQC,YAAcH,EAAQI,aAMpD3B,EACI4B,KAAKC,MAAMJ,EAAQK,YACfL,EAAQC,YAAcD,EAAQE,aAEtC9B,EAAgB4B,EAAQK,YAAc,KAEtC9B,GAAiB,GACjBH,GAAgB,GACpB,GACD,IAEGkC,EAAyBC,GAAQ,IAC5BC,EAASZ,EAAc,KAC/B,CAACA,IAwBJ,SAASN,IACLM,IAEI5B,GACAA,EAAS,CACLgC,QAAStB,EAAWqB,QACpBD,QAAStB,EAAWuB,SAQhC,CApCAU,GAAU,KACN,MAAMT,EAAUtB,EAAWqB,QAQ3B,OANAH,IAEII,GACAA,EAAQU,iBAAiB,SAAUJ,GAGhC,KACCN,GACAA,EAAQW,oBAAoB,SAAUL,GAG1CA,EAAuBM,QAAQ,CAClC,GACF,CAAChB,EAAcU,IAsElB,OACIO,EAAA,MAAAC,OAAAC,OAAA,CACIrD,IAAKc,EACLZ,UAAWoD,EAAKC,EAAgBrD,IAC5BK,EAAU,CAAAN,SAAA,CAEduD,EAAA,MAAA,CAAKxD,IAAKgB,EAAYd,UAAWuD,EAAcxD,SAC1CA,IAlDLkD,eACK1C,GACG+C,EAAA,MAAA,CACIxD,IAAKiB,EACLf,UAAWoD,EAAKI,EAAaC,GAAgB,UACrC,YAAW1D,SAElBI,GACGmD,EAACI,EAAa,CACV1D,UAAWoD,EACPO,EACAC,GACH,UACO,6BAA4B7D,SAEpCuD,EAACO,EAAe,CAAA,OAK/BnD,GACG4C,EAAA,MAAA,CACIxD,IAAKkB,EACLhB,UAAWoD,EAAKI,EAAaM,aACrB,aAAY/D,SAEnBI,GACGmD,EAACI,EAAa,CACV1D,UAAWoD,EACPO,EACAI,aAEI,8BAA6BhE,SAErCuD,EAACU,EAAgB,CAAA,aAmBnC"}
1
+ {"version":3,"file":"fade-wrapper.js","sources":["../../../src/shared/fade-wrapper/fade-wrapper.tsx"],"sourcesContent":["import { ChevronLeftIcon } from \"@lifesg/react-icons/chevron-left\";\nimport { ChevronRightIcon } from \"@lifesg/react-icons/chevron-right\";\nimport clsx from \"clsx\";\nimport throttle from \"lodash/throttle\";\nimport type React from \"react\";\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { useResizeDetector } from \"react-resize-detector\";\n\nimport { useApplyStyle } from \"../../theme\";\nimport { ClickableIcon } from \"../clickable-icon\";\nimport * as styles from \"./fade-wrapper.styles\";\nimport { getFadeBackgroundColorValue, getFadeColorSet } from \"./helpers\";\nimport type { FadeWrapperProps, FadeWrapperRef } from \"./types\";\n\nconst Component = (\n {\n children,\n className,\n fadeColor,\n fadePosition = \"both\",\n showIndicator = false,\n onResize,\n ...otherProps\n }: FadeWrapperProps,\n ref: React.Ref<FadeWrapperRef>\n) => {\n // =========================================================================\n // CONST, STATE, REFS\n // =========================================================================\n const [showFadeLeft, setShowFadeLeft] = useState<boolean>(\n fadePosition === \"left\" || fadePosition === \"both\"\n );\n const [showFadeRight, setShowFadeRight] = useState<boolean>(\n fadePosition === \"right\" || fadePosition === \"both\"\n );\n\n const wrapperRef = useRef<HTMLDivElement>(null);\n const contentRef = useRef<HTMLDivElement>(null);\n const fadeLeftRef = useRef<HTMLDivElement>(null);\n const fadeRightRef = useRef<HTMLDivElement>(null);\n\n const fadeColorSet = getFadeColorSet(fadeColor);\n\n useApplyStyle(fadeLeftRef, {\n [styles.tokens.backgroundColor]: getFadeBackgroundColorValue(\n fadeColorSet.left,\n showIndicator\n ),\n });\n useApplyStyle(fadeRightRef, {\n [styles.tokens.backgroundColor]: getFadeBackgroundColorValue(\n fadeColorSet.right,\n showIndicator\n ),\n });\n\n // To scroll left when wrapper resizes\n useResizeDetector({\n onResize: handleResize,\n targetRef: wrapperRef,\n refreshMode: \"debounce\",\n refreshRate: 50,\n });\n\n useImperativeHandle(ref, () => {\n return {\n resize() {\n handleResize();\n },\n scrollToEnd() {\n const content = contentRef.current;\n const wrapper = wrapperRef.current;\n if (content && wrapper) {\n content.scrollLeft =\n content.scrollWidth - wrapper.offsetWidth;\n }\n },\n };\n });\n\n // =========================================================================\n // EFFECTS\n // =========================================================================\n const handleScroll = useCallback(() => {\n const wrapper = wrapperRef.current;\n const content = contentRef.current;\n\n if (wrapper && content && content.scrollWidth > wrapper.offsetWidth) {\n /**\n * NOTE: Used Math.round because we observe a 0.5 difference\n * even when we scroll to the end. This causes the fade right\n * to not be removed\n */\n setShowFadeRight(\n Math.round(content.scrollLeft) <\n content.scrollWidth - content.offsetWidth\n );\n setShowFadeLeft(content.scrollLeft >= 1);\n } else {\n setShowFadeRight(false);\n setShowFadeLeft(false);\n }\n }, []);\n\n const throttledScrollHandler = useMemo(() => {\n return throttle(handleScroll, 50);\n }, [handleScroll]);\n\n useEffect(() => {\n const content = contentRef.current;\n\n handleScroll();\n\n if (content) {\n content.addEventListener(\"scroll\", throttledScrollHandler);\n }\n\n return () => {\n if (content) {\n content.removeEventListener(\"scroll\", throttledScrollHandler);\n }\n\n throttledScrollHandler.cancel();\n };\n }, [handleScroll, throttledScrollHandler]);\n\n // =========================================================================\n // EVENT HANDLERS\n // =========================================================================\n\n function handleResize() {\n handleScroll();\n\n if (onResize) {\n onResize({\n content: contentRef.current!,\n wrapper: wrapperRef.current!,\n });\n }\n }\n\n // =========================================================================\n // RENDER FUNCTIONS\n // =========================================================================\n const renderFade = () => {\n return (\n <>\n {showFadeLeft && (\n <div\n ref={fadeLeftRef}\n className={clsx(styles.fade, styles.fadeLeft)}\n data-id=\"left-fade\"\n >\n {showIndicator && (\n <ClickableIcon\n className={clsx(\n styles.fadeIndicatorButton,\n styles.indicatorLeft\n )}\n data-id=\"left-fade-indicator-button\"\n >\n <ChevronLeftIcon />\n </ClickableIcon>\n )}\n </div>\n )}\n {showFadeRight && (\n <div\n ref={fadeRightRef}\n className={clsx(styles.fade, styles.fadeRight)}\n data-id=\"right-fade\"\n >\n {showIndicator && (\n <ClickableIcon\n className={clsx(\n styles.fadeIndicatorButton,\n styles.indicatorRight\n )}\n data-id=\"right-fade-indicator-button\"\n >\n <ChevronRightIcon />\n </ClickableIcon>\n )}\n </div>\n )}\n </>\n );\n };\n\n return (\n <div\n ref={wrapperRef}\n className={clsx(styles.wrapper, className)}\n {...otherProps}\n >\n <div ref={contentRef} className={styles.content}>\n {children}\n </div>\n {renderFade()}\n </div>\n );\n};\n\nexport const FadeWrapper = forwardRef(Component);\n"],"names":["FadeWrapper","forwardRef","_a","ref","children","className","fadeColor","fadePosition","showIndicator","onResize","otherProps","__rest","showFadeLeft","setShowFadeLeft","useState","showFadeRight","setShowFadeRight","wrapperRef","useRef","contentRef","fadeLeftRef","fadeRightRef","fadeColorSet","getFadeColorSet","useApplyStyle","styles.tokens","backgroundColor","getFadeBackgroundColorValue","left","right","useResizeDetector","handleResize","targetRef","refreshMode","refreshRate","useImperativeHandle","resize","scrollToEnd","content","current","wrapper","scrollLeft","scrollWidth","offsetWidth","handleScroll","useCallback","Math","round","throttledScrollHandler","useMemo","throttle","useEffect","addEventListener","removeEventListener","cancel","_jsxs","Object","assign","clsx","styles.wrapper","_jsx","styles.content","styles.fade","styles.fadeLeft","ClickableIcon","styles.fadeIndicatorButton","styles.indicatorLeft","ChevronLeftIcon","styles.fadeRight","styles.indicatorRight","ChevronRightIcon"],"mappings":"ysCAsBA,MA6LaA,EAAcC,GA7LT,CACdC,EASAC,SATAC,SACIA,EAAQC,UACRA,EAASC,UACTA,EAASC,aACTA,EAAe,OAAMC,cACrBA,GAAgB,EAAKC,SACrBA,GAAQP,EACLQ,EAAUC,EAAAT,EAPjB,gFAcA,MAAOU,EAAcC,GAAmBC,EACnB,SAAjBP,GAA4C,SAAjBA,IAExBQ,EAAeC,GAAoBF,EACrB,UAAjBP,GAA6C,SAAjBA,GAG1BU,EAAaC,EAAuB,MACpCC,EAAaD,EAAuB,MACpCE,EAAcF,EAAuB,MACrCG,EAAeH,EAAuB,MAEtCI,EAAeC,EAAgBjB,GAErCkB,EAAcJ,EAAa,CACvB,CAACK,EAAcC,iBAAkBC,EAC7BL,EAAaM,KACbpB,KAGRgB,EAAcH,EAAc,CACxB,CAACI,EAAcC,iBAAkBC,EAC7BL,EAAaO,MACbrB,KAKRsB,EAAkB,CACdrB,SAAUsB,EACVC,UAAWf,EACXgB,YAAa,WACbC,YAAa,KAGjBC,EAAoBhC,GAAK,KACd,CACH,MAAAiC,GACIL,GACJ,EACA,WAAAM,GACI,MAAMC,EAAUnB,EAAWoB,QACrBC,EAAUvB,EAAWsB,QACvBD,GAAWE,IACXF,EAAQG,WACJH,EAAQI,YAAcF,EAAQG,YAE1C,MAOR,MAAMC,EAAeC,GAAY,KAC7B,MAAML,EAAUvB,EAAWsB,QACrBD,EAAUnB,EAAWoB,QAEvBC,GAAWF,GAAWA,EAAQI,YAAcF,EAAQG,aAMpD3B,EACI8B,KAAKC,MAAMT,EAAQG,YACfH,EAAQI,YAAcJ,EAAQK,aAEtC9B,EAAgByB,EAAQG,YAAc,KAEtCzB,GAAiB,GACjBH,GAAgB,GACpB,GACD,IAEGmC,EAAyBC,GAAQ,IAC5BC,EAASN,EAAc,KAC/B,CAACA,IAwBJ,SAASb,IACLa,IAEInC,GACAA,EAAS,CACL6B,QAASnB,EAAWoB,QACpBC,QAASvB,EAAWsB,SAGhC,CA/BAY,GAAU,KACN,MAAMb,EAAUnB,EAAWoB,QAQ3B,OANAK,IAEIN,GACAA,EAAQc,iBAAiB,SAAUJ,GAGhC,KACCV,GACAA,EAAQe,oBAAoB,SAAUL,GAG1CA,EAAuBM,QAAQ,CAClC,GACF,CAACV,EAAcI,IAiElB,OACIO,EAAA,MAAAC,OAAAC,OAAA,CACItD,IAAKc,EACLZ,UAAWqD,EAAKC,EAAgBtD,IAC5BK,EAAU,CAAAN,SAAA,CAEdwD,EAAA,MAAA,CAAKzD,IAAKgB,EAAYd,UAAWwD,EAAczD,SAC1CA,IAlDLmD,eACK3C,GACGgD,EAAA,MAAA,CACIzD,IAAKiB,EACLf,UAAWqD,EAAKI,EAAaC,GAAgB,UACrC,YAAW3D,SAElBI,GACGoD,EAACI,EAAa,CACV3D,UAAWqD,EACPO,EACAC,GACH,UACO,6BAA4B9D,SAEpCwD,EAACO,EAAe,CAAA,OAK/BpD,GACG6C,EAAA,MAAA,CACIzD,IAAKkB,EACLhB,UAAWqD,EAAKI,EAAaM,aACrB,aAAYhE,SAEnBI,GACGoD,EAACI,EAAa,CACV3D,UAAWqD,EACPO,EACAI,aAEI,8BAA6BjE,SAErCwD,EAACU,EAAgB,CAAA,aAmBnC"}
@@ -19,4 +19,5 @@ export interface FadeWrapperProps {
19
19
  }
20
20
  export interface FadeWrapperRef {
21
21
  resize: () => void;
22
+ scrollToEnd: () => void;
22
23
  }
@@ -1,3 +1,8 @@
1
+ /**
2
+ * Generates CSS for a styled scrollbar. Pass "vertical" for panels that scroll
3
+ * vertically and "horizontal" for containers that scroll horizontally.
4
+ */
5
+ export declare const scrollbarCss: (axis: "vertical" | "horizontal") => string;
1
6
  /**
2
7
  * Generates CSS for limiting text to a specified number of lines after which it
3
8
  * is truncated with an ellipsis.
@@ -0,0 +1,167 @@
1
+ ---
2
+ name: "fds-build"
3
+ description: "Use when building with @lifesg/react-design-system v4. Covers composition rules, correct import patterns, token usage, and component discovery."
4
+ metadata:
5
+ version: "4.0.0-alpha"
6
+ audience: external
7
+ category: design-system
8
+ ---
9
+
10
+ # Flagship Design System — Agent Skill
11
+
12
+ You are helping build web applications with **`@lifesg/react-design-system`** for government digital products.
13
+
14
+ ---
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ npm install @lifesg/react-design-system @lifesg/react-icons @floating-ui/react
20
+ ```
21
+
22
+ Do not install `styled-components` — v4 uses CSS Modules for custom styles.
23
+
24
+ Peer deps: `react` + `react-dom` (^17, ^18, or ^19).
25
+
26
+ **If the project is new or missing setup** — read `./resources/setup/setup.md` for the full CSS/ThemeProvider/Vite walkthrough before writing any component code.
27
+
28
+ ---
29
+
30
+ ## ThemeProvider
31
+
32
+ Wrap your app in `ThemeProvider` to apply the correct theme CSS variables and enable dark/light mode.
33
+
34
+ ```tsx
35
+ import { ThemeProvider } from "@lifesg/react-design-system/theme";
36
+ import "@lifesg/react-design-system/theme/styles/lifesg.css";
37
+
38
+ export default function App() {
39
+ return <ThemeProvider theme="lifesg">{/* your app */}</ThemeProvider>;
40
+ }
41
+ ```
42
+
43
+ Theme is a **string**: `"lifesg"` · `"bookingsg"` · `"ccube"` · `"mylegacy"` · `"oneservice"` · `"pa"` · `"supportgowhere"` · `"sgw-digital-lobby"` · `"careercompass"` · `"rbs"` · `"imda"` · `"spf"` · `"smgs"` · `"a11y-playground"`
44
+
45
+ Dark/light mode: `mode` prop defaults to `"auto"` (OS preference). Override with `mode="light"` or `mode="dark"`.
46
+
47
+ ---
48
+
49
+ ## Component imports
50
+
51
+ Always use subpath imports:
52
+
53
+ ```tsx
54
+ import { Button } from "@lifesg/react-design-system/button";
55
+ import { Form } from "@lifesg/react-design-system/form";
56
+ ```
57
+
58
+ ---
59
+
60
+ ## Page structure
61
+
62
+ Every page must have `Navbar` at the top and `Footer` at the bottom.
63
+
64
+ ```tsx
65
+ import { Navbar } from "@lifesg/react-design-system/navbar";
66
+ import { Footer } from "@lifesg/react-design-system/footer";
67
+
68
+ <Navbar
69
+ resources={{ primary: { brandName: "MyService", logoSrc: "/logo.svg" } }}
70
+ items={{ desktop: [{ id: "home", children: "Home", href: "/" }] }}
71
+ />;
72
+ {
73
+ /* page content */
74
+ }
75
+ <Footer />;
76
+ ```
77
+
78
+ ---
79
+
80
+ ## Design tokens
81
+
82
+ Tokens are **plain CSS variable strings** — use directly in CSS Modules or inline styles.
83
+
84
+ Prefer CSS Modules for layout and responsive styles:
85
+
86
+ ```css
87
+ /* Component.module.css */
88
+ .card {
89
+ background: var(--fds-colour-bg-strong);
90
+ padding: var(--fds-spacing-24);
91
+ border-radius: var(--fds-radius-md);
92
+
93
+ :global(body.fds-breakpoint-lg-min) & {
94
+ padding: var(--fds-spacing-32);
95
+ }
96
+ }
97
+ ```
98
+
99
+ > **CSS Modules gotcha:** always use `:global(body.fds-breakpoint-lg-min)` — never `:where(body.fds-breakpoint-lg-min)`. CSS Modules scopes every class selector it finds; `:global()` opts the breakpoint class out of scoping so it matches the runtime class `ThemeProvider` adds to `document.body`.
100
+
101
+ Inline styles work for simple one-off values:
102
+
103
+ ```tsx
104
+ import { Colour, Spacing } from "@lifesg/react-design-system/theme";
105
+
106
+ <div style={{ color: Colour.text, padding: Spacing["spacing-24"] }} />;
107
+ ```
108
+
109
+ **Responsive breakpoints** use body class selectors added by `ThemeProvider`:
110
+
111
+ - `body.fds-breakpoint-lg-min` — viewport ≥ lg (769px)
112
+ - `body.fds-breakpoint-md-min` — viewport ≥ md (481px)
113
+ - `body.fds-breakpoint-xl-min` — viewport ≥ xl (1201px)
114
+
115
+ ---
116
+
117
+ ## Icons
118
+
119
+ 1. `@lifesg/react-icons` first — read `./resources/components/icon.md` to discover available icon names.
120
+ 2. `material-symbols` as fallback only.
121
+ 3. Never guess icon names — verify the import exists before using.
122
+
123
+ ---
124
+
125
+ ## Rules
126
+
127
+ - Sentence case for all UI text — "Save changes" not "Save Changes"
128
+ - DS components over raw HTML — `Button` not `<button>`, `Form.Input` not `<input>`
129
+ - `Form.*` fields: `label` always required; `errorMessage` only after validation fails; `readOnly` on review screens, not `disabled`
130
+ - Never hardcode `px` values — always use `Spacing` tokens
131
+ - All `Form.*` components import from `@lifesg/react-design-system/form`
132
+
133
+ ---
134
+
135
+ ## For AI agents — task → resource map
136
+
137
+ This skill is self-contained — all resource files live under `./resources/` relative to this file. No npm install or external lookup needed.
138
+
139
+ **Do not write component code without reading its resource file first.**
140
+
141
+ | Task | What to read |
142
+ | --------------------------------------------------------------------- | ----------------------------------------------------------- |
143
+ | New project / missing setup | `./resources/setup/setup.md` |
144
+ | Find the right component | `./catalogue.md` — search "Also known as" column by keyword |
145
+ | Use a component | `./resources/components/{name}.md` |
146
+ | Grid system, breakpoints, column widths | `./resources/components/layout.md` |
147
+ | Build a form page (single or multi-step) | `./resources/templates/form.md` |
148
+ | Build a listing / search results page | `./resources/templates/listing.md` |
149
+ | Handle loading / error / empty states | `./resources/patterns/states.md` |
150
+ | Token question (Colour, Spacing, Radius, Typography) | `./resources/foundations/tokens.md` |
151
+ | Spacing rhythm, visual hierarchy, card rules, settings page structure | `./resources/foundations/layout-composition-patterns.md` |
152
+
153
+ **Reading strategy:** read `catalogue.md` once for discovery, then fetch only the specific component `{name}.md` files you need. Do not pre-load all component files.
154
+
155
+ ---
156
+
157
+ ## Gotchas
158
+
159
+ | Symptom | Fix |
160
+ | ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
161
+ | No styles / wrong fonts | Read `./resources/setup/setup.md` — `main.css` CDN link or theme CSS import is likely missing |
162
+ | Footer doesn't stick to bottom on short pages | Apply a `className` to `ThemeProvider` and target that selector with `display: flex; flex-direction: column; flex: 1` |
163
+ | Responsive breakpoint rules don't fire | Using `:where(body.fds-breakpoint-*)` — replace with `:global(body.fds-breakpoint-*)` so CSS Modules doesn't scope the class |
164
+ | Theme CSS not loading | Vite resolves `node_modules` CSS automatically; other tools need CSS resolver config |
165
+ | `styled-components` import error | Remove it — v4 uses CSS Modules; use `className={styles.x}` with a `.module.css` file |
166
+ | `RadioButton` with children throws in React 19 | v4 `RadioButton` spreads all props (including `children`) onto `<input>` — React 19 rejects children on void elements. Wrap in `<label>` with text outside: `<label><RadioButton .../> Label text</label>` |
167
+ | Next.js App Router: no `#root` element | Next.js renders into `<body>` directly. Replace `#root { ... }` with those styles on `body`, and `#root > div` with `body > div`. See setup.md Next.js section |