@lifesg/react-design-system 4.0.0-alpha.13 → 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 (100) hide show
  1. package/cjs/external/dompurify/dist/purify.es.js +4 -2
  2. package/cjs/external/dompurify/dist/purify.es.js.map +1 -1
  3. package/cjs/modal-v2/slots/slot-styles.js +1 -1
  4. package/cjs/modal-v2/slots/slot-styles.js.map +1 -1
  5. package/cjs/modal-v2/slots/{slot-styles_ayb9il.css → slot-styles_ygu6zc.css} +1 -1
  6. package/cjs/notification-banner/notification-banner-hoc.js +1 -1
  7. package/cjs/notification-banner/notification-banner-hoc.js.map +1 -1
  8. package/external/dompurify/dist/purify.es.js +4 -2
  9. package/external/dompurify/dist/purify.es.js.map +1 -1
  10. package/modal-v2/slots/slot-styles.js +1 -1
  11. package/modal-v2/slots/slot-styles.js.map +1 -1
  12. package/modal-v2/slots/{slot-styles_ayb9il.css → slot-styles_ygu6zc.css} +1 -1
  13. package/notification-banner/notification-banner-hoc.js +1 -1
  14. package/notification-banner/notification-banner-hoc.js.map +1 -1
  15. package/package.json +2 -2
  16. package/scripts/cli.js +18 -0
  17. package/skills/fds-build/SKILL.md +167 -0
  18. package/skills/fds-build/catalogue.md +181 -0
  19. package/skills/fds-build/evals/evals.json +519 -0
  20. package/skills/fds-build/resources/components/accordion.md +128 -0
  21. package/skills/fds-build/resources/components/alert.md +54 -0
  22. package/skills/fds-build/resources/components/animation.md +18 -0
  23. package/skills/fds-build/resources/components/avatar.md +10 -0
  24. package/skills/fds-build/resources/components/badge.md +18 -0
  25. package/skills/fds-build/resources/components/box-container.md +61 -0
  26. package/skills/fds-build/resources/components/breadcrumb.md +60 -0
  27. package/skills/fds-build/resources/components/button.md +74 -0
  28. package/skills/fds-build/resources/components/calendar.md +113 -0
  29. package/skills/fds-build/resources/components/card.md +43 -0
  30. package/skills/fds-build/resources/components/checkbox.md +53 -0
  31. package/skills/fds-build/resources/components/countdown-timer.md +20 -0
  32. package/skills/fds-build/resources/components/data-table.md +142 -0
  33. package/skills/fds-build/resources/components/date-navigator.md +25 -0
  34. package/skills/fds-build/resources/components/divider.md +23 -0
  35. package/skills/fds-build/resources/components/drawer.md +78 -0
  36. package/skills/fds-build/resources/components/error-display.md +137 -0
  37. package/skills/fds-build/resources/components/feedback-rating.md +61 -0
  38. package/skills/fds-build/resources/components/file-download.md +54 -0
  39. package/skills/fds-build/resources/components/file-upload.md +105 -0
  40. package/skills/fds-build/resources/components/filter.md +228 -0
  41. package/skills/fds-build/resources/components/footer.md +94 -0
  42. package/skills/fds-build/resources/components/form-date-input.md +85 -0
  43. package/skills/fds-build/resources/components/form-date-range-input.md +146 -0
  44. package/skills/fds-build/resources/components/form-e-signature.md +55 -0
  45. package/skills/fds-build/resources/components/form-input.md +65 -0
  46. package/skills/fds-build/resources/components/form-masked-input.md +69 -0
  47. package/skills/fds-build/resources/components/form-multi-select.md +100 -0
  48. package/skills/fds-build/resources/components/form-nested-multi-select.md +159 -0
  49. package/skills/fds-build/resources/components/form-nested-select.md +151 -0
  50. package/skills/fds-build/resources/components/form-otp-verification.md +175 -0
  51. package/skills/fds-build/resources/components/form-phone-number-input.md +87 -0
  52. package/skills/fds-build/resources/components/form-predictive-text-input.md +108 -0
  53. package/skills/fds-build/resources/components/form-select.md +103 -0
  54. package/skills/fds-build/resources/components/form-slider.md +116 -0
  55. package/skills/fds-build/resources/components/form-textarea.md +116 -0
  56. package/skills/fds-build/resources/components/form-time-range-picker.md +98 -0
  57. package/skills/fds-build/resources/components/form-timepicker.md +77 -0
  58. package/skills/fds-build/resources/components/form-unit-number-input.md +138 -0
  59. package/skills/fds-build/resources/components/fullscreen-image-carousel.md +159 -0
  60. package/skills/fds-build/resources/components/histogram-slider.md +41 -0
  61. package/skills/fds-build/resources/components/icon.md +167 -0
  62. package/skills/fds-build/resources/components/image-button.md +12 -0
  63. package/skills/fds-build/resources/components/input.md +51 -0
  64. package/skills/fds-build/resources/components/language-switcher.md +15 -0
  65. package/skills/fds-build/resources/components/layout.md +156 -0
  66. package/skills/fds-build/resources/components/link-list.md +120 -0
  67. package/skills/fds-build/resources/components/local-nav.md +119 -0
  68. package/skills/fds-build/resources/components/masthead.md +14 -0
  69. package/skills/fds-build/resources/components/menu.md +109 -0
  70. package/skills/fds-build/resources/components/modal.md +150 -0
  71. package/skills/fds-build/resources/components/navbar.md +270 -0
  72. package/skills/fds-build/resources/components/notification-banner.md +106 -0
  73. package/skills/fds-build/resources/components/pagination.md +104 -0
  74. package/skills/fds-build/resources/components/pill.md +55 -0
  75. package/skills/fds-build/resources/components/popover.md +80 -0
  76. package/skills/fds-build/resources/components/progress-indicator.md +47 -0
  77. package/skills/fds-build/resources/components/radio-button.md +66 -0
  78. package/skills/fds-build/resources/components/schedule.md +78 -0
  79. package/skills/fds-build/resources/components/sidenav.md +183 -0
  80. package/skills/fds-build/resources/components/singpass-button.md +14 -0
  81. package/skills/fds-build/resources/components/smart-app-banner.md +28 -0
  82. package/skills/fds-build/resources/components/tab.md +107 -0
  83. package/skills/fds-build/resources/components/table.md +65 -0
  84. package/skills/fds-build/resources/components/tag.md +68 -0
  85. package/skills/fds-build/resources/components/text-list.md +44 -0
  86. package/skills/fds-build/resources/components/time-slot-bar-week.md +66 -0
  87. package/skills/fds-build/resources/components/time-slot-bar.md +57 -0
  88. package/skills/fds-build/resources/components/time-slot-week-view.md +70 -0
  89. package/skills/fds-build/resources/components/timeline.md +69 -0
  90. package/skills/fds-build/resources/components/timetable.md +123 -0
  91. package/skills/fds-build/resources/components/toast.md +72 -0
  92. package/skills/fds-build/resources/components/toggle.md +119 -0
  93. package/skills/fds-build/resources/components/typography.md +86 -0
  94. package/skills/fds-build/resources/components/uneditable-section.md +84 -0
  95. package/skills/fds-build/resources/foundations/layout-composition-patterns.md +53 -0
  96. package/skills/fds-build/resources/foundations/tokens.md +547 -0
  97. package/skills/fds-build/resources/patterns/states.md +300 -0
  98. package/skills/fds-build/resources/setup/setup.md +202 -0
  99. package/skills/fds-build/resources/templates/form.md +427 -0
  100. package/skills/fds-build/resources/templates/listing.md +435 -0
@@ -0,0 +1,77 @@
1
+ # Form.Timepicker
2
+
3
+ Import: `import { Form } from "@lifesg/react-design-system/form"`
4
+
5
+ > **Casing:** the export is `Form.Timepicker` (lowercase `p`), not `Form.TimePicker`.
6
+
7
+ ## When to use
8
+
9
+ - Collecting a single time value from the user (e.g. appointment time, start time, notification time).
10
+ - Use `format="12hr"` for consumer-facing forms where AM/PM is more intuitive.
11
+ - Use `format="24hr"` (default) for operational or technical contexts.
12
+
13
+ ## Usage
14
+
15
+ ```tsx
16
+ import styles from "./MyForm.module.css";
17
+
18
+ <Form.Timepicker className={styles.timepicker} ... />
19
+ ```
20
+
21
+ ```css
22
+ /* MyForm.module.css */
23
+ :where(body.fds-breakpoint-lg-min) .timepicker {
24
+ max-width: 24rem;
25
+ }
26
+ ```
27
+
28
+ Defaults to 4 columns wide at `lg` and above; full-width on mobile. Use `max-width` via a CSS Module class.
29
+
30
+ ## Props
31
+
32
+ | Prop | Type | Default | Description |
33
+ | ------------------ | --------------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
34
+ | `className` | `string` | — | — |
35
+ | `id` | `string` | — | — |
36
+ | `style` | `React.CSSProperties` | — | — |
37
+ | `aria-labelledby` | `string` | — | ID of an element that labels the timepicker input. |
38
+ | `aria-describedby` | `string` | — | ID of an element that describes the timepicker input. |
39
+ | `value` | `string` | — | The controlled time value as a string. - 24 hour uses "hh:mm" e.g. `{ start: "13:00", end: "14:00" }` - 12 hour uses "hh:mmA" e.g. `{ start: "01:00PM", end: "02:00PM" }` |
40
+ | `placeholder` | `string` | "hh:mm" for 24-hour format or "hh:mmA" for 12-hour format. | Placeholder text shown when no value is selected. |
41
+ | `format` | `TimepickerFormat` | "24hr" | The time display and input format. |
42
+ | `disabled` | `boolean` | — | — |
43
+ | `readOnly` | `boolean` | — | — |
44
+ | `error` | `boolean` | — | Renders the input in an error state when `true`. |
45
+ | `alignment` | `DropdownAlignmentType` | — | Aligns the dropdown relative to the trigger element. |
46
+ | `dropdownZIndex` | `number` | — | CSS `z-index` applied to the dropdown overlay. |
47
+ | `dropdownRootNode` | `RefObject<HTMLElement>` | document.body | The root element that contains the dropdown element. |
48
+ | `onChange` | `((value: string) => void)` | — | Called when the user confirms a selection. |
49
+ | `onFocus` | `(() => void)` | — | Called when the timepicker input receives focus. |
50
+ | `onBlur` | `(() => void)` | — | Called when focus leaves both the input and the dropdown. |
51
+ | `label` | `FormLabelProps \| string` | — | Label rendered above the input. Accepts a plain string or a `FormLabelProps` object to provide additional configuration. |
52
+ | `errorMessage` | `string \| React.ReactNode` | — | Inline error text rendered below the input. |
53
+ | `layoutType` | `FormElementLayoutType` | — | Controls whether the container uses flex layout or a responsive column grid. |
54
+ | `xxlCols` | `ColSpan` | — | Column span at the `xxl` breakpoint. |
55
+ | `xlCols` | `ColSpan` | — | Column span at the `xl` breakpoint. |
56
+ | `lgCols` | `ColSpan` | — | Column span at the `lg` breakpoint. |
57
+ | `mdCols` | `ColSpan` | — | Column span at the `md` breakpoint. |
58
+ | `smCols` | `ColSpan` | — | Column span at the `sm` breakpoint. |
59
+ | `xsCols` | `ColSpan` | — | Column span at the `xs` breakpoint. |
60
+ | `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. |
61
+
62
+ ## Rules
63
+
64
+ - Always supply a `label` so the field is accessible.
65
+ - Pass `value` and `onChange` together for controlled usage.
66
+ - Match `format` to the expected string format: `"HH:mm"` for 24hr, `"hh:mmA"` for 12hr.
67
+ - Set `errorMessage` rather than `error` alone so users understand what is wrong.
68
+ - Use `dropdownRootNode` when the picker is inside a modal or fixed container to avoid z-index issues.
69
+ - Use `alignment="right"` when the picker is near the right edge of the viewport.
70
+ - `onChange` fires only after the user clicks the Confirm button, not on every dial interaction.
71
+ - `onBlur` does not apply any in-progress selection — use `onChange` for committed values.
72
+
73
+ ## Anti-patterns
74
+
75
+ - Do not use for time-range selection — use `Form.TimeRangePicker` instead.
76
+ - Do not mix 12hr format strings with `format="24hr"` or vice versa.
77
+ - Do not rely on `onBlur` to capture the selected value — it does not commit the selection.
@@ -0,0 +1,138 @@
1
+ # Form.UnitNumberInput
2
+
3
+ Import: `import { Form } from "@lifesg/react-design-system/form"`
4
+
5
+ ## When to use
6
+
7
+ - Collecting a Singapore unit number in `#floor-unit` format (e.g. `#01-234`).
8
+ - Any address form that includes a unit number field for Singapore addresses.
9
+
10
+ ## Usage
11
+
12
+ ### Basic controlled usage with validation guard
13
+
14
+ `onChange` returns a `floor-unit` string (e.g. `"03-12"`) or the sentinel string `"Invalid unit number"` for malformed input. Always check before persisting.
15
+
16
+ ```tsx
17
+ import { useState } from "react";
18
+ import { Form } from "@lifesg/react-design-system/form";
19
+
20
+ const UnitField = () => {
21
+ const [value, setValue] = useState("");
22
+ const [error, setError] = useState("");
23
+
24
+ const handleChange = (val: string) => {
25
+ if (val === "Invalid unit number") {
26
+ setError("Please enter a valid unit number.");
27
+ // Do not persist — keep previous valid value in state
28
+ } else {
29
+ setError("");
30
+ setValue(val); // e.g. "03-12"
31
+ }
32
+ };
33
+
34
+ return (
35
+ <Form.UnitNumberInput
36
+ label="Unit number"
37
+ value={value}
38
+ onChange={handleChange}
39
+ errorMessage={error}
40
+ />
41
+ );
42
+ };
43
+ ```
44
+
45
+ ### `onBlur` zero-pads automatically
46
+
47
+ After the user tabs away, `onBlur` fires with the formatted value. The displayed value may change without any user input — update state accordingly.
48
+
49
+ ```tsx
50
+ <Form.UnitNumberInput
51
+ label="Unit number"
52
+ value={value}
53
+ onChange={handleChange}
54
+ onBlur={(formatted) => {
55
+ // e.g. user typed "3-2" → formatted is "03-02"
56
+ setValue(formatted);
57
+ }}
58
+ />
59
+ ```
60
+
61
+ ### `onChangeRaw` — separate floor and unit
62
+
63
+ Use `onChangeRaw` when downstream code needs floor and unit as distinct values, regardless of validity.
64
+
65
+ ```tsx
66
+ <Form.UnitNumberInput
67
+ label="Unit number"
68
+ onChangeRaw={(parts: string[]) => {
69
+ const [floor, unit] = parts; // e.g. ["03", "12"]
70
+ setFloor(floor);
71
+ setUnit(unit);
72
+ }}
73
+ />
74
+ ```
75
+
76
+ Width: defaults to 4 columns wide at `lg` and above; full-width on mobile. Use `max-width` via a CSS Module class:
77
+
78
+ ```tsx
79
+ import styles from "./MyForm.module.css";
80
+
81
+ <Form.UnitNumberInput className={styles.unitNumberInput} ... />
82
+ ```
83
+
84
+ ```css
85
+ /* MyForm.module.css */
86
+ :where(body.fds-breakpoint-lg-min) .unitNumberInput {
87
+ max-width: 24rem;
88
+ }
89
+ ```
90
+
91
+ ## Props
92
+
93
+ | Prop | Type | Default | Description |
94
+ | -------------- | ----------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
95
+ | `className` | `string` | — | — |
96
+ | `id` | `string` | — | — |
97
+ | `style` | `React.CSSProperties` | — | — |
98
+ | `tabIndex` | `number` | — | — |
99
+ | `readOnly` | `boolean` | — | — |
100
+ | `placeholder` | `string` | "00-8888" | Placeholder text in `"floor-unit"` format. |
101
+ | `role` | `React.AriaRole` | — | — |
102
+ | `value` | `string` | — | The combined unit number value in `"floor-unit"` format (e.g. `"01-234"`). |
103
+ | `name` | `string` | — | — |
104
+ | `error` | `boolean` | — | Renders the input in an error state. |
105
+ | `disabled` | `boolean` | — | — |
106
+ | `autoComplete` | `string` | — | — |
107
+ | `onChange` | `((value: string) => void)` | — | Called when the value of either sub-input changes. |
108
+ | `onChangeRaw` | `((value: string[]) => void)` | — | Called when either sub-input changes. |
109
+ | `onBlur` | `((value: string) => void)` | — | Called when focus leaves the component entirely. |
110
+ | `onBlurRaw` | `((value: string[]) => void)` | — | Called when focus leaves the component. |
111
+ | `label` | `FormLabelProps \| string` | — | Label rendered above the input. Accepts a plain string or a `FormLabelProps` object to provide additional configuration. |
112
+ | `errorMessage` | `string \| React.ReactNode` | — | Inline error text rendered below the input. |
113
+ | `layoutType` | `FormElementLayoutType` | — | Controls whether the container uses flex layout or a responsive column grid. |
114
+ | `xxlCols` | `ColSpan` | — | Column span at the `xxl` breakpoint. |
115
+ | `xlCols` | `ColSpan` | — | Column span at the `xl` breakpoint. |
116
+ | `lgCols` | `ColSpan` | — | Column span at the `lg` breakpoint. |
117
+ | `mdCols` | `ColSpan` | — | Column span at the `md` breakpoint. |
118
+ | `smCols` | `ColSpan` | — | Column span at the `sm` breakpoint. |
119
+ | `xsCols` | `ColSpan` | — | Column span at the `xs` breakpoint. |
120
+ | `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. |
121
+
122
+ ## Rules
123
+
124
+ - Always supply a `label` so the field is accessible.
125
+ - Pass `value` and `onChange` together for controlled usage.
126
+ - Check the return value of `onChange` for `"Invalid unit number"` and handle it before persisting the value.
127
+ - Use `onChangeRaw` if you need floor and unit as separate values for downstream processing.
128
+ - Use `onBlur` to get the zero-padded, formatted value after the user finishes typing.
129
+ - Set `errorMessage` to explain validation failures.
130
+ - `onChange` returns `"Invalid unit number"` for malformed input — check for this string before saving.
131
+ - `onBlur` zero-pads the unit part (e.g. `"11-2"` → `"11-02"`).
132
+ - Use `onChangeRaw` / `onBlurRaw` when you need floor and unit as separate values.
133
+
134
+ ## Anti-patterns
135
+
136
+ - Do not use a plain text input for unit numbers — the component enforces the correct format automatically.
137
+ - Do not persist the value returned from `onChange` without first checking it is not `"Invalid unit number"`.
138
+ - Do not use for non-Singapore address formats — the component enforces the `floor-unit` structure.
@@ -0,0 +1,159 @@
1
+ # FullscreenImageCarousel
2
+
3
+ Import: `import { FullscreenImageCarousel } from "@lifesg/react-design-system/fullscreen-image-carousel"`
4
+
5
+ ## When to use
6
+
7
+ Use to display a fullscreen overlay carousel of images or custom content (e.g. PDFs, videos) with thumbnail navigation, zoom, and optional delete controls.
8
+
9
+ ## Sub-components
10
+
11
+ - `FullscreenImageCarouselItemProps` — union of `FullscreenImageCarouselImageItemProps` and `FullscreenImageCarouselCustomItemProps`
12
+
13
+ ## Usage
14
+
15
+ ### Mixed image and custom items
16
+
17
+ Items use a discriminated union on `type`. Image items may omit `type` (defaults to image); custom items **must** set `type: "custom"` and provide `renderContent`.
18
+
19
+ ```tsx
20
+ import { useState } from "react";
21
+ import { FullscreenImageCarousel } from "@lifesg/react-design-system/fullscreen-image-carousel";
22
+
23
+ const DocViewer = () => {
24
+ const [show, setShow] = useState(false);
25
+
26
+ const items = [
27
+ // Image item — type may be omitted
28
+ {
29
+ src: "https://example.com/photo.jpg",
30
+ alt: "Site photo",
31
+ fileName: "photo.jpg",
32
+ fileSize: "1.2 MB",
33
+ },
34
+ // Custom item — type is required
35
+ {
36
+ type: "custom" as const,
37
+ itemLabel: "PDF",
38
+ thumbnailSrc: "https://example.com/pdf-thumb.svg",
39
+ renderContent: () => (
40
+ <iframe
41
+ src="https://example.com/document.pdf"
42
+ title="PDF preview"
43
+ style={{ width: "100%", height: "100%", border: "none" }}
44
+ />
45
+ ),
46
+ },
47
+ ];
48
+
49
+ const handleDelete = (item, index) => {
50
+ // Remove the item from your state; the delete button only appears
51
+ // because onDelete is provided.
52
+ removeItem(index);
53
+ };
54
+
55
+ return (
56
+ <>
57
+ <button onClick={() => setShow(true)}>View files</button>
58
+ <FullscreenImageCarousel
59
+ show={show}
60
+ items={items}
61
+ onClose={() => setShow(false)}
62
+ onDelete={handleDelete} // omit to hide delete button
63
+ showFirstAndLastNav
64
+ />
65
+ </>
66
+ );
67
+ };
68
+ ```
69
+
70
+ ## Props — `FullscreenImageCarouselRef`
71
+
72
+ | Prop | Type | Default | Description |
73
+ | --------------------- | ------------------------------------ | ------- | ------------------------------------------------------------------------- |
74
+ | `currentItemIndex` \* | `number` | — | Zero-based index of the currently visible item. |
75
+ | `setCurrentItem` \* | `(currentItemIndex: number) => void` | — | Navigates directly to the item at the given zero-based index. |
76
+ | `goToPrevItem` \* | `() => void` | — | Navigates to the previous item, wrapping from the first item to the last. |
77
+ | `goToNextItem` \* | `() => void` | — | Navigates to the next item, wrapping from the last item to the first. |
78
+
79
+ ## Props — `FullscreenImageCarouselCustomAction`
80
+
81
+ | Prop | Type | Default | Description |
82
+ | -------------- | ----------------------------------------------------------------- | ------- | ---------------------------------------- |
83
+ | `icon` \* | `JSX.Element` | — | Icon element rendered inside the button. |
84
+ | `ariaLabel` \* | `string` | — | Accessible name for the button. |
85
+ | `onClick` \* | `(item: FullscreenImageCarouselItemProps, index: number) => void` | — | Called when the action is activated. |
86
+
87
+ ## Props — `FullscreenImageCarouselProps`
88
+
89
+ | Prop | Type | Default | Description |
90
+ | ------------------------ | ---------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
91
+ | `items` \* | `FullscreenImageCarouselItemProps[]` | — | The list of items to display. Each item is either an image or a custom content item. |
92
+ | `initialActiveItemIndex` | `number` | 0 | Zero-based index of the item that is active when the carousel first opens. |
93
+ | `hideThumbnail` | `boolean` | false | Hides the thumbnail strip below the main slide. |
94
+ | `hideNavigation` | `boolean` | false | Hides the previous and next arrow navigation buttons. |
95
+ | `hideCounter` | `boolean` | false | Hides the slide position counter chip (e.g. "2/5"). |
96
+ | `hideMagnifier` | `boolean` | false | Hides the zoom in/out magnifier button. Has no effect on custom items, which never show the magnifier regardless of this prop. |
97
+ | `customActions` | `FullscreenImageCarouselCustomAction[]` | — | Additional icon buttons rendered in the top bar, positioned before the default buttons. Applies to every item. An item's own `customActions` replaces this list for that item. |
98
+ | `onDelete` | `\| ((item: FullscreenImageCarouselItemProps, index: number) => void)` | — | Called when the delete button is activated for the current item. |
99
+ | `onClose` | `(() => void)` | — | Called when the close button is activated or the Escape key is pressed. |
100
+ | `insets` | `Insets` | — | Additional pixel insets applied to keep content within a safe area of the viewport and avoid overlap with device UI elements. |
101
+ | `topBarRef` | `React.Ref<HTMLDivElement>` | — | Ref to the top bar element, which holds the file info and the action buttons. |
102
+ | `show` \* | `boolean` | false | Controls whether the modal is visible. |
103
+ | `rootComponentId` | `string` | — | The `id` of the DOM element to portal the modal into. |
104
+ | `animationFrom` | `ModalAnimationDirection` | "bottom" | Direction from which the modal animates in and out. |
105
+ | `zIndex` | `number` | — | The z-index applied to the overlay stack. |
106
+
107
+ ## Props — `FullscreenImageCarouselImageItemProps`
108
+
109
+ | Prop | Type | Default | Description |
110
+ | --------------- | --------------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
111
+ | `type` | `"image"` | — | — |
112
+ | `src` \* | `string` | — | URL of the full-resolution image shown in the slide. |
113
+ | `alt` | `string` | — | Accessible label for the image. |
114
+ | `thumbnailSrc` | `string` | — | URL of the image shown in the thumbnail strip. Falls back to `src` when omitted. |
115
+ | `renderContent` | `never` | — | — |
116
+ | `fileName` | `string` | — | Display name of the file shown in the top info bar. |
117
+ | `fileSize` | `string` | — | Human-readable file size shown alongside `fileName` in the top info bar (e.g. "2.4 MB"). |
118
+ | `customActions` | `FullscreenImageCarouselCustomAction[]` | — | Replaces the component-level `customActions` while this item is active. Leave `undefined` to inherit the component-level list. Pass `[]` to render no custom actions for this item. |
119
+
120
+ ## Props — `FullscreenImageCarouselCustomItemProps`
121
+
122
+ | Prop | Type | Default | Description |
123
+ | ------------------ | --------------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
124
+ | `type` \* | `"custom"` | — | — |
125
+ | `thumbnailSrc` | `string` | — | The thumbnail image src. If omitted, a placeholder is shown in the thumbnail strip. |
126
+ | `itemLabel` | `string` | "image" | Label for this item used in aria-labels (e.g. "PDF"). |
127
+ | `renderContent` \* | `() => React.ReactNode` | — | Render prop for the full slide area. Consumer is responsible for the entire slide content (e.g. an iframe, embed, or custom viewer). |
128
+ | `fileName` | `string` | — | Display name of the file shown in the top info bar. |
129
+ | `fileSize` | `string` | — | Human-readable file size shown alongside `fileName` in the top info bar (e.g. "2.4 MB"). |
130
+ | `customActions` | `FullscreenImageCarouselCustomAction[]` | — | Replaces the component-level `customActions` while this item is active. Leave `undefined` to inherit the component-level list. Pass `[]` to render no custom actions for this item. |
131
+
132
+ ## Props — `ImageDimension`
133
+
134
+ | Prop | Type | Default | Description |
135
+ | ----------- | -------- | ------- | ----------- |
136
+ | `width` \* | `number` | — | — |
137
+ | `height` \* | `number` | — | — |
138
+
139
+ ## Props — `FullscreenImageCarouselItemProps`
140
+
141
+ | Prop | Type | Default | Description |
142
+ | --------------- | --------------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
143
+ | `type` | `"image"` | — | — |
144
+ | `thumbnailSrc` | `string` | — | URL of the image shown in the thumbnail strip. Falls back to `src` when omitted. |
145
+ | `renderContent` | `never` | — | — |
146
+ | `fileName` | `string` | — | Display name of the file shown in the top info bar. |
147
+ | `fileSize` | `string` | — | Human-readable file size shown alongside `fileName` in the top info bar (e.g. "2.4 MB"). |
148
+ | `customActions` | `FullscreenImageCarouselCustomAction[]` | — | Replaces the component-level `customActions` while this item is active. Leave `undefined` to inherit the component-level list. Pass `[]` to render no custom actions for this item. |
149
+
150
+ ## Rules
151
+
152
+ - Always provide `show` and `items` — both are required.
153
+ - Always wire `onClose` to set `show` to `false`; the component does not close itself.
154
+ - Do not reorder the `items` array while a delete is in flight.
155
+ - The delete button only appears when `onDelete` is provided — omit the prop to hide the button entirely.
156
+
157
+ ## Anti-patterns
158
+
159
+ - Do not use for inline image galleries — this component renders as a fullscreen modal overlay.
@@ -0,0 +1,41 @@
1
+ # HistogramSlider
2
+
3
+ Import: `import { HistogramSlider } from "@lifesg/react-design-system/histogram-slider"`
4
+
5
+ A range slider with a histogram distribution chart above it — used in filter panels where showing the value distribution helps users pick a range (e.g. price range, age range).
6
+
7
+ ## Props — `HistogramBinProps`
8
+
9
+ | Prop | Type | Default | Description |
10
+ | ------------- | -------- | ------- | -------------------------------------------------------- |
11
+ | `count` \* | `number` | — | Number of items that fall within this bin's value range. |
12
+ | `minValue` \* | `number` | — | Lower bound of this bin's value range. |
13
+
14
+ ## Props — `HistogramSliderProps`
15
+
16
+ | Prop | Type | Default | Description |
17
+ | ------------------ | -------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
18
+ | `className` | `string` | — | — |
19
+ | `id` | `string` | — | — |
20
+ | `bins` \* | `HistogramBinProps[]` | — | The set of bins to render as bars. |
21
+ | `interval` \* | `number` | — | Controls the slider's step increment and minimum selectable range. |
22
+ | `disabled` | `boolean` | — | — |
23
+ | `readOnly` | `boolean` | — | — |
24
+ | `value` | `[number, number]` | — | The selected range as `[start, end]`. When omitted, the selection is initialised to `[minValue, minValue + interval]` derived from the provided `bins`. |
25
+ | `showRangeLabels` | `boolean` | — | When `true`, displays the current start and end values above the histogram. |
26
+ | `rangeLabelPrefix` | `string` | — | Text prepended to each range label value. Has no effect when `renderRangeLabel` is provided. |
27
+ | `rangeLabelSuffix` | `string` | — | Text appended to each range label value. Has no effect when `renderRangeLabel` is provided. |
28
+ | `ariaLabels` | `[string, string]` | — | Accessible labels for the two slider thumbs, as `[startLabel, endLabel]`. |
29
+ | `aria-labelledby` | `string` | — | ID of an element that labels the slider group. |
30
+ | `aria-describedby` | `string` | — | ID of an element that describes the slider group. |
31
+ | `renderRangeLabel` | `((value: number) => React.ReactNode)` | — | Custom renderer for each range label value. When provided, overrides the `rangeLabelPrefix` and `rangeLabelSuffix` combination. |
32
+ | `onChange` | `((value: [number, number]) => void)` | — | Called on every selection change. |
33
+ | `onChangeEnd` | `((value: [number, number]) => void)` | — | Called when a thumb is released after selection is complete. |
34
+ | `renderEmptyView` | `(() => React.ReactNode)` | — | Rendered in place of the histogram and slider when all bins have a count of zero. |
35
+
36
+ ## Rules
37
+
38
+ - `onChange` fires continuously during drag — do not put API calls here; use `onChangeEnd`.
39
+ - `interval` must match the gap between `minValue` of consecutive bins.
40
+ - Always provide `ariaLabels` or `aria-labelledby` for accessibility.
41
+ - Pair with `Filter` in a filter sidebar — `HistogramSlider` is not a `Form.*` component and does not use `label`/`errorMessage`.
@@ -0,0 +1,167 @@
1
+ # Icons
2
+
3
+ Icons come from `@lifesg/react-icons` — a separate package. Each icon is its own named export.
4
+
5
+ ## Import pattern
6
+
7
+ Icon names follow `{name}` → component name is `{PascalCase}Icon`:
8
+
9
+ ```tsx
10
+ import { ArrowRightIcon } from "@lifesg/react-icons/arrow-right";
11
+ import { TickCircleFillIcon } from "@lifesg/react-icons/tick-circle-fill";
12
+ import { PersonIcon } from "@lifesg/react-icons/person";
13
+ ```
14
+
15
+ Pattern: `kebab-case` name → import path `@lifesg/react-icons/{name}` → component `{PascalCase}Icon`
16
+
17
+ ## Available icons
18
+
19
+ Icons come in two variants: outline (default) and filled (`-fill` suffix).
20
+
21
+ ### Arrows & Navigation
22
+
23
+ `arrow-up` `arrow-down` `arrow-left` `arrow-right`
24
+ `arrow-up-circle` `arrow-up-circle-fill` `arrow-down-circle` `arrow-down-circle-fill`
25
+ `arrow-left-circle` `arrow-left-circle-fill` `arrow-right-circle` `arrow-right-circle-fill`
26
+ `arrow-left-up` `arrow-left-down` `arrow-right-up` `arrow-right-down`
27
+ `chevron-up` `chevron-down` `chevron-left` `chevron-right`
28
+ `chevron-2-left` `chevron-2-right`
29
+ `chevron-line-left` `chevron-line-right`
30
+ `caret-up` `caret-down` `caret-left` `caret-right`
31
+ `multi-chevron-up-down` `multi-chevron-down-up`
32
+ `navigation` `navigation-fill`
33
+ `external` `return`
34
+
35
+ ### Actions
36
+
37
+ `plus` `plus-circle` `plus-circle-fill`
38
+ `minus` `minus-circle` `minus-circle-fill` `minus-square` `minus-square-fill`
39
+ `cross` `cross-circle` `cross-circle-fill`
40
+ `tick` `tick-circle` `tick-circle-fill`
41
+ `pencil` `pencil-fill` `pencil-stroke` `pencil-stroke-fill`
42
+ `bin` `bin-fill`
43
+ `copy` `copy-fill`
44
+ `download` `upload`
45
+ `share` `link`
46
+ `login` `logout`
47
+ `refresh` `replay` `retry`
48
+ `drag-handle`
49
+ `filter`
50
+ `sort-ascending` `sort-descending`
51
+ `fullscreen-open` `fullscreen-close`
52
+ `read-more`
53
+ `eraser`
54
+
55
+ ### Status & Feedback
56
+
57
+ `tick-circle` `tick-circle-fill`
58
+ `cross-circle` `cross-circle-fill`
59
+ `exclamation-circle` `exclamation-circle-fill`
60
+ `exclamation-triangle` `exclamation-triangle-fill`
61
+ `i-circle` `i-circle-fill`
62
+ `questionmark-circle` `questionmark-circle-fill`
63
+ `thumb-up` `thumb-up-fill`
64
+ `star` `star-fill` `star-half`
65
+
66
+ ### People & Identity
67
+
68
+ `person` `person-fill`
69
+ `person-2` `person-2-fill`
70
+ `person-group` `person-group-fill`
71
+ `person-plus` `person-plus-fill`
72
+ `person-minus` `person-minus-fill`
73
+ `person-2-plus` `person-2-plus-fill`
74
+ `person-2-minus` `person-2-minus-fill`
75
+ `person-gear` `person-gear-fill`
76
+ `person-off` `person-off-fill`
77
+ `id-card` `id-card-fill`
78
+
79
+ ### Communication
80
+
81
+ `envelope` `envelope-fill`
82
+ `envelope-open` `envelope-open-fill`
83
+ `bell` `bell-fill`
84
+ `phone` `phone-fill`
85
+ `inbox` `inbox-fill`
86
+ `speaker` `speaker-fill`
87
+
88
+ ### Files & Documents
89
+
90
+ `doc` `doc-fill`
91
+ `doc-pencil` `doc-pencil-fill`
92
+ `album` `album-fill`
93
+ `box` `box-fill`
94
+ `index`
95
+ `text` `text-fill`
96
+
97
+ ### Calendar & Time
98
+
99
+ `calendar` `calendar-fill`
100
+ `calendar-event` `calendar-event-fill`
101
+ `calendar-plus` `calendar-plus-fill`
102
+ `calendar-cross` `calendar-cross-fill`
103
+ `calendar-dot` `calendar-dot-fill`
104
+ `calendar-pencil` `calendar-pencil-fill`
105
+ `calendar-tick` `calendar-tick-fill`
106
+ `clock` `clock-fill`
107
+ `recurring` `non-recurring`
108
+
109
+ ### Location & Maps
110
+
111
+ `map` `map-fill`
112
+ `pin` `pin-fill`
113
+ `point`
114
+ `navigation` `navigation-fill`
115
+ `globe`
116
+ `house` `house-fill`
117
+ `building`
118
+ `floor-plan` `floor-plan-fill`
119
+
120
+ ### Devices & Media
121
+
122
+ `camera` `camera-fill`
123
+ `tablet` `tablet-fill`
124
+ `phone` `phone-fill`
125
+ `cloud` `cloud-fill`
126
+ `cloud-arrow-up` `cloud-arrow-up-fill`
127
+ `cloud-arrow-down` `cloud-arrow-down-fill`
128
+ `cloud-tick` `cloud-tick-fill`
129
+ `qr`
130
+
131
+ ### UI & Layout
132
+
133
+ `menu`
134
+ `ellipsis-horizontal` `ellipsis-vertical`
135
+ `gear` `gear-fill`
136
+ `lock` `lock-fill`
137
+ `eye` `eye-fill`
138
+ `eye-slash` `eye-slash-fill`
139
+ `list`
140
+ `square` `square-fill`
141
+ `square-2x2` `square-2x2-fill`
142
+ `square-tick` `square-tick-fill`
143
+ `circle` `circle-dot`
144
+ `toggle-on` `toggle-on-fill`
145
+ `toggle-off` `toggle-off-fill`
146
+ `magnifier` `magnifier-plus` `magnifier-minus`
147
+
148
+ ### Misc
149
+
150
+ `bookmark` `bookmark-fill`
151
+ `chart` `chart-fill`
152
+ `car`
153
+ `dollar`
154
+ `printer` `printer-fill`
155
+ `lightbulb` `lightbulb-fill`
156
+ `language`
157
+ `ticket` `ticket-fill`
158
+ `triangle-forward` `triangle-forward-fill`
159
+ `align-left` `align-center` `align-right` `align-justify`
160
+ `placeholder` `placeholder-image`
161
+
162
+ ## Rules
163
+
164
+ - Never guess an icon name — verify it exists in the list above before importing.
165
+ - `-fill` variants are the solid/filled version; the base name is outline.
166
+ - Fallback: if no suitable icon exists in `@lifesg/react-icons`, use `@mui/icons-material` as a secondary source.
167
+ - For icon-only buttons (`<Button icon={<Icon />} />`), always set `aria-label` — the icon alone is not accessible.
@@ -0,0 +1,12 @@
1
+ # ImageButton
2
+
3
+ Import: `import { ImageButton } from "@lifesg/react-design-system/image-button"`
4
+
5
+ ## Props
6
+
7
+ | Prop | Type | Default | Description |
8
+ | ----------------------- | --------- | ------- | ---------------------------------------------------------------------------- |
9
+ | `imgSrc` \* | `string` | — | URL of the image to display inside the button. |
10
+ | `selected` | `boolean` | — | Applies a selected visual style to indicate the button is the active choice. |
11
+ | `error` | `boolean` | — | Applies an error visual style to indicate a validation or selection error. |
12
+ | `focusableWhenDisabled` | `boolean` | false | Keeps the button reachable via keyboard focus when `disabled` is `true`. |