@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,108 @@
1
+ # Form.PredictiveTextInput
2
+
3
+ Import: `import { Form } from "@lifesg/react-design-system/form"`
4
+
5
+ ## When to use
6
+
7
+ - The option set is too large for a static dropdown and must be fetched dynamically (e.g. address lookup, postal code search, product search, large dataset autocomplete).
8
+ - Users need to type to narrow down results before selecting.
9
+
10
+ ## Usage
11
+
12
+ ### Location search — three extractors working together
13
+
14
+ ```tsx
15
+ import { useState } from "react";
16
+ import { Form } from "@lifesg/react-design-system/form";
17
+
18
+ interface Location {
19
+ id: string;
20
+ name: string;
21
+ address: string;
22
+ }
23
+
24
+ const LocationSearchExample = () => {
25
+ const [selected, setSelected] = useState<Location | undefined>();
26
+
27
+ const fetchOptions = async (input: string): Promise<Location[]> => {
28
+ const res = await api.searchLocations(input);
29
+ return res.results;
30
+ };
31
+
32
+ return (
33
+ <Form.PredictiveTextInput<Location, string>
34
+ label="Search location"
35
+ minimumCharacters={3} // fetch only after 3 chars typed
36
+ fetchOptions={fetchOptions}
37
+ // Populates the text field when an option is selected
38
+ valueExtractor={(item) => item.name}
39
+ // Populates each row in the dropdown list
40
+ listExtractor={(item) => `${item.name} — ${item.address}`}
41
+ // Displays the chosen value as a chip / confirmation label
42
+ displayValueExtractor={(item) => item.name}
43
+ selectedOption={selected}
44
+ onSelectOption={(item) => setSelected(item ?? undefined)}
45
+ />
46
+ );
47
+ };
48
+ ```
49
+
50
+ **How the three extractors divide responsibility:**
51
+
52
+ | Extractor | When it runs | What it renders |
53
+ | ----------------------- | -------------------------- | -------------------------------- |
54
+ | `listExtractor` | While the dropdown is open | Each option row in the list |
55
+ | `valueExtractor` | After selection | The text field value |
56
+ | `displayValueExtractor` | After selection | The chip / confirmed-value label |
57
+
58
+ Debounce `fetchOptions` internally (e.g. with `setTimeout` or a library) — the component calls it on every character change.
59
+
60
+ ## Props
61
+
62
+ | Prop | Type | Default | Description |
63
+ | ----------------------- | ----------------------------------------------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
64
+ | `id` | `string` | — | — |
65
+ | `className` | `string` | — | — |
66
+ | `minimumCharacters` | `number` | 3 | Minimum number of characters the user must type before `fetchOptions` is called and the dropdown opens. |
67
+ | `placeholder` | `string` | "Enter here..." | Placeholder text shown inside the input when it is empty. |
68
+ | `readOnly` | `boolean` | — | — |
69
+ | `disabled` | `boolean` | — | — |
70
+ | `error` | `boolean` | — | Applies error styling to the input wrapper. |
71
+ | `selectedOption` | `T` | — | The currently confirmed option. Updating this prop resets the displayed text and clears the dropdown options list. Pass `undefined` to clear the selection. |
72
+ | `alignment` | `DropdownAlignmentType` | — | Horizontal alignment of the dropdown relative to the input element. |
73
+ | `dropdownZIndex` | `number` | — | CSS `z-index` applied to the dropdown overlay. |
74
+ | `dropdownRootNode` | `RefObject<HTMLElement>` | document.body | The root element that contains the dropdown element. |
75
+ | `dropdownWidth` | `string` | — | Custom width for the dropdown. When specified, the dropdown uses this width instead of matching the trigger element width. |
76
+ | `fetchOptions` \* | `(input: string) => Promise<T[]>` | — | Async function called with the current input string to retrieve matching options. A rejected promise triggers the error state in the dropdown. |
77
+ | `valueExtractor` | `((item: T) => V)` | — | Derives the submitted value `V` from a selected item `T`. |
78
+ | `listExtractor` | `((item: T) => string \| ListItemDisplayProps)` | — | Derives the display label for each option in the dropdown list. |
79
+ | `displayValueExtractor` | `((option: T) => string)` | — | Derives the text shown in the input field after an option is selected. |
80
+ | `onSelectOption` | `\| ((option: T, extractedValue: V) => void)` | — | Called when the user selects an option from the dropdown or clears the field. |
81
+ | `label` | `FormLabelProps \| string` | — | Label rendered above the input. Accepts a plain string or a `FormLabelProps` object to provide additional configuration. |
82
+ | `errorMessage` | `string \| React.ReactNode` | — | Inline error text rendered below the input. |
83
+ | `layoutType` | `FormElementLayoutType` | — | Controls whether the container uses flex layout or a responsive column grid. |
84
+ | `xxlCols` | `ColSpan` | — | Column span at the `xxl` breakpoint. |
85
+ | `xlCols` | `ColSpan` | — | Column span at the `xl` breakpoint. |
86
+ | `lgCols` | `ColSpan` | — | Column span at the `lg` breakpoint. |
87
+ | `mdCols` | `ColSpan` | — | Column span at the `md` breakpoint. |
88
+ | `smCols` | `ColSpan` | — | Column span at the `sm` breakpoint. |
89
+ | `xsCols` | `ColSpan` | — | Column span at the `xs` breakpoint. |
90
+ | `xxsCols` | `ColSpan` | — | Column span at the `xxs` (smallest) breakpoint. Acts as the base value. All larger breakpoints fall back to this when their own prop is not set. |
91
+
92
+ ## Rules
93
+
94
+ - Always supply a `label` so the field is accessible.
95
+ - `fetchOptions` is required — it must be async and return an array of option objects.
96
+ - Debounce inside `fetchOptions` to avoid flooding the API on every keystroke.
97
+ - Set `minimumCharacters` to a value that produces useful results — `3` is the default, but adjust for your data source.
98
+ - Always provide `valueExtractor`, `listExtractor`, and `displayValueExtractor` — omitting any of these produces inconsistent display behaviour.
99
+ - Use `selectedOption` and `onSelectOption` for controlled selection.
100
+ - Set `errorMessage` when the selected option fails validation.
101
+ - `fetchOptions` is called on every keystroke after `minimumCharacters` is reached — debounce inside the function if needed.
102
+ - Generic type `T` is the option object shape; `V` is the extracted value type.
103
+
104
+ ## Anti-patterns
105
+
106
+ - Do not use when the full option list is small and known upfront — use `Form.Select` or `Form.MultiSelect` instead.
107
+ - Do not use `fetchOptions` without debouncing — each keystroke fires a new call.
108
+ - Do not omit extractor functions — the component cannot determine what to display without them.
@@ -0,0 +1,103 @@
1
+ # Form.Select
2
+
3
+ Import: `import { Form } from "@lifesg/react-design-system/form"`
4
+
5
+ ## When to use
6
+
7
+ Use when users must select exactly one option from a predefined list. For multiple selections use `Form.MultiSelect`. For hierarchical options use `Form.NestedSelect`.
8
+
9
+ ## Variants
10
+
11
+ | `variant` | When to use |
12
+ | ----------- | -------------------------------------------------------- |
13
+ | `"default"` | Standard form context |
14
+ | `"small"` | Compact layouts — inline filters, dense data-entry forms |
15
+
16
+ ## Usage
17
+
18
+ ```tsx
19
+ import { Form } from "@lifesg/react-design-system/form";
20
+
21
+ const options = [
22
+ { value: "sg", label: "Singapore" },
23
+ { value: "my", label: "Malaysia" },
24
+ ];
25
+
26
+ <Form.Select
27
+ label="Country"
28
+ options={options}
29
+ selectedOption={selected}
30
+ valueExtractor={(opt) => opt.value}
31
+ listExtractor={(opt) => opt.label}
32
+ displayValueExtractor={(opt) => opt.label}
33
+ onSelectOption={(opt) => {}}
34
+ />;
35
+ ```
36
+
37
+ ## Props
38
+
39
+ | Prop | Type | Default | Description |
40
+ | ---------------------------- | ------------------------------------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
41
+ | `readOnly` | `boolean` | — | — |
42
+ | `selectedOption` | `T` | — | The currently selected option. The component syncs to this value on each render. |
43
+ | `onSelectOption` | `((option: T, extractedValue: V) => void)` | — | Called when the user picks an item from the dropdown. |
44
+ | `displayValueExtractor` | `((option: T) => string)` | — | Derives the display string shown in the trigger for the selected option. Takes precedence over `valueExtractor` + `valueToStringFunction`. |
45
+ | `valueToStringFunction` | `((value: V) => string)` | — | Function to convert selected value into a display string |
46
+ | `renderCustomSelectedOption` | `((option: T) => JSX.Element)` | — | Renders a custom element for the currently selected option. |
47
+ | `onBlur` | `(() => void)` | — | Called when focus leaves both the trigger and dropdown. |
48
+ | `variant` | `DropdownVariantType` | "default" | Visual size variant of the trigger and dropdown. |
49
+ | `alignment` | `DropdownAlignmentType` | — | Aligns the dropdown relative to the trigger element. |
50
+ | `dropdownZIndex` | `number` | — | CSS `z-index` applied to the dropdown overlay. |
51
+ | `dropdownRootNode` | `RefObject<HTMLElement>` | document.body | The root element that contains the dropdown element. |
52
+ | `dropdownWidth` | `string` | — | Custom width for the dropdown. When specified, the dropdown uses this width instead of matching the trigger element width. |
53
+ | `customLabels` | `DropdownCustomLabelProps` | — | Overrides the default labels shown inside the dropdown and on the trigger. |
54
+ | `options` \* | `T[]` | — | The list of selectable options rendered in the dropdown. |
55
+ | `optionsLoadState` | `ItemsLoadStateType` | "success" | Indicates the load state of options fetched asynchronously. Renders a loading spinner, error retry, or the list depending on the state. |
56
+ | `optionTruncationType` | `TruncateType` | "end" | Controls how long option labels are truncated when they overflow. Specify `"none"` to display option labels in full. |
57
+ | `onShowOptions` | `(() => void)` | — | Called when the dropdown list becomes visible. |
58
+ | `onHideOptions` | `(() => void)` | — | Called when the dropdown list is hidden. |
59
+ | `onRetry` | `(() => void)` | — | Called when the user activates the retry action after a load failure. |
60
+ | `name` | `string` | — | Name attribute forwarded to the underlying button element. |
61
+ | `disabled` | `boolean` | — | — |
62
+ | `valueExtractor` | `((item: T) => V)` | — | Extracts the comparable value from an option item. |
63
+ | `listExtractor` | `((item: T) => string \| ListItemDisplayProps)` | — | Extracts the display text for a list item. |
64
+ | `isOptionDisabled` | `((item: T) => boolean)` | — | Returns `true` if the given option should be shown but not selectable. |
65
+ | `renderListItem` | `\| ((item: T, args: ListItemRenderArgs) => JSX.Element)` | — | Renders a fully custom element for each list item. |
66
+ | `renderCustomCallToAction` | `\| ((hideOptions: () => void, options: T[]) => JSX.Element)` | — | Renders a custom call-to-action at the bottom of the dropdown list. |
67
+ | `enableSearch` | `boolean` | — | Renders a search bar inside the dropdown when `true`. |
68
+ | `hideNoResultsDisplay` | `boolean` | — | Suppresses the built-in "no results" message when `true`. |
69
+ | `noResultsDescription` | `React.ReactNode` | — | — |
70
+ | `searchPlaceholder` | `string` | — | — |
71
+ | `searchFunction` | `((searchValue: string) => T[])` | — | Custom filter function invoked on each keystroke in the search input. |
72
+ | `onSearch` | `(() => void)` | — | Called when the user types in the search input. |
73
+ | `label` | `FormLabelProps \| string` | — | Label rendered above the input. Accepts a plain string or a `FormLabelProps` object to provide additional configuration. |
74
+ | `errorMessage` | `string \| React.ReactNode` | — | Inline error text rendered below the input. |
75
+ | `layoutType` | `FormElementLayoutType` | — | Controls whether the container uses flex layout or a responsive column grid. |
76
+ | `xxlCols` | `ColSpan` | — | Column span at the `xxl` breakpoint. |
77
+ | `xlCols` | `ColSpan` | — | Column span at the `xl` breakpoint. |
78
+ | `lgCols` | `ColSpan` | — | Column span at the `lg` breakpoint. |
79
+ | `mdCols` | `ColSpan` | — | Column span at the `md` breakpoint. |
80
+ | `smCols` | `ColSpan` | — | Column span at the `sm` breakpoint. |
81
+ | `xsCols` | `ColSpan` | — | Column span at the `xs` breakpoint. |
82
+ | `xxsCols` | `ColSpan` | — | Column span at the `xxs` (smallest) breakpoint. Acts as the base value. All larger breakpoints fall back to this when their own prop is not set. |
83
+
84
+ ## Rules
85
+
86
+ - Always provide `options` — the component has no meaning without it.
87
+ - Provide `valueExtractor` and `listExtractor` when the option objects are not plain strings — these are effectively required for typed option arrays.
88
+ - Always provide `displayValueExtractor` alongside `valueExtractor` when option objects have separate label/value fields. Without it, the component falls back to `valueExtractor`, showing the raw value (e.g. "sg") in the closed selector instead of the label (e.g. "Singapore").
89
+ - Use `optionsLoadState` to communicate async loading states — set to `"loading"` while fetching, `"failed"` on error (triggers retry UI).
90
+ - Use `enableSearch` when the list exceeds ~10 items.
91
+ - Provide `customLabels.searchPlaceholder` instead of the deprecated `searchPlaceholder` prop.
92
+ - Use `readOnly` on review screens — not `disabled`.
93
+ - Only override `dropdownZIndex` or `dropdownRootNode` when there are confirmed z-index or scroll conflicts.
94
+ - Use `dropdownWidth` when the dropdown must be wider than the trigger (e.g. long option text).
95
+ - `options` is the only mandatory component-specific prop.
96
+ - Only specify `dropdownRootNode` when the default `body` rendering causes scroll or z-index conflicts.
97
+
98
+ ## Anti-patterns
99
+
100
+ - Do not use `Form.Select` when users can choose more than one option — use `Form.MultiSelect`.
101
+ - Do not use the deprecated `searchPlaceholder` or `noResultsDescription` props directly — use `customLabels`.
102
+ - Do not set both `error` and `errorMessage`.
103
+ - Do not use `disabled` on review screens — use `readOnly`.
@@ -0,0 +1,116 @@
1
+ # Form.Slider
2
+
3
+ Import: `import { Form } from "@lifesg/react-design-system/form"`
4
+
5
+ ## When to use
6
+
7
+ - Selecting a single numeric value within a bounded range (e.g. budget, age, percentage, rating).
8
+ - When the relative position of a value matters more than typing an exact number.
9
+
10
+ ## Usage
11
+
12
+ ### Basic slider with display-only `onChange` and API-safe `onChangeEnd`
13
+
14
+ ```tsx
15
+ import { useState } from "react";
16
+ import { Form } from "@lifesg/react-design-system/form";
17
+
18
+ const BudgetSlider = () => {
19
+ const [displayValue, setDisplayValue] = useState(500);
20
+
21
+ const handleChange = (value: number) => {
22
+ // Safe: fires continuously during drag — update display only
23
+ setDisplayValue(value);
24
+ };
25
+
26
+ const handleChangeEnd = (value: number) => {
27
+ // Safe: fires once on mouse/touch release — trigger API calls here
28
+ fetchResults({ maxBudget: value });
29
+ };
30
+
31
+ return (
32
+ <Form.Slider
33
+ label={`Budget: $${displayValue}`}
34
+ min={0}
35
+ max={2000}
36
+ step={100}
37
+ value={displayValue}
38
+ onChange={handleChange}
39
+ onChangeEnd={handleChangeEnd}
40
+ showSliderLabels
41
+ sliderLabelPrefix="$"
42
+ showIndicatorLabel
43
+ indicatorLabelPrefix="$"
44
+ />
45
+ );
46
+ };
47
+ ```
48
+
49
+ **Never put API calls or expensive state updates in `onChange`** — it fires on every pixel of drag movement. Use `onChangeEnd` instead.
50
+
51
+ ### Custom track colours
52
+
53
+ `colors` takes a two-element array: `[leftTrackColor, rightTrackColor]`.
54
+
55
+ ```tsx
56
+ <Form.Slider
57
+ label="Risk level"
58
+ min={0}
59
+ max={10}
60
+ colors={["#4caf50", "#e0e0e0"]}
61
+ showSliderLabels
62
+ sliderLabelSuffix=" / 10"
63
+ />
64
+ ```
65
+
66
+ ## Props
67
+
68
+ | Prop | Type | Default | Description |
69
+ | ---------------------- | -------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
70
+ | `value` | `number` | 0 | The controlled value of the thumb. |
71
+ | `ariaLabel` | `string` | — | Accessible label for the thumb. |
72
+ | `onChange` | `((value: number) => void)` | — | Called on every selection change. |
73
+ | `onChangeEnd` | `((value: number) => void)` | — | Called when a thumb is released after selection is complete. |
74
+ | `className` | `string` | — | — |
75
+ | `id` | `string` | — | — |
76
+ | `min` | `number` | 0 | The lower bound of the slider range. |
77
+ | `max` | `number` | 100 | The upper bound of the slider range. |
78
+ | `step` | `number` | 1 | The interval between selectable values. |
79
+ | `disabled` | `boolean` | — | — |
80
+ | `readOnly` | `boolean` | — | — |
81
+ | `colors` | `(string)[]` | — | Colours applied to each track segment in order. The array length should equal to `numOfThumbs + 1`. |
82
+ | `showSliderLabels` | `boolean` | — | Renders label text at the minimum and maximum ends of the track. |
83
+ | `sliderLabelPrefix` | `string` | — | Text prepended to each slider label value. Ignored when `renderSliderLabel` is provided. |
84
+ | `sliderLabelSuffix` | `string` | — | Text appended to each slider label value. Ignored when `renderSliderLabel` is provided. |
85
+ | `showIndicatorLabel` | `boolean` | — | Renders a label above the slider showing the current selection. |
86
+ | `indicatorLabelPrefix` | `string` | — | Text prepended to the indicator label value. |
87
+ | `indicatorLabelSuffix` | `string` | — | Text appended to the indicator label value. |
88
+ | `renderSliderLabel` | `((value: number) => React.ReactNode)` | — | Custom renderer for the slider labels shown at the min and max track ends. Takes precedence over `sliderLabelPrefix` and `sliderLabelSuffix`. |
89
+ | `label` | `FormLabelProps \| string` | — | Label rendered above the input. Accepts a plain string or a `FormLabelProps` object to provide additional configuration. |
90
+ | `errorMessage` | `string \| React.ReactNode` | — | Inline error text rendered below the input. |
91
+ | `layoutType` | `FormElementLayoutType` | — | Controls whether the container uses flex layout or a responsive column grid. |
92
+ | `xxlCols` | `ColSpan` | — | Column span at the `xxl` breakpoint. |
93
+ | `xlCols` | `ColSpan` | — | Column span at the `xl` breakpoint. |
94
+ | `lgCols` | `ColSpan` | — | Column span at the `lg` breakpoint. |
95
+ | `mdCols` | `ColSpan` | — | Column span at the `md` breakpoint. |
96
+ | `smCols` | `ColSpan` | — | Column span at the `sm` breakpoint. |
97
+ | `xsCols` | `ColSpan` | — | Column span at the `xs` breakpoint. |
98
+ | `xxsCols` | `ColSpan` | — | Column span at the `xxs` (smallest) breakpoint. Acts as the base value. All larger breakpoints fall back to this when their own prop is not set. |
99
+
100
+ ## Rules
101
+
102
+ - Always supply a `label` so the field is accessible.
103
+ - Always set `min` and `max` to meaningful bounds for the use case.
104
+ - Use `step` to restrict values to meaningful increments (e.g. `step={500}` for a budget slider).
105
+ - Use `onChangeEnd` for expensive operations (API calls, heavy computations) — `onChange` fires on every pixel of movement.
106
+ - Enable `showSliderLabels` so users know the range bounds.
107
+ - Enable `showIndicatorLabel` so users can see the current value while dragging.
108
+ - Provide `ariaLabel` when the label alone does not fully describe the slider's purpose to screen readers.
109
+ - `onChange` fires continuously during drag; use `onChangeEnd` for expensive operations such as API calls.
110
+ - `colors` array format is `[leftTrack, rightTrack]`.
111
+
112
+ ## Anti-patterns
113
+
114
+ - Do not use `onChange` to trigger API calls — use `onChangeEnd` to avoid flooding the network.
115
+ - Do not use when the user needs to enter a precise value — prefer a number input in that case.
116
+ - Do not omit `min` and `max` — the defaults (0–100) may not be appropriate for the domain.
@@ -0,0 +1,116 @@
1
+ # Form.Textarea
2
+
3
+ Import: `import { Form } from "@lifesg/react-design-system/form"`
4
+
5
+ ## When to use
6
+
7
+ Use for freeform long-text entry — descriptions, comments, reasons, or any field where users need more than one line. Use `Form.Input` for single-line text.
8
+
9
+ ## Usage
10
+
11
+ ### 1. Basic controlled textarea
12
+
13
+ ```tsx
14
+ import { useState } from "react";
15
+ import { Form } from "@lifesg/react-design-system/form";
16
+
17
+ const [value, setValue] = useState("");
18
+
19
+ <Form.Textarea
20
+ label="Description"
21
+ value={value}
22
+ onChange={(e) => setValue(e.target.value)}
23
+ placeholder="Enter a description..."
24
+ rows={4}
25
+ />;
26
+ ```
27
+
28
+ ### 2. With `maxLength` — auto-renders a character counter
29
+
30
+ ```tsx
31
+ <Form.Textarea
32
+ label="Comments"
33
+ maxLength={200}
34
+ value={value}
35
+ onChange={(e) => setValue(e.target.value)}
36
+ />;
37
+ {
38
+ /* A counter "0 / 200" appears below automatically — no extra code needed */
39
+ }
40
+ ```
41
+
42
+ ### 3. With `renderCustomCounter`
43
+
44
+ The prop signature is `(maxLength: number, currentValueLength: number) => JSX.Element`. Note the argument order: `maxLength` first, then `currentValueLength`.
45
+
46
+ ```tsx
47
+ <Form.Textarea
48
+ label="Comments"
49
+ maxLength={200}
50
+ value={value}
51
+ onChange={(e) => setValue(e.target.value)}
52
+ renderCustomCounter={(maxLength, currentValueLength) => (
53
+ <span
54
+ style={{
55
+ color: currentValueLength > maxLength * 0.9 ? "red" : "inherit",
56
+ }}
57
+ >
58
+ {currentValueLength} of {maxLength} characters used
59
+ </span>
60
+ )}
61
+ />
62
+ ```
63
+
64
+ ### 4. With `transformValue`
65
+
66
+ `transformValue` runs on every input event and replaces the raw value before it is applied. Do not duplicate this logic in `onChange`.
67
+
68
+ ```tsx
69
+ <Form.Textarea
70
+ label="Reference code"
71
+ value={value}
72
+ onChange={(e) => setValue(e.target.value)}
73
+ transformValue={(raw) => raw.toUpperCase()}
74
+ maxLength={50}
75
+ />;
76
+ {
77
+ /* User types "abc" → stored value becomes "ABC" */
78
+ }
79
+ ```
80
+
81
+ ## Props
82
+
83
+ | Prop | Type | Default | Description |
84
+ | --------------------- | --------------------------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
85
+ | `errorMessage` | `string \| React.ReactNode` | — | Inline error text rendered below the textarea. Also sets the input into an error state. |
86
+ | `transformValue` | `((value: string) => string)` | — | Transforms user input on every change event. |
87
+ | `prefix` | `string` | — | A read-only string permanently displayed at the start of the textarea. |
88
+ | `renderCustomCounter` | `\| ((maxLength: number, currentValueLength: number) => JSX.Element)` | — | Replaces the built-in character counter displayed below the textarea. Called only when `maxLength` is provided. |
89
+ | `label` | `FormLabelProps \| string` | — | Label rendered above the input. Accepts a plain string or a `FormLabelProps` object to provide additional configuration. |
90
+ | `layoutType` | `FormElementLayoutType` | — | Controls whether the container uses flex layout or a responsive column grid. |
91
+ | `xxlCols` | `ColSpan` | — | Column span at the `xxl` breakpoint. |
92
+ | `xlCols` | `ColSpan` | — | Column span at the `xl` breakpoint. |
93
+ | `lgCols` | `ColSpan` | — | Column span at the `lg` breakpoint. |
94
+ | `mdCols` | `ColSpan` | — | Column span at the `md` breakpoint. |
95
+ | `smCols` | `ColSpan` | — | Column span at the `sm` breakpoint. |
96
+ | `xsCols` | `ColSpan` | — | Column span at the `xs` breakpoint. |
97
+ | `xxsCols` | `ColSpan` | — | Column span at the `xxs` (smallest) breakpoint. Acts as the base value. All larger breakpoints fall back to this when their own prop is not set. |
98
+
99
+ ## Rules
100
+
101
+ - Set `rows` to match the expected content length — a short reason field needs fewer rows than a free-text description.
102
+ - Set `resize="none"` when the textarea height is fixed by design to prevent layout disruption.
103
+ - Provide `maxLength` when a character limit must be enforced — the built-in counter appears automatically.
104
+ - Use `renderCustomCounter` only when the default counter format does not meet content requirements.
105
+ - Use `transformValue` for on-the-fly normalisation (e.g. forced capitalisation) — do not replicate this in `onChange`.
106
+ - Provide `errorMessage` rather than `error` alone.
107
+ - Setting native `maxLength` renders a character counter below the textarea automatically.
108
+ - `renderCustomCounter` overrides the default counter UI when `maxLength` is set.
109
+ - `error` is redundant when `errorMessage` is provided.
110
+ - `transformValue` runs on every input event — use for normalisation (e.g. uppercase conversion).
111
+
112
+ ## Anti-patterns
113
+
114
+ - Do not use `Form.Textarea` for single-line inputs — use `Form.Input`.
115
+ - Do not implement a manual character counter in consumer code when `maxLength` is set — the built-in counter handles it.
116
+ - Do not set both `error` and `errorMessage`.
@@ -0,0 +1,98 @@
1
+ # Form.TimeRangePicker
2
+
3
+ Import: `import { Form } from "@lifesg/react-design-system/form"`
4
+
5
+ ## When to use
6
+
7
+ - Collecting a start and end time from the user (e.g. operating hours, meeting slot, booking window).
8
+ - Use `variant="combobox"` with `interval` when users should pick from fixed time slots (e.g. every 30 minutes).
9
+ - Use `variant="dial"` (default) for freeform time selection.
10
+
11
+ ## Usage
12
+
13
+ ```tsx
14
+ import styles from "./MyForm.module.css";
15
+
16
+ <Form.TimeRangePicker className={styles.timeRangePicker} ... />
17
+ ```
18
+
19
+ ```css
20
+ /* MyForm.module.css */
21
+ :where(body.fds-breakpoint-lg-min) .timeRangePicker {
22
+ max-width: 24rem;
23
+ }
24
+ ```
25
+
26
+ Defaults to 4 columns wide at `lg` and above; full-width on mobile. Use `max-width` via a CSS Module class.
27
+
28
+ ## Combobox-variant-specific props
29
+
30
+ | Prop | Type | Default | Description |
31
+ | ------------------------ | -------- | ------- | ------------------------------------------------------------ |
32
+ | `interval` | `number` | `15` | Minutes between each dropdown option |
33
+ | `startLimit` | `string` | — | Earliest time shown in the dropdown |
34
+ | `endLimit` | `string` | — | Latest time shown in the dropdown |
35
+ | `initialScrollStartTime` | `string` | — | Time the start dropdown scrolls to when start field is empty |
36
+ | `initialScrollEndTime` | `string` | — | Time the end dropdown scrolls to when end field is empty |
37
+
38
+ ## TimeRangePickerValue
39
+
40
+ | Prop | Type | Description |
41
+ | ------- | -------- | ----------------------------------------------------------------------------------------------------------- |
42
+ | `start` | `string` | Start time. Dial 24hr: `"HH:mm"`, dial 12hr: `"hh:mmA"`. Combobox 24hr: `"HH:mm"`, combobox 12hr: `"h:mma"` |
43
+ | `end` | `string` | End time, same format as `start` |
44
+
45
+ ## Props
46
+
47
+ | Prop | Type | Default | Description |
48
+ | ------------------------ | ----------------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
49
+ | `className` | `string` | — | — |
50
+ | `id` | `string` | — | — |
51
+ | `style` | `React.CSSProperties` | — | — |
52
+ | `aria-labelledby` | `string` | — | ID of an element that labels both start and end time inputs. |
53
+ | `aria-describedby` | `string` | — | ID of an element that describes both start and end time inputs. |
54
+ | `aria-invalid` | `boolean` | — | — |
55
+ | `value` | `TimeRangePickerValue` | — | An object with `start` and `end` values as an empty string or a string based format. |
56
+ | `format` | `TimeRangePickerFormat` | "24hr" | The time display and input format. |
57
+ | `disabled` | `boolean` | — | — |
58
+ | `readOnly` | `boolean` | — | — |
59
+ | `error` | `boolean` | — | — |
60
+ | `variant` | `TimeRangePickerVariant` | "dial" | The picker interaction style. |
61
+ | `interval` | `number` | — | Minute interval between each option in the combobox dropdowns. |
62
+ | `startLimit` | `string` | — | Earliest selectable time shown in the combobox dropdowns. |
63
+ | `endLimit` | `string` | — | Latest selectable time shown in the combobox dropdowns. |
64
+ | `initialScrollStartTime` | `string` | — | The time that the start field dropdown scrolls to on open.. |
65
+ | `initialScrollEndTime` | `string` | — | The time that the end field dropdown scrolls to on open. |
66
+ | `alignment` | `DropdownAlignmentType` | — | Aligns the dropdown relative to the trigger element. |
67
+ | `dropdownZIndex` | `number` | — | CSS `z-index` applied to the dropdown overlay. |
68
+ | `dropdownRootNode` | `RefObject<HTMLElement>` | document.body | The root element that contains the dropdown element. |
69
+ | `onChange` | `((value: TimeRangePickerValue) => void)` | — | Called when the selected time range changes. |
70
+ | `onFocus` | `(() => void)` | — | Called when the field receives focus. |
71
+ | `onBlur` | `(() => void)` | — | Called when the field loses focus. |
72
+ | `label` | `FormLabelProps \| string` | — | Label rendered above the input. Accepts a plain string or a `FormLabelProps` object to provide additional configuration. |
73
+ | `errorMessage` | `string \| React.ReactNode` | — | Inline error text rendered below the input. |
74
+ | `layoutType` | `FormElementLayoutType` | — | Controls whether the container uses flex layout or a responsive column grid. |
75
+ | `xxlCols` | `ColSpan` | — | Column span at the `xxl` breakpoint. |
76
+ | `xlCols` | `ColSpan` | — | Column span at the `xl` breakpoint. |
77
+ | `lgCols` | `ColSpan` | — | Column span at the `lg` breakpoint. |
78
+ | `mdCols` | `ColSpan` | — | Column span at the `md` breakpoint. |
79
+ | `smCols` | `ColSpan` | — | Column span at the `sm` breakpoint. |
80
+ | `xsCols` | `ColSpan` | — | Column span at the `xs` breakpoint. |
81
+ | `xxsCols` | `ColSpan` | — | Column span at the `xxs` (smallest) breakpoint. Acts as the base value. All larger breakpoints fall back to this when their own prop is not set. |
82
+
83
+ ## Rules
84
+
85
+ - Always supply a `label` so the field is accessible.
86
+ - Pass `value` and `onChange` together for controlled usage; `onChange` receives a `TimeRangePickerValue`.
87
+ - Match `format` to the expected string format in `TimeRangePickerValue`.
88
+ - Use `startLimit`/`endLimit` with `combobox` variant to restrict available options to valid business hours.
89
+ - Set `errorMessage` rather than `error` alone.
90
+ - Use `dropdownRootNode` when inside a modal or scrollable container.
91
+ - Time string format differs between `dial` and `combobox` variants — see `TimeRangePickerValue` above.
92
+ - `interval`, `startLimit`, `endLimit`, and `initialScroll*` props only apply when `variant="combobox"`.
93
+
94
+ ## Anti-patterns
95
+
96
+ - Do not use for single time selection — use `Form.TimePicker` instead.
97
+ - Do not mix dial and combobox time string formats — the format differs between variants.
98
+ - Do not set combobox-specific props (`interval`, `startLimit`, `endLimit`, `initialScroll*`) when `variant="dial"`.