@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
@@ -0,0 +1,104 @@
1
+ # Pagination
2
+
3
+ Import: `import { Pagination } from "@lifesg/react-design-system/pagination"`
4
+
5
+ ## When to use
6
+
7
+ Use below lists or tables when content is split across multiple pages.
8
+
9
+ ## Usage
10
+
11
+ ### Basic pagination (1-based indexing)
12
+
13
+ ```tsx
14
+ import { useState } from "react";
15
+ import { Pagination } from "@lifesg/react-design-system/pagination";
16
+
17
+ const ResultsList = () => {
18
+ const totalItems = 243;
19
+ const pageSize = 10;
20
+ // activePage is 1-based: page 1 is the first page, not page 0
21
+ const [activePage, setActivePage] = useState(1);
22
+
23
+ return (
24
+ <>
25
+ <MyResultsTable
26
+ items={results.slice(
27
+ (activePage - 1) * pageSize,
28
+ activePage * pageSize
29
+ )}
30
+ />
31
+ <Pagination
32
+ totalItems={totalItems}
33
+ pageSize={pageSize}
34
+ activePage={activePage}
35
+ onPageChange={(page) => setActivePage(page)} // page is 1-based
36
+ showFirstAndLastNav
37
+ />
38
+ </>
39
+ );
40
+ };
41
+ ```
42
+
43
+ ### With page size changer
44
+
45
+ `onPageSizeChange` receives `(page, pageSize)` — reset to page 1 when size changes.
46
+
47
+ ```tsx
48
+ const [activePage, setActivePage] = useState(1);
49
+ const [pageSize, setPageSize] = useState(10);
50
+
51
+ <Pagination
52
+ totalItems={500}
53
+ pageSize={pageSize}
54
+ activePage={activePage}
55
+ onPageChange={(page) => setActivePage(page)}
56
+ onPageSizeChange={(page, size) => {
57
+ setPageSize(size);
58
+ setActivePage(page); // component resets to page 1 on size change
59
+ }}
60
+ showFirstAndLastNav
61
+ showPageSizeChanger
62
+ />;
63
+ ```
64
+
65
+ ## Props — `PaginationProps`
66
+
67
+ | Prop | Type | Default | Description |
68
+ | --------------------- | -------------------------------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------- |
69
+ | `id` | `string` | — | — |
70
+ | `className` | `string` | — | — |
71
+ | `variant` | `"default" \| "full" \| "compact"` | "default" | Controls whether the pagination follows the responsive layout or always renders the full or compact layout. |
72
+ | `pageSize` | `number` | 10 | Number of items displayed per page. |
73
+ | `totalItems` \* | `number` | — | Total number of items across all pages. Used to compute the total page count. |
74
+ | `activePage` \* | `number` | — | The currently active 1-based page number. This is a controlled prop; update it in response to `onPageChange`. |
75
+ | `pageSizeOptions` | `PageSizeItemProps[]` | 10, 20, 30 per page | Options listed in the page-size changer dropdown. Only relevant when `showPageSizeChanger` is `true`. |
76
+ | `showFirstAndLastNav` | `boolean` | — | When `true`, renders first-page and last-page navigation buttons alongside the previous/next buttons. |
77
+ | `showPageSizeChanger` | `boolean` | false | When `true`, renders a dropdown in the full layout to change the `pageSize`. |
78
+ | `onPageChange` | `((page: number) => void)` | — | Called when the user navigates to a different page. |
79
+ | `onPageSizeChange` | `((page: number, pageSize: number) => void)` | — | Called when the user selects a different page size from the dropdown and `showPageSizeChanger` is `true`. |
80
+
81
+ ## Props — `PageSizeItemProps`
82
+
83
+ | Prop | Type | Default | Description |
84
+ | ---------- | -------- | ------- | ----------- |
85
+ | `value` \* | `number` | — | — |
86
+ | `label` \* | `string` | — | — |
87
+
88
+ ## Rules
89
+
90
+ - `activePage` is 1-based — the first page is `1`, not `0`.
91
+ - Always provide `onPageChange` to update `activePage` when the user changes page.
92
+ - Provide `onPageSizeChange` when `showPageSizeChanger` is enabled.
93
+ - Always center-align pagination below the results list.
94
+ - Place pagination below the results — never above.
95
+ - When total pages exceed 7, the component automatically shows an ellipsis — no additional props needed.
96
+ - The page size changer is only visible in the desktop viewport.
97
+ - The selected option in the page size changer takes precedence over `pageSize`.
98
+
99
+ ## Anti-patterns
100
+
101
+ - Do not use `showFirstAndLastNav` and `showPageSizeChanger` together unless the dataset is large enough to warrant both — avoid cluttering the interface with excessive controls.
102
+ - Do not left- or right-align pagination.
103
+ - Do not place pagination above the results.
104
+ - Do not use `pageSize` and a `pageSizeOptions` selection simultaneously without handling `onPageSizeChange` — the changer selection overrides `pageSize`.
@@ -0,0 +1,55 @@
1
+ # Pill
2
+
3
+ Import: `import { Pill } from "@lifesg/react-design-system/pill"`
4
+
5
+ ## When to use
6
+
7
+ Use to communicate a status — application state, approval outcome, or processing stage. For categories, filters, or tagging, use Tag instead.
8
+
9
+ ## Variants
10
+
11
+ | `type` | Usage |
12
+ | ----------- | -------------------------------------------------------- |
13
+ | `"outline"` | Subtle, secondary label — default for most uses |
14
+ | `"solid"` | Higher emphasis — use when the status needs to stand out |
15
+
16
+ | `colorType` | Semantic use |
17
+ | ----------- | ----------------------------- |
18
+ | `"green"` | Success, active, approved |
19
+ | `"red"` | Error, rejected, danger |
20
+ | `"yellow"` | Warning, pending, in progress |
21
+ | `"blue"` | Informational |
22
+ | `"grey"` | Neutral, inactive, draft |
23
+ | `"primary"` | Theme primary color |
24
+ | `"black"` | Default, no semantic meaning |
25
+
26
+ ## Usage
27
+
28
+ ```tsx
29
+ import { Pill } from "@lifesg/react-design-system/pill";
30
+
31
+ <Pill type="outline" colorType="green">
32
+ Approved
33
+ </Pill>;
34
+ ```
35
+
36
+ ## Props — `PillProps`
37
+
38
+ | Prop | Type | Default | Description |
39
+ | ----------- | --------------- | ------- | ------------------------------------ |
40
+ | `type` \* | `PillType` | — | Visual style variant for the pill. |
41
+ | `colorType` | `PillColorType` | "black" | Color theme applied to the pill. |
42
+ | `icon` | `JSX.Element` | — | Icon rendered before the label text. |
43
+
44
+ ## Rules
45
+
46
+ - Use color semantically — do not use `"red"` for non-error states.
47
+ - Use `"solid"` sparingly — reserve for statuses that need emphasis.
48
+ - Keep label text to 1–2 words — text does not wrap inside a pill.
49
+ - Pill is a static, non-interactive label — it does not accept `onClick`. Use Tag with `interactive` for clickable labels.
50
+
51
+ ## Anti-patterns
52
+
53
+ - Using Pill for categories or filters — use Tag instead.
54
+ - Using `"red"` for non-error states.
55
+ - Labels longer than 2–3 words — text overflow is not handled.
@@ -0,0 +1,80 @@
1
+ # Popover
2
+
3
+ Import: `import { Popover, withPopover } from "@lifesg/react-design-system/popover"`
4
+
5
+ ## When to use
6
+
7
+ Use to show supplementary information when a user interacts with (clicks or hovers over) an element. On mobile, the popover renders as a bottom modal sheet.
8
+
9
+ ## Variants
10
+
11
+ Two usage patterns are available:
12
+
13
+ - **`withPopover` HOC** — wraps an existing element to add popover behaviour.
14
+ - **`Popover` component** — a standalone popover that you control manually.
15
+
16
+ ## Usage
17
+
18
+ ```tsx
19
+ import { withPopover } from "@lifesg/react-design-system/popover";
20
+ import { Button } from "@lifesg/react-design-system/button";
21
+
22
+ {
23
+ /* Preferred: HOC wraps an existing element */
24
+ }
25
+ {
26
+ withPopover(<Button styleType="link">What is this?</Button>, {
27
+ content: "This field requires your NRIC number.",
28
+ trigger: "click",
29
+ });
30
+ }
31
+ ```
32
+
33
+ ## Props — `PopoverProps`
34
+
35
+ | Prop | Type | Default | Description |
36
+ | --------------- | ----------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------- |
37
+ | `children` \* | `string \| JSX.Element` | — | Content displayed inside the popover. A plain string is automatically wrapped in a `Typography.BodyMD` element. |
38
+ | `visible` | `boolean` | false | Controls whether the popover is visible. |
39
+ | `id` | `string` | — | — |
40
+ | `className` | `string` | — | — |
41
+ | `onMobileClose` | `(() => void)` | — | Called when the user dismisses the popover on a mobile viewport, either by tapping the overlay or the close button. |
42
+ | `ariaLabel` | `string` | "More information" | Accessible label for the `role="dialog"` element. |
43
+ | `overflow` | `PopoverOverflowType` | — | CSS `overflow` value applied to the popover card body. |
44
+ | `maxHeight` | `number` | — | Maximum height of the popover card in pixels. |
45
+
46
+ ## Props — `PopoverRenderProps`
47
+
48
+ | Prop | Type | Default | Description |
49
+ | ----------- | --------------------- | ------- | ------------------------------------------------------ |
50
+ | `overflow` | `PopoverOverflowType` | — | CSS `overflow` value applied to the popover card body. |
51
+ | `maxHeight` | `number` | — | Maximum height of the popover card in pixels. |
52
+
53
+ ## Props — `PopoverTriggerProps`
54
+
55
+ | Prop | Type | Default | Description |
56
+ | ------------------- | ------------------------------------------------------------------------------------ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
57
+ | `children` \* | `React.ReactNode` | — | The trigger element rendered inside a focusable wrapper div. |
58
+ | `popoverContent` \* | `\| string \| JSX.Element \| ((renderProps: PopoverRenderProps) => React.ReactNode)` | — | Content shown inside the popover panel. A string or JSX element is rendered inside the default `Popover`; a render function bypasses it for completely custom rendering. |
59
+ | `trigger` | `PopoverTriggerType` | "click" | Interaction that opens the popover. |
60
+ | `position` | `PopoverPosition` | "top" | Preferred placement of the popover relative to the trigger. |
61
+ | `id` | `string` | — | — |
62
+ | `zIndex` | `number` | — | Overrides the stacking context z-index for the floating popover layer. Falls back to the inherited floating context z-index when omitted. |
63
+ | `className` | `string` | — | — |
64
+ | `rootNode` | `RefObject<HTMLElement>` | document.body | The root element that contains the popover element. |
65
+ | `customOffset` | `number` | 16 | Distance in pixels between the trigger element and the popover panel. |
66
+ | `delay` | `\| { open?: number; close?: number }` | — | Open and close delays in milliseconds. Only applies when `trigger` is `"hover"`. |
67
+ | `enableFlip` | `boolean` | true | Allows the popover to flip to the opposite side when the preferred position has insufficient viewport space. |
68
+ | `enableResize` | `boolean` | false | Resizes the popover to fit the remaining vertical space in the viewport. |
69
+ | `overflow` | `PopoverOverflowType` | "auto" | CSS `overflow` value forwarded to the popover card when `enableResize` is active. |
70
+ | `popoverAriaLabel` | `string` | "More information" | Accessible label for the popover dialog element. |
71
+ | `triggerOnFocus` | `boolean` | false | Also opens the popover when the trigger element receives focus. |
72
+ | `isModal` | `boolean` | true | When `true`, focus is trapped inside the popover until dismissed. When `false`, the popover closes automatically once the user tabs away from both the trigger and the popover. |
73
+ | `onPopoverAppear` | `(() => void)` | — | Called when the popover becomes visible. |
74
+ | `onPopoverDismiss` | `(() => void)` | — | Called when the popover is dismissed. |
75
+
76
+ ## Rules
77
+
78
+ - Use `withPopover` for most cases — it handles trigger logic automatically.
79
+ - Use the `Popover` component directly when you need full manual control over visibility.
80
+ - On mobile viewports the Popover renders as a bottom modal sheet — provide `onMobileClose` to handle dismissal.
@@ -0,0 +1,47 @@
1
+ # ProgressIndicator
2
+
3
+ Import: `import { ProgressIndicator } from "@lifesg/react-design-system/progress-indicator"`
4
+
5
+ ## When to use
6
+
7
+ Use in multi-step forms or wizards to show the user's position within the flow.
8
+
9
+ ## Usage
10
+
11
+ ```tsx
12
+ import { ProgressIndicator } from "@lifesg/react-design-system/progress-indicator";
13
+
14
+ const steps = [
15
+ { label: "Personal details" },
16
+ { label: "Documents" },
17
+ { label: "Review" },
18
+ ];
19
+
20
+ <ProgressIndicator
21
+ steps={steps}
22
+ currentIndex={1}
23
+ displayExtractor={(step) => step.label}
24
+ />;
25
+ ```
26
+
27
+ ## Props
28
+
29
+ | Prop | Type | Default | Description |
30
+ | ------------------ | ----------------------- | ------- | -------------------------------------------------------------------------------------------------------- |
31
+ | `steps` \* | `T[]` | — | Ordered list of step items to render as progress bars and labels. |
32
+ | `currentIndex` \* | `number` | — | Zero-based index of the current active step. All steps at or before this index are visually highlighted. |
33
+ | `displayExtractor` | `((item: T) => string)` | — | Derives the display label for a step item. |
34
+ | `className` | `string` | — | — |
35
+ | `id` | `string` | — | — |
36
+
37
+ ## Rules
38
+
39
+ - `currentIndex` is zero-based — the first step is `0`.
40
+ - Always provide `displayExtractor` when `steps` contains objects — without it, step labels will not render.
41
+ - Update `currentIndex` as the user moves between steps.
42
+ - Do not use `fadeColor` or `fadePosition` — these props are deprecated and have no effect in v3.
43
+
44
+ ## Anti-patterns
45
+
46
+ - Do not use ProgressIndicator on single-step pages.
47
+ - Do not use it as a general progress bar for loading states — use a loading spinner or skeleton instead.
@@ -0,0 +1,66 @@
1
+ # RadioButton
2
+
3
+ Import: `import { RadioButton } from "@lifesg/react-design-system/radio-button"`
4
+
5
+ ## When to use
6
+
7
+ - User must choose exactly one option from a small set (typically 2–5 options).
8
+ - Options are mutually exclusive.
9
+
10
+ Prefer `Toggle` with `type="radio"` over bare `RadioButton` when building form inputs that need a visible card-style label container.
11
+
12
+ ## Variants
13
+
14
+ | `displaySize` | When to use |
15
+ | ------------- | ------------------------------ |
16
+ | `"default"` | Standard use |
17
+ | `"small"` | Dense layouts, inline controls |
18
+
19
+ ## Usage
20
+
21
+ ```tsx
22
+ import { RadioButton } from "@lifesg/react-design-system/radio-button";
23
+
24
+ <div role="radiogroup" aria-labelledby="group-label">
25
+ <label>
26
+ <RadioButton
27
+ name="plan"
28
+ value="basic"
29
+ checked={selected === "basic"}
30
+ onChange={(e) => {}}
31
+ />
32
+ Basic
33
+ </label>
34
+ <label>
35
+ <RadioButton
36
+ name="plan"
37
+ value="pro"
38
+ checked={selected === "pro"}
39
+ onChange={(e) => {}}
40
+ />
41
+ Pro
42
+ </label>
43
+ </div>;
44
+ ```
45
+
46
+ ## Props — `RadioButtonProps`
47
+
48
+ | Prop | Type | Default | Description |
49
+ | ----------------------- | ----------------- | --------- | ----------------------------------------------------------------------------------------------------------------------- |
50
+ | `checked` | `boolean` | — | The selected state of the component |
51
+ | `disabled` | `boolean` | — | — |
52
+ | `displaySize` | `RadioButtonSize` | "default" | Visual size of the radio button icon. |
53
+ | `focusableWhenDisabled` | `boolean` | — | When `true` and `disabled` is also `true`, the radio button remains keyboard-focusable. `onChange` is still suppressed. |
54
+
55
+ ## Rules
56
+
57
+ - Always wrap a group in `role="radiogroup"` with a visible group label (`aria-labelledby`).
58
+ - Always set the same `name` on all buttons in a group for native radio behaviour.
59
+ - Always pair each button with a visible label — wrap in `<label>` or use `aria-label`.
60
+ - Never render a single `RadioButton` in isolation — radio inputs only make sense in groups.
61
+
62
+ ## Anti-patterns
63
+
64
+ - Do not use `RadioButton` for binary yes/no questions in forms — use `Toggle` with `type="yes"` / `type="no"`.
65
+ - Do not use `RadioButton` when multiple selections are allowed — use `Checkbox` instead.
66
+ - Do not omit the `name` prop — without it, browsers cannot enforce mutual exclusivity natively.
@@ -0,0 +1,78 @@
1
+ # Schedule
2
+
3
+ Import: `import { Schedule } from "@lifesg/react-design-system/schedule"`
4
+
5
+ ## Sub-components
6
+
7
+ - `ScheduleEntityProps` — a service/resource row with its time slots
8
+ - `ScheduleSlotProps` — a single bookable timeslot cell
9
+ - `SchedulePopoverProps` — popover config for a slot
10
+
11
+ ## Props — `ScheduleEntityProps`
12
+
13
+ | Prop | Type | Default | Description |
14
+ | ---------- | --------------------- | ------- | ---------------------------------------------------------- |
15
+ | `id` \* | `string` | — | Unique identifier. |
16
+ | `name` \* | `string` | — | Display name shown in the column header. |
17
+ | `slots` \* | `ScheduleSlotProps[]` | — | All time slots belonging to this service across all dates. |
18
+
19
+ ## Props — `SchedulePopoverProps`
20
+
21
+ | Prop | Type | Default | Description |
22
+ | ------------ | -------------------------------------------------- | ------- | ------------------------------------------------------------ |
23
+ | `trigger` \* | `PopoverTriggerType` | — | Interaction type that opens the popover. |
24
+ | `content` \* | `string \| JSX.Element \| (() => React.ReactNode)` | — | The popover body. |
25
+ | `delay` | `{ open?: number; close?: number }` | — | Open and close delay in milliseconds. |
26
+ | `offset` | `number` | — | Distance in pixels between the slot element and the popover. |
27
+
28
+ ## Props — `ScheduleProps`
29
+
30
+ | Prop | Type | Default | Description |
31
+ | ------------------------- | ------------------------------------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------- |
32
+ | `id` | `string` | — | — |
33
+ | `className` | `string` | — | — |
34
+ | `view` | `ScheduleView` | "day" | The layout view. |
35
+ | `date` \* | `string` | — | The currently displayed date in `YYYY-MM-DD` format. Slots are filtered to match this date in the day view. |
36
+ | `emptyContentMessage` | `string` | — | Message shown inside the empty-state illustration when `serviceData` is empty. |
37
+ | `serviceData` \* | `ScheduleEntityProps[]` | — | Array of service entities to render. Each entity maps to a column in the week view, or to a carousel item in the day view. |
38
+ | `loading` | `boolean` | false | When `true` the schedule renders a loading animation in place of slot content. |
39
+ | `minTime` | `string` | "00:00" | Earliest time shown on the time axis, in `HH:mm` format. |
40
+ | `maxTime` | `string` | "23:59" | Latest time shown on the time axis, in `HH:mm` format. |
41
+ | `initialScrollTime` | `string` | — | Time the schedule scrolls to on first render, in `HH:mm` format. |
42
+ | `minDate` | `string` | — | Earliest selectable date in the calendar picker, in `YYYY-MM-DD` format. |
43
+ | `maxDate` | `string` | — | Latest selectable date in the calendar picker, in `YYYY-MM-DD` format. |
44
+ | `onPreviousDayClick` \* | `(currentDate: string) => void` | — | Called when the user navigates to the previous day. |
45
+ | `onNextDayClick` \* | `(currentDate: string) => void` | — | Called when the user navigates to the next day. |
46
+ | `onCalendarDateSelect` \* | `(currentDate: string) => void` | — | Called when the user picks a date from the calendar popover. |
47
+ | `onTodayClick` \* | `() => void` | — | Called when the user clicks the "Today" button in the header. |
48
+ | `onEmptySlotClick` | `((slot: ScheduleEmptySlotProps) => void)` | — | Called when the user clicks a cell without any time slots in the day view. |
49
+ | `emptySlotPopover` | `\| ((slot: ScheduleEmptySlotProps) => SchedulePopoverProps)` | — | When provided, a popover is attached to each empty slot in the day view instead of calling `onEmptySlotClick` directly. |
50
+ | `blockedMessage` | `string` | — | Message displayed inside blocked slots. |
51
+ | `onClickHiddenSlots` | `((hiddenServices: string[]) => void)` | — | Called in the week view when the user clicks a cell that contains hidden overflow slots. |
52
+
53
+ ## Props — `ScheduleSlotProps`
54
+
55
+ | Prop | Type | Default | Description |
56
+ | --------------- | ------------------------------------------------------------- | ------- | ---------------------------------------------------------- |
57
+ | `id` \* | `string` | — | Unique identifier for the slot. |
58
+ | `startTime` \* | `string` | — | Slot start time in `HH:mm` format. |
59
+ | `endTime` \* | `string` | — | Slot end time in `HH:mm` format. |
60
+ | `status` \* | `ScheduleCellType` | — | Visual state of the slot. |
61
+ | `capacity` | `number` | — | Total number of bookable places in this slot. |
62
+ | `date` \* | `string` | — | Date this slot belongs to in `YYYY-MM-DD` format. |
63
+ | `booked` | `number` | — | Number of confirmed bookings already made for this slot. |
64
+ | `customPopover` | `SchedulePopoverProps` | — | Attaches a popover to this slot with custom configuration. |
65
+ | `onClick` | `\| ((data: ScheduleSlotProps, e: React.MouseEvent) => void)` | — | Called when the user clicks this slot. |
66
+
67
+ ## Props — `ScheduleEmptySlotProps`
68
+
69
+ | Prop | Type | Default | Description |
70
+ | -------------- | -------- | ------- | ----------------------------------------------- |
71
+ | `startTime` \* | `string` | — | Slot start time in `HH:mm` format. |
72
+ | `endTime` \* | `string` | — | Slot end time in `HH:mm` format. |
73
+ | `name` \* | `string` | — | Name of the service this empty slot belongs to. |
74
+
75
+ ## Rules
76
+
77
+ - `date`, `serviceData`, `onPreviousDayClick`, and `onNextDayClick` are mandatory.
78
+ - Avoid combining a per-slot `onClick` with `customPopover` using `trigger: "click"` on the same slot — they conflict.
@@ -0,0 +1,183 @@
1
+ # Sidenav
2
+
3
+ Import: `import { Sidenav } from "@lifesg/react-design-system/sidenav"`
4
+
5
+ ## When to use
6
+
7
+ Use for section-level navigation in admin dashboards or portals. The Sidenav sits fixed to the left and fills the full viewport height below the Navbar, which is always present at the top.
8
+
9
+ Do not use for:
10
+
11
+ - Consumer-facing pages (use Navbar nav items or LocalNav instead)
12
+ - Simple single-section apps
13
+
14
+ ## Sub-components
15
+
16
+ ```
17
+ Sidenav ← fixed sidebar container
18
+ ├── Sidenav.Group ← logical section (optional separator)
19
+ │ ├── Sidenav.Item ← top-level nav link (no children)
20
+ │ ├── Sidenav.DrawerItem ← expandable item with sub-links
21
+ │ │ ├── Sidenav.DrawerSubitem
22
+ │ │ └── Sidenav.DrawerSubitem
23
+ │ └── Sidenav.Item
24
+ └── Sidenav.Group
25
+ └── Sidenav.Item
26
+ ```
27
+
28
+ `Sidenav.Group` is the only valid direct child of `Sidenav`. Use `Sidenav.Item` for leaf links, `Sidenav.DrawerItem` when a nav entry has sub-items. `Sidenav.DrawerSubitem` can only nest inside `Sidenav.DrawerItem`.
29
+
30
+ ## Variants
31
+
32
+ | Depth | Components used |
33
+ | -------- | -------------------------------------------- |
34
+ | 1 level | `Sidenav` → `Sidenav.Group` → `Sidenav.Item` |
35
+ | 2 levels | + `Sidenav.DrawerItem` |
36
+ | 3 levels | + `Sidenav.DrawerSubitem` |
37
+
38
+ ## Usage
39
+
40
+ ### 2-group, 3-item nav with full nesting
41
+
42
+ ```tsx
43
+ import { CalendarIcon } from "@lifesg/react-icons/calendar";
44
+ import { DocIcon } from "@lifesg/react-icons/doc";
45
+ import { Square2x2Icon } from "@lifesg/react-icons/square-2x2";
46
+ import { useState } from "react";
47
+ import { Sidenav } from "@lifesg/react-design-system/sidenav";
48
+
49
+ const AdminSidenav = () => {
50
+ const [selectedId, setSelectedId] = useState("dashboard");
51
+
52
+ return (
53
+ <Sidenav>
54
+ {/* Group 1 — top-level items with no sub-items: onClick fires */}
55
+ <Sidenav.Group separator>
56
+ <Sidenav.Item
57
+ id="dashboard"
58
+ title="Dashboard"
59
+ icon={<Square2x2Icon />}
60
+ selected={selectedId === "dashboard"}
61
+ onClick={(id) => setSelectedId(id)}
62
+ />
63
+ </Sidenav.Group>
64
+
65
+ {/* Group 2 — Reports has sub-items so onClick is omitted;
66
+ clicking it opens the drawer instead */}
67
+ <Sidenav.Group>
68
+ <Sidenav.Item
69
+ id="reports"
70
+ title="Reports"
71
+ icon={<DocIcon />}
72
+ selected={
73
+ selectedId === "monthly-reports" ||
74
+ selectedId === "annual-reports"
75
+ }
76
+ >
77
+ {/* DrawerItem with no children: onClick fires */}
78
+ <Sidenav.DrawerItem
79
+ id="monthly-reports"
80
+ title="Monthly reports"
81
+ onClick={(id) => setSelectedId(id)}
82
+ />
83
+ {/* DrawerItem with children: onClick is omitted */}
84
+ <Sidenav.DrawerItem
85
+ id="annual-reports"
86
+ title="Annual reports"
87
+ selected={selectedId === "annual-reports"}
88
+ >
89
+ <Sidenav.DrawerSubitem
90
+ id="annual-summary"
91
+ title="Summary"
92
+ onClick={(id) => setSelectedId(id)}
93
+ />
94
+ <Sidenav.DrawerSubitem
95
+ id="annual-detail"
96
+ title="Detailed breakdown"
97
+ onClick={(id) => setSelectedId(id)}
98
+ />
99
+ </Sidenav.DrawerItem>
100
+ </Sidenav.Item>
101
+ <Sidenav.Item
102
+ id="bookings"
103
+ title="Bookings"
104
+ icon={<CalendarIcon />}
105
+ selected={selectedId === "bookings"}
106
+ onClick={(id) => setSelectedId(id)}
107
+ />
108
+ </Sidenav.Group>
109
+ </Sidenav>
110
+ );
111
+ };
112
+ ```
113
+
114
+ Key wiring notes:
115
+
116
+ - `onClick` on `Sidenav.Item` is only attached when the item has **no** `children` — if children are present, clicking opens the drawer instead and `onClick` is ignored.
117
+ - The same rule applies to `Sidenav.DrawerItem`: omit `onClick` when it wraps `DrawerSubitem` children.
118
+ - Track `selectedId` in parent state; pass `selected={selectedId === id}` down to whichever level is active.
119
+ - `separator` on `Sidenav.Group` draws a dividing line below that group.
120
+
121
+ ## Props — `SidenavProps`
122
+
123
+ | Prop | Type | Default | Description |
124
+ | ------------- | ----------------- | --------- | ------------------------------------------------------------------------- |
125
+ | `fixed` | `boolean` | true | Pins the sidebar to the left edge of the viewport while the page scrolls. |
126
+ | `children` \* | `React.ReactNode` | — | Navigation groups and items. Accepts `Sidenav.Group` elements. |
127
+ | `aria-label` | `string` | "Sidebar" | Accessible label announced for the sidebar navigation landmark. |
128
+ | `id` | `string` | — | — |
129
+ | `className` | `string` | — | — |
130
+
131
+ ## Props — `SidenavGroupProps`
132
+
133
+ | Prop | Type | Default | Description |
134
+ | ------------- | ----------------- | ------- | ------------------------------------------------------------------- |
135
+ | `separator` | `boolean` | — | Renders a horizontal divider after the last item in the group. |
136
+ | `children` \* | `React.ReactNode` | — | Navigation items within the group. Accepts `Sidenav.Item` elements. |
137
+ | `aria-label` | `string` | — | Accessible label for this navigation group. |
138
+ | `id` | `string` | — | — |
139
+ | `className` | `string` | — | — |
140
+
141
+ ## Props — `SidenavItemProps`
142
+
143
+ | Prop | Type | Default | Description |
144
+ | ----------- | ------------------------ | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
145
+ | `title` \* | `string` | — | Label displayed below the icon. |
146
+ | `icon` \* | `React.ReactNode` | — | Icon displayed above the title. |
147
+ | `selected` | `boolean` | — | Sets the item as initially selected on mount. |
148
+ | `onClick` | `((id: string) => void)` | — | Called when the item is activated and it has no drawer children. When `children` are provided, clicking opens the drawer without invoking this callback. |
149
+ | `children` | `React.ReactNode` | — | Drawer content displayed to the right of the sidebar when the item is active. Accepts `Sidenav.DrawerItem` elements. |
150
+ | `id` | `string` | — | — |
151
+ | `className` | `string` | — | — |
152
+
153
+ ## Props — `SidenavDrawerItemProps`
154
+
155
+ | Prop | Type | Default | Description |
156
+ | ----------- | ------------------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
157
+ | `title` \* | `string` | — | The displayed label. |
158
+ | `children` | `React.ReactNode` | — | Sub-level entries for this section. Accepts `Sidenav.DrawerSubitem` elements. When provided, clicking toggles the collapsed/expanded state instead of invoking `onClick`. |
159
+ | `onClick` | `((id: string) => void)` | — | Called when the item is activated and it has no sub-item children. |
160
+ | `id` | `string` | — | — |
161
+ | `className` | `string` | — | — |
162
+
163
+ ## Props — `SidenavDrawerSubitemProps`
164
+
165
+ | Prop | Type | Default | Description |
166
+ | ----------- | ------------------------ | ------- | -------------------------------------- |
167
+ | `title` \* | `string` | — | The displayed label. |
168
+ | `onClick` | `((id: string) => void)` | — | Called when the sub-item is activated. |
169
+ | `id` | `string` | — | — |
170
+ | `className` | `string` | — | — |
171
+
172
+ ## Rules
173
+
174
+ - Always provide `icon` on `Sidenav.Item` — it is required for the collapsed state.
175
+ - Maximum depth is 3 levels: `Item` → `DrawerItem` → `DrawerSubitem`.
176
+ - Use `Sidenav.Group` with `separator` to visually separate unrelated sections.
177
+ - `onClick` on `Sidenav.Item` and `Sidenav.DrawerItem` only fires when the item has no children — do not attach click handlers to items that have sub-items.
178
+
179
+ ## Anti-patterns
180
+
181
+ - Do not omit `icon` on `Sidenav.Item` — the component is designed around icon + label.
182
+ - Do not nest deeper than 3 levels.
183
+ - Do not use `Sidenav` on consumer-facing pages — it is suited to admin portals.
@@ -0,0 +1,14 @@
1
+ # SingpassButton
2
+
3
+ Import: `import { SingpassButton } from "@lifesg/react-design-system/singpass-button"`
4
+
5
+ ## Props — `SingpassButtonProps`
6
+
7
+ | Prop | Type | Default | Description |
8
+ | ----------- | ------------------------- | -------------- | ----------------------------------------------------------------------- |
9
+ | `styleType` | `SingpassButtonStyleType` | "white-filled" | Visual style variant controlling the button background and logo colour. |
10
+ | `sizeType` | `SingpassButtonSizeType` | "default" | Size variant of the button. |
11
+
12
+ ## Rules
13
+
14
+ - Do not custom style — Singpass branding guidelines require using the component as-is.