@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.
- package/breadcrumb/breadcrumb.js +1 -1
- package/breadcrumb/breadcrumb.js.map +1 -1
- package/breadcrumb/breadcrumb.styles.d.ts +0 -8
- package/breadcrumb/breadcrumb.styles.js +1 -1
- package/breadcrumb/breadcrumb.styles.js.map +1 -1
- package/breadcrumb/breadcrumb.styles_1blzn8n.css +7 -0
- package/breadcrumb/types.d.ts +2 -13
- package/cjs/breadcrumb/breadcrumb.js +1 -1
- package/cjs/breadcrumb/breadcrumb.js.map +1 -1
- package/cjs/breadcrumb/breadcrumb.styles.js +1 -1
- package/cjs/breadcrumb/breadcrumb.styles.js.map +1 -1
- package/cjs/breadcrumb/breadcrumb.styles_1blzn8n.css +7 -0
- package/cjs/drawer/drawer.js +1 -1
- package/cjs/drawer/drawer.js.map +1 -1
- package/cjs/drawer/drawer.styles.js +1 -1
- package/cjs/drawer/drawer.styles.js.map +1 -1
- package/{drawer/drawer.styles_94hx3y.css → cjs/drawer/drawer.styles_a4bwe0.css} +6 -3
- package/cjs/external/dompurify/dist/purify.es.js +4 -2
- package/cjs/external/dompurify/dist/purify.es.js.map +1 -1
- package/cjs/file-upload/file-list/file-list.js +1 -1
- package/cjs/file-upload/file-list/file-list.js.map +1 -1
- package/cjs/file-upload/file-list/file-list.styles.js +1 -1
- package/cjs/file-upload/file-list/file-list.styles.js.map +1 -1
- package/cjs/file-upload/file-list/file-list.styles_1xbypnh.css +2 -0
- package/cjs/file-upload/file-list-item/file-item-actions.js +2 -0
- package/cjs/file-upload/file-list-item/file-item-actions.js.map +1 -0
- package/cjs/file-upload/file-list-item/file-item-actions.styles.js +2 -0
- package/cjs/file-upload/file-list-item/file-item-actions.styles.js.map +1 -0
- package/cjs/file-upload/file-list-item/file-item-actions.styles_1quulgy.css +8 -0
- package/cjs/file-upload/file-list-item/file-item-details.js +2 -0
- package/cjs/file-upload/file-list-item/file-item-details.js.map +1 -0
- package/cjs/file-upload/file-list-item/file-item-details.styles.js +2 -0
- package/cjs/file-upload/file-list-item/file-item-details.styles.js.map +1 -0
- package/cjs/file-upload/file-list-item/file-item-details.styles_1ce9akx.css +7 -0
- package/cjs/file-upload/file-list-item/file-list-item.js +1 -1
- package/cjs/file-upload/file-list-item/file-list-item.js.map +1 -1
- package/cjs/file-upload/file-list-item/file-list-item.styles.js +1 -1
- package/cjs/file-upload/file-list-item/file-list-item.styles.js.map +1 -1
- package/cjs/file-upload/file-list-item/file-list-item.styles_x3a500.css +10 -0
- package/cjs/file-upload/file-list-item/use-truncated-name.js +2 -0
- package/cjs/file-upload/file-list-item/use-truncated-name.js.map +1 -0
- package/cjs/local-nav/local-nav-dropdown/local-nav-dropdown.js +1 -1
- package/cjs/local-nav/local-nav-dropdown/local-nav-dropdown.js.map +1 -1
- package/cjs/local-nav/local-nav-dropdown/local-nav-dropdown.styles.js +1 -1
- package/cjs/local-nav/local-nav-dropdown/local-nav-dropdown.styles.js.map +1 -1
- 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
- package/cjs/local-nav/local-nav-menu/local-nav-menu.js +1 -1
- package/cjs/local-nav/local-nav-menu/local-nav-menu.js.map +1 -1
- package/cjs/local-nav/local-nav-menu/local-nav-menu.styles.js +1 -1
- package/cjs/local-nav/local-nav-menu/local-nav-menu.styles.js.map +1 -1
- package/cjs/local-nav/local-nav-menu/local-nav-menu.styles_67fcyc.css +8 -0
- package/cjs/menu/menu-content.js +1 -1
- package/cjs/menu/menu-content.js.map +1 -1
- package/cjs/menu/menu-content.styles.js +1 -1
- package/cjs/menu/menu-content.styles.js.map +1 -1
- package/cjs/menu/menu-content.styles_1r8oxtk.css +5 -0
- package/cjs/menu/menu-item.styles.js +1 -1
- package/cjs/menu/menu-item.styles.js.map +1 -1
- package/cjs/menu/{menu-item.styles_liyed8.css → menu-item.styles_eut2j9.css} +1 -1
- package/cjs/menu/menu-link.styles.js +1 -1
- package/cjs/menu/menu-link.styles.js.map +1 -1
- package/cjs/menu/menu-link.styles_rng7n7.css +2 -0
- package/cjs/menu/menu-section.js +1 -1
- package/cjs/menu/menu-section.js.map +1 -1
- package/cjs/menu/menu-section.styles.js +1 -1
- package/cjs/menu/menu-section.styles.js.map +1 -1
- package/cjs/menu/menu-section.styles_1n0ppxl.css +5 -0
- package/cjs/modal-v2/slots/slot-styles.js +1 -1
- package/cjs/modal-v2/slots/slot-styles.js.map +1 -1
- package/cjs/modal-v2/slots/{slot-styles_ayb9il.css → slot-styles_ygu6zc.css} +1 -1
- package/cjs/navbar/navbar-items.js +1 -1
- package/cjs/navbar/navbar-items.js.map +1 -1
- package/cjs/notification-banner/notification-banner-hoc.js +1 -1
- package/cjs/notification-banner/notification-banner-hoc.js.map +1 -1
- package/cjs/pagination/pagination.js +1 -1
- package/cjs/pagination/pagination.js.map +1 -1
- package/cjs/shared/dropdown-list/dropdown-label.js +1 -1
- package/cjs/shared/dropdown-list/dropdown-label.js.map +1 -1
- package/cjs/shared/fade-wrapper/fade-wrapper.js +1 -1
- package/cjs/shared/fade-wrapper/fade-wrapper.js.map +1 -1
- package/cjs/theme/tokens/colour.js +1 -1
- package/cjs/theme/tokens/colour.js.map +1 -1
- package/cjs/theme/tokens/radius.js +1 -1
- package/cjs/theme/tokens/radius.js.map +1 -1
- package/codemods/run-codemod.js +8 -2
- package/codemods/run-codemod.js.map +1 -1
- package/codemods/run-codemod.ts +13 -4
- package/drawer/drawer.d.ts +1 -1
- package/drawer/drawer.js +1 -1
- package/drawer/drawer.js.map +1 -1
- package/drawer/drawer.styles.d.ts +3 -0
- package/drawer/drawer.styles.js +1 -1
- package/drawer/drawer.styles.js.map +1 -1
- package/{cjs/drawer/drawer.styles_94hx3y.css → drawer/drawer.styles_a4bwe0.css} +6 -3
- package/drawer/types.d.ts +7 -0
- package/external/dompurify/dist/purify.es.js +4 -2
- package/external/dompurify/dist/purify.es.js.map +1 -1
- package/file-upload/file-list/file-list.js +1 -1
- package/file-upload/file-list/file-list.js.map +1 -1
- package/file-upload/file-list/file-list.styles.d.ts +0 -1
- package/file-upload/file-list/file-list.styles.js +1 -1
- package/file-upload/file-list/file-list.styles.js.map +1 -1
- package/file-upload/file-list/file-list.styles_1xbypnh.css +2 -0
- package/file-upload/file-list-item/file-item-actions.d.ts +30 -0
- package/file-upload/file-list-item/file-item-actions.js +2 -0
- package/file-upload/file-list-item/file-item-actions.js.map +1 -0
- package/file-upload/file-list-item/file-item-actions.styles.d.ts +6 -0
- package/file-upload/file-list-item/file-item-actions.styles.js +2 -0
- package/file-upload/file-list-item/file-item-actions.styles.js.map +1 -0
- package/file-upload/file-list-item/file-item-actions.styles_1quulgy.css +8 -0
- package/file-upload/file-list-item/file-item-details.d.ts +15 -0
- package/file-upload/file-list-item/file-item-details.js +2 -0
- package/file-upload/file-list-item/file-item-details.js.map +1 -0
- package/file-upload/file-list-item/file-item-details.styles.d.ts +7 -0
- package/file-upload/file-list-item/file-item-details.styles.js +2 -0
- package/file-upload/file-list-item/file-item-details.styles.js.map +1 -0
- package/file-upload/file-list-item/file-item-details.styles_1ce9akx.css +7 -0
- package/file-upload/file-list-item/file-list-item.d.ts +1 -7
- package/file-upload/file-list-item/file-list-item.js +1 -1
- package/file-upload/file-list-item/file-list-item.js.map +1 -1
- package/file-upload/file-list-item/file-list-item.styles.d.ts +6 -13
- package/file-upload/file-list-item/file-list-item.styles.js +1 -1
- package/file-upload/file-list-item/file-list-item.styles.js.map +1 -1
- package/file-upload/file-list-item/file-list-item.styles_x3a500.css +10 -0
- package/file-upload/file-list-item/types.d.ts +10 -3
- package/file-upload/file-list-item/use-truncated-name.d.ts +1 -0
- package/file-upload/file-list-item/use-truncated-name.js +2 -0
- package/file-upload/file-list-item/use-truncated-name.js.map +1 -0
- package/input-group/types.d.ts +4 -1
- package/input-select/types.d.ts +1 -0
- package/local-nav/local-nav-dropdown/local-nav-dropdown.js +1 -1
- package/local-nav/local-nav-dropdown/local-nav-dropdown.js.map +1 -1
- package/local-nav/local-nav-dropdown/local-nav-dropdown.styles.d.ts +3 -0
- package/local-nav/local-nav-dropdown/local-nav-dropdown.styles.js +1 -1
- package/local-nav/local-nav-dropdown/local-nav-dropdown.styles.js.map +1 -1
- 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
- package/local-nav/local-nav-menu/local-nav-menu.js +1 -1
- package/local-nav/local-nav-menu/local-nav-menu.js.map +1 -1
- package/local-nav/local-nav-menu/local-nav-menu.styles.d.ts +3 -0
- package/local-nav/local-nav-menu/local-nav-menu.styles.js +1 -1
- package/local-nav/local-nav-menu/local-nav-menu.styles.js.map +1 -1
- package/local-nav/local-nav-menu/local-nav-menu.styles_67fcyc.css +8 -0
- package/local-nav/types.d.ts +17 -0
- package/masked-input/types.d.ts +1 -2
- package/menu/menu-content.js +1 -1
- package/menu/menu-content.js.map +1 -1
- package/menu/menu-content.styles.d.ts +1 -0
- package/menu/menu-content.styles.js +1 -1
- package/menu/menu-content.styles.js.map +1 -1
- package/menu/menu-content.styles_1r8oxtk.css +5 -0
- package/menu/menu-item.styles.js +1 -1
- package/menu/menu-item.styles.js.map +1 -1
- package/menu/{menu-item.styles_liyed8.css → menu-item.styles_eut2j9.css} +1 -1
- package/menu/menu-link.styles.js +1 -1
- package/menu/menu-link.styles.js.map +1 -1
- package/menu/menu-link.styles_rng7n7.css +2 -0
- package/menu/menu-section.d.ts +1 -1
- package/menu/menu-section.js +1 -1
- package/menu/menu-section.js.map +1 -1
- package/menu/menu-section.styles.d.ts +7 -0
- package/menu/menu-section.styles.js +1 -1
- package/menu/menu-section.styles.js.map +1 -1
- package/menu/menu-section.styles_1n0ppxl.css +5 -0
- package/menu/menu.d.ts +1 -1
- package/menu/types.d.ts +10 -2
- package/modal-v2/slots/slot-styles.js +1 -1
- package/modal-v2/slots/slot-styles.js.map +1 -1
- package/modal-v2/slots/{slot-styles_ayb9il.css → slot-styles_ygu6zc.css} +1 -1
- package/navbar/navbar-items.js +1 -1
- package/navbar/navbar-items.js.map +1 -1
- package/navbar/types.d.ts +9 -0
- package/notification-banner/notification-banner-hoc.js +1 -1
- package/notification-banner/notification-banner-hoc.js.map +1 -1
- package/package.json +3 -4
- package/pagination/pagination.js +1 -1
- package/pagination/pagination.js.map +1 -1
- package/pagination/types.d.ts +8 -1
- package/scripts/cli.js +18 -0
- package/select-histogram/types.d.ts +1 -0
- package/shared/dropdown-list/dropdown-label.d.ts +2 -2
- package/shared/dropdown-list/dropdown-label.js +1 -1
- package/shared/dropdown-list/dropdown-label.js.map +1 -1
- package/shared/dropdown-list/types.d.ts +6 -2
- package/shared/fade-wrapper/fade-wrapper.js +1 -1
- package/shared/fade-wrapper/fade-wrapper.js.map +1 -1
- package/shared/fade-wrapper/types.d.ts +1 -0
- package/shared/styles/index.d.ts +5 -0
- package/skills/fds-build/SKILL.md +167 -0
- package/skills/fds-build/catalogue.md +181 -0
- package/skills/fds-build/evals/evals.json +519 -0
- package/skills/fds-build/resources/components/accordion.md +128 -0
- package/skills/fds-build/resources/components/alert.md +54 -0
- package/skills/fds-build/resources/components/animation.md +18 -0
- package/skills/fds-build/resources/components/avatar.md +10 -0
- package/skills/fds-build/resources/components/badge.md +18 -0
- package/skills/fds-build/resources/components/box-container.md +61 -0
- package/skills/fds-build/resources/components/breadcrumb.md +60 -0
- package/skills/fds-build/resources/components/button.md +74 -0
- package/skills/fds-build/resources/components/calendar.md +113 -0
- package/skills/fds-build/resources/components/card.md +43 -0
- package/skills/fds-build/resources/components/checkbox.md +53 -0
- package/skills/fds-build/resources/components/countdown-timer.md +20 -0
- package/skills/fds-build/resources/components/data-table.md +142 -0
- package/skills/fds-build/resources/components/date-navigator.md +25 -0
- package/skills/fds-build/resources/components/divider.md +23 -0
- package/skills/fds-build/resources/components/drawer.md +78 -0
- package/skills/fds-build/resources/components/error-display.md +137 -0
- package/skills/fds-build/resources/components/feedback-rating.md +61 -0
- package/skills/fds-build/resources/components/file-download.md +54 -0
- package/skills/fds-build/resources/components/file-upload.md +105 -0
- package/skills/fds-build/resources/components/filter.md +228 -0
- package/skills/fds-build/resources/components/footer.md +94 -0
- package/skills/fds-build/resources/components/form-date-input.md +85 -0
- package/skills/fds-build/resources/components/form-date-range-input.md +146 -0
- package/skills/fds-build/resources/components/form-e-signature.md +55 -0
- package/skills/fds-build/resources/components/form-input.md +65 -0
- package/skills/fds-build/resources/components/form-masked-input.md +69 -0
- package/skills/fds-build/resources/components/form-multi-select.md +100 -0
- package/skills/fds-build/resources/components/form-nested-multi-select.md +159 -0
- package/skills/fds-build/resources/components/form-nested-select.md +151 -0
- package/skills/fds-build/resources/components/form-otp-verification.md +175 -0
- package/skills/fds-build/resources/components/form-phone-number-input.md +87 -0
- package/skills/fds-build/resources/components/form-predictive-text-input.md +108 -0
- package/skills/fds-build/resources/components/form-select.md +103 -0
- package/skills/fds-build/resources/components/form-slider.md +116 -0
- package/skills/fds-build/resources/components/form-textarea.md +116 -0
- package/skills/fds-build/resources/components/form-time-range-picker.md +98 -0
- package/skills/fds-build/resources/components/form-timepicker.md +77 -0
- package/skills/fds-build/resources/components/form-unit-number-input.md +138 -0
- package/skills/fds-build/resources/components/fullscreen-image-carousel.md +159 -0
- package/skills/fds-build/resources/components/histogram-slider.md +41 -0
- package/skills/fds-build/resources/components/icon.md +167 -0
- package/skills/fds-build/resources/components/image-button.md +12 -0
- package/skills/fds-build/resources/components/input.md +51 -0
- package/skills/fds-build/resources/components/language-switcher.md +15 -0
- package/skills/fds-build/resources/components/layout.md +156 -0
- package/skills/fds-build/resources/components/link-list.md +120 -0
- package/skills/fds-build/resources/components/local-nav.md +119 -0
- package/skills/fds-build/resources/components/masthead.md +14 -0
- package/skills/fds-build/resources/components/menu.md +109 -0
- package/skills/fds-build/resources/components/modal.md +150 -0
- package/skills/fds-build/resources/components/navbar.md +270 -0
- package/skills/fds-build/resources/components/notification-banner.md +106 -0
- package/skills/fds-build/resources/components/pagination.md +104 -0
- package/skills/fds-build/resources/components/pill.md +55 -0
- package/skills/fds-build/resources/components/popover.md +80 -0
- package/skills/fds-build/resources/components/progress-indicator.md +47 -0
- package/skills/fds-build/resources/components/radio-button.md +66 -0
- package/skills/fds-build/resources/components/schedule.md +78 -0
- package/skills/fds-build/resources/components/sidenav.md +183 -0
- package/skills/fds-build/resources/components/singpass-button.md +14 -0
- package/skills/fds-build/resources/components/smart-app-banner.md +28 -0
- package/skills/fds-build/resources/components/tab.md +107 -0
- package/skills/fds-build/resources/components/table.md +65 -0
- package/skills/fds-build/resources/components/tag.md +68 -0
- package/skills/fds-build/resources/components/text-list.md +44 -0
- package/skills/fds-build/resources/components/time-slot-bar-week.md +66 -0
- package/skills/fds-build/resources/components/time-slot-bar.md +57 -0
- package/skills/fds-build/resources/components/time-slot-week-view.md +70 -0
- package/skills/fds-build/resources/components/timeline.md +69 -0
- package/skills/fds-build/resources/components/timetable.md +123 -0
- package/skills/fds-build/resources/components/toast.md +72 -0
- package/skills/fds-build/resources/components/toggle.md +119 -0
- package/skills/fds-build/resources/components/typography.md +86 -0
- package/skills/fds-build/resources/components/uneditable-section.md +84 -0
- package/skills/fds-build/resources/foundations/layout-composition-patterns.md +53 -0
- package/skills/fds-build/resources/foundations/tokens.md +547 -0
- package/skills/fds-build/resources/patterns/states.md +300 -0
- package/skills/fds-build/resources/setup/setup.md +202 -0
- package/skills/fds-build/resources/templates/form.md +427 -0
- package/skills/fds-build/resources/templates/listing.md +435 -0
- package/theme/styles/a11y-playground.css +52 -40
- package/theme/styles/bookingsg.css +52 -40
- package/theme/styles/careercompass.css +52 -40
- package/theme/styles/ccube.css +52 -40
- package/theme/styles/imda.css +52 -40
- package/theme/styles/lifesg.css +52 -40
- package/theme/styles/mylegacy.css +52 -40
- package/theme/styles/oneservice.css +52 -40
- package/theme/styles/pa.css +28 -22
- package/theme/styles/rbs.css +52 -40
- package/theme/styles/sgw-digital-lobby.css +52 -40
- package/theme/styles/smgs.css +52 -40
- package/theme/styles/spf.css +28 -27
- package/theme/styles/sportsg-orange.css +52 -40
- package/theme/styles/supportgowhere.css +52 -40
- package/theme/styles/tote-board.css +52 -40
- package/theme/styles/vica.css +52 -40
- package/theme/styles/websg.css +52 -40
- package/theme/styles/wise.css +52 -40
- package/theme/styles/wogaa.css +52 -40
- package/theme/tokens/colour.d.ts +6 -0
- package/theme/tokens/colour.js +1 -1
- package/theme/tokens/colour.js.map +1 -1
- package/theme/tokens/radius.d.ts +3 -0
- package/theme/tokens/radius.js +1 -1
- package/theme/tokens/radius.js.map +1 -1
- package/breadcrumb/breadcrumb.styles_1nl4aua.css +0 -18
- package/cjs/breadcrumb/breadcrumb.styles_1nl4aua.css +0 -18
- package/cjs/file-upload/file-item-edit.js +0 -2
- package/cjs/file-upload/file-item-edit.js.map +0 -1
- package/cjs/file-upload/file-item-edit.styles.js +0 -2
- package/cjs/file-upload/file-item-edit.styles.js.map +0 -1
- package/cjs/file-upload/file-item-edit.styles_19bvztn.css +0 -11
- package/cjs/file-upload/file-list/file-list.styles_1w7so98.css +0 -3
- package/cjs/file-upload/file-list-item/file-list-item.styles_kgtfke.css +0 -17
- package/cjs/local-nav/local-nav-menu/local-nav-menu.styles_pq19dh.css +0 -5
- package/cjs/menu/menu-content.styles_p7ijll.css +0 -2
- package/cjs/menu/menu-link.styles_hfxoez.css +0 -2
- package/cjs/menu/menu-section.styles_g7my4d.css +0 -3
- package/file-upload/file-item-edit.d.ts +0 -14
- package/file-upload/file-item-edit.js +0 -2
- package/file-upload/file-item-edit.js.map +0 -1
- package/file-upload/file-item-edit.styles.d.ts +0 -9
- package/file-upload/file-item-edit.styles.js +0 -2
- package/file-upload/file-item-edit.styles.js.map +0 -1
- package/file-upload/file-item-edit.styles_19bvztn.css +0 -11
- package/file-upload/file-list/file-list.styles_1w7so98.css +0 -3
- package/file-upload/file-list-item/file-list-item.styles_kgtfke.css +0 -17
- package/local-nav/local-nav-menu/local-nav-menu.styles_pq19dh.css +0 -5
- package/menu/menu-content.styles_p7ijll.css +0 -2
- package/menu/menu-link.styles_hfxoez.css +0 -2
- 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.
|