@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.
- package/cjs/external/dompurify/dist/purify.es.js +4 -2
- package/cjs/external/dompurify/dist/purify.es.js.map +1 -1
- 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/notification-banner/notification-banner-hoc.js +1 -1
- package/cjs/notification-banner/notification-banner-hoc.js.map +1 -1
- package/external/dompurify/dist/purify.es.js +4 -2
- package/external/dompurify/dist/purify.es.js.map +1 -1
- 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/notification-banner/notification-banner-hoc.js +1 -1
- package/notification-banner/notification-banner-hoc.js.map +1 -1
- package/package.json +2 -2
- package/scripts/cli.js +18 -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
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
# States Pattern
|
|
2
|
+
|
|
3
|
+
Loading, empty, error, and success states for async operations and data absence.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Loading states
|
|
8
|
+
|
|
9
|
+
### Full-page loading
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import { Layout } from "@lifesg/react-design-system/layout";
|
|
13
|
+
import { Card } from "@lifesg/react-design-system/card";
|
|
14
|
+
import { Typography } from "@lifesg/react-design-system/typography";
|
|
15
|
+
import { Spacing } from "@lifesg/react-design-system/theme";
|
|
16
|
+
|
|
17
|
+
if (isLoading) {
|
|
18
|
+
return (
|
|
19
|
+
<Layout.Container type="grid">
|
|
20
|
+
<Layout.ColDiv xsCols={8} lgCols={12}>
|
|
21
|
+
<Card
|
|
22
|
+
style={{
|
|
23
|
+
textAlign: "center",
|
|
24
|
+
padding: `${Spacing["spacing-64"]} ${Spacing["spacing-32"]}`,
|
|
25
|
+
}}
|
|
26
|
+
>
|
|
27
|
+
<LoadingSpinner />
|
|
28
|
+
{/* import { LoadingSpinner } from "@lifesg/react-design-system/loading-spinner" */}
|
|
29
|
+
<Typography.BodyBL>Loading...</Typography.BodyBL>
|
|
30
|
+
</Card>
|
|
31
|
+
</Layout.ColDiv>
|
|
32
|
+
</Layout.Container>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Skeleton loading (preferred for content-heavy pages)
|
|
38
|
+
|
|
39
|
+
Render placeholder blocks matching the shape of real content. Use a CSS `pulse` animation (`opacity 0.5 → 1` at 1.5s). Show the same grid structure as the loaded state — same column count, same card shapes.
|
|
40
|
+
|
|
41
|
+
### Button loading (inline async)
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
<Button type="submit" loading={isSubmitting} disabled={isSubmitting}>
|
|
45
|
+
{isSubmitting ? "Submitting..." : "Submit"}
|
|
46
|
+
</Button>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Always combine `loading` + `disabled` during async to prevent double-submission.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Empty states
|
|
54
|
+
|
|
55
|
+
### First-time use (no data)
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
import { Card } from "@lifesg/react-design-system/card";
|
|
59
|
+
import { Typography } from "@lifesg/react-design-system/typography";
|
|
60
|
+
import { Button } from "@lifesg/react-design-system/button";
|
|
61
|
+
import { Spacing } from "@lifesg/react-design-system/theme";
|
|
62
|
+
|
|
63
|
+
<Card
|
|
64
|
+
style={{
|
|
65
|
+
textAlign: "center",
|
|
66
|
+
padding: `${Spacing["spacing-64"]} ${Spacing["spacing-32"]}`,
|
|
67
|
+
}}
|
|
68
|
+
>
|
|
69
|
+
<Typography.HeadingSM>No items yet</Typography.HeadingSM>
|
|
70
|
+
<Typography.BodyBL>
|
|
71
|
+
Get started by creating your first item
|
|
72
|
+
</Typography.BodyBL>
|
|
73
|
+
<Button
|
|
74
|
+
icon={<PlusIcon />}
|
|
75
|
+
style={{ marginTop: Spacing["spacing-24"] }}
|
|
76
|
+
onClick={handleCreate}
|
|
77
|
+
>
|
|
78
|
+
Create item
|
|
79
|
+
</Button>
|
|
80
|
+
</Card>;
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### No search results
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
<Card
|
|
87
|
+
style={{
|
|
88
|
+
textAlign: "center",
|
|
89
|
+
padding: `${Spacing["spacing-64"]} ${Spacing["spacing-32"]}`,
|
|
90
|
+
}}
|
|
91
|
+
>
|
|
92
|
+
<Typography.HeadingSM>No results found</Typography.HeadingSM>
|
|
93
|
+
<Typography.BodyBL>Try adjusting your search or filters</Typography.BodyBL>
|
|
94
|
+
<Button
|
|
95
|
+
styleType="secondary"
|
|
96
|
+
style={{ marginTop: Spacing["spacing-24"] }}
|
|
97
|
+
onClick={handleClearFilters}
|
|
98
|
+
>
|
|
99
|
+
Clear filters
|
|
100
|
+
</Button>
|
|
101
|
+
</Card>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
For listing pages, prefer `ErrorDisplay type="no-item-found"` over a custom card.
|
|
105
|
+
|
|
106
|
+
### Empty section (within a page)
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
import { Typography } from "@lifesg/react-design-system/typography";
|
|
110
|
+
import { Colour, Spacing } from "@lifesg/react-design-system/theme";
|
|
111
|
+
|
|
112
|
+
<div style={{ textAlign: "center", padding: `${Spacing["spacing-32"]} 0` }}>
|
|
113
|
+
<Typography.BodySM style={{ color: Colour["text-subtler"] }}>
|
|
114
|
+
No recent activity to display
|
|
115
|
+
</Typography.BodySM>
|
|
116
|
+
</div>;
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Error states
|
|
122
|
+
|
|
123
|
+
### API / fetch error
|
|
124
|
+
|
|
125
|
+
```tsx
|
|
126
|
+
import { Alert } from "@lifesg/react-design-system/alert";
|
|
127
|
+
import { Layout } from "@lifesg/react-design-system/layout";
|
|
128
|
+
|
|
129
|
+
if (error) {
|
|
130
|
+
return (
|
|
131
|
+
<Layout.Container type="grid">
|
|
132
|
+
<Layout.ColDiv xsCols={8} lgCols={12}>
|
|
133
|
+
<Alert
|
|
134
|
+
type="error"
|
|
135
|
+
actionLink={{
|
|
136
|
+
children: "Try again",
|
|
137
|
+
onClick: () => window.location.reload(),
|
|
138
|
+
}}
|
|
139
|
+
>
|
|
140
|
+
{error.message}
|
|
141
|
+
</Alert>
|
|
142
|
+
</Layout.ColDiv>
|
|
143
|
+
</Layout.Container>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### 404 Not found
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
import { Card } from "@lifesg/react-design-system/card";
|
|
152
|
+
import { Typography } from "@lifesg/react-design-system/typography";
|
|
153
|
+
import { Button } from "@lifesg/react-design-system/button";
|
|
154
|
+
import { Spacing } from "@lifesg/react-design-system/theme";
|
|
155
|
+
|
|
156
|
+
<Card
|
|
157
|
+
style={{
|
|
158
|
+
textAlign: "center",
|
|
159
|
+
padding: `${Spacing["spacing-64"]} ${Spacing["spacing-32"]}`,
|
|
160
|
+
}}
|
|
161
|
+
>
|
|
162
|
+
<Typography.HeadingXL>404</Typography.HeadingXL>
|
|
163
|
+
<Typography.HeadingSM>Page not found</Typography.HeadingSM>
|
|
164
|
+
<Typography.BodyBL>
|
|
165
|
+
The page you're looking for doesn't exist or has been moved
|
|
166
|
+
</Typography.BodyBL>
|
|
167
|
+
<Button
|
|
168
|
+
style={{ marginTop: Spacing["spacing-24"] }}
|
|
169
|
+
onClick={() => navigate("/")}
|
|
170
|
+
>
|
|
171
|
+
Go to home
|
|
172
|
+
</Button>
|
|
173
|
+
</Card>;
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Permission denied
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
<Card
|
|
180
|
+
style={{
|
|
181
|
+
textAlign: "center",
|
|
182
|
+
padding: `${Spacing["spacing-64"]} ${Spacing["spacing-32"]}`,
|
|
183
|
+
}}
|
|
184
|
+
>
|
|
185
|
+
<Typography.HeadingSM>Access denied</Typography.HeadingSM>
|
|
186
|
+
<Typography.BodyBL>
|
|
187
|
+
You don't have permission to view this page
|
|
188
|
+
</Typography.BodyBL>
|
|
189
|
+
<Button
|
|
190
|
+
styleType="secondary"
|
|
191
|
+
style={{ marginTop: Spacing["spacing-24"] }}
|
|
192
|
+
onClick={() => navigate("/")}
|
|
193
|
+
>
|
|
194
|
+
Go to home
|
|
195
|
+
</Button>
|
|
196
|
+
</Card>
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Success states
|
|
202
|
+
|
|
203
|
+
### Inline success
|
|
204
|
+
|
|
205
|
+
```tsx
|
|
206
|
+
<Alert
|
|
207
|
+
type="success"
|
|
208
|
+
actionLink={{ children: "View details", onClick: handleViewDetails }}
|
|
209
|
+
>
|
|
210
|
+
{successMessage}
|
|
211
|
+
</Alert>
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Toast notification
|
|
215
|
+
|
|
216
|
+
```tsx
|
|
217
|
+
import { Toast } from "@lifesg/react-design-system/toast";
|
|
218
|
+
|
|
219
|
+
<Toast type="success">Changes saved successfully</Toast>
|
|
220
|
+
<Toast type="error">Failed to save changes</Toast>
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Use `Toast` for non-critical feedback. Use `Alert` for errors requiring action.
|
|
224
|
+
|
|
225
|
+
### Full-page success (major operations)
|
|
226
|
+
|
|
227
|
+
```tsx
|
|
228
|
+
import { Card } from "@lifesg/react-design-system/card";
|
|
229
|
+
import { Typography } from "@lifesg/react-design-system/typography";
|
|
230
|
+
import { Button } from "@lifesg/react-design-system/button";
|
|
231
|
+
import { Spacing } from "@lifesg/react-design-system/theme";
|
|
232
|
+
|
|
233
|
+
<Card
|
|
234
|
+
style={{
|
|
235
|
+
textAlign: "center",
|
|
236
|
+
padding: `${Spacing["spacing-64"]} ${Spacing["spacing-32"]}`,
|
|
237
|
+
}}
|
|
238
|
+
>
|
|
239
|
+
<Typography.HeadingMD>
|
|
240
|
+
Application submitted successfully
|
|
241
|
+
</Typography.HeadingMD>
|
|
242
|
+
<Typography.BodyBL>Your application is being processed</Typography.BodyBL>
|
|
243
|
+
<div
|
|
244
|
+
style={{
|
|
245
|
+
display: "flex",
|
|
246
|
+
gap: Spacing["spacing-16"],
|
|
247
|
+
justifyContent: "center",
|
|
248
|
+
marginTop: Spacing["spacing-32"],
|
|
249
|
+
}}
|
|
250
|
+
>
|
|
251
|
+
<Button onClick={() => navigate("/applications")}>
|
|
252
|
+
View applications
|
|
253
|
+
</Button>
|
|
254
|
+
<Button styleType="secondary" onClick={() => navigate("/")}>
|
|
255
|
+
Go to home
|
|
256
|
+
</Button>
|
|
257
|
+
</div>
|
|
258
|
+
</Card>;
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## State management pattern
|
|
264
|
+
|
|
265
|
+
```tsx
|
|
266
|
+
const [state, setState] = useState({
|
|
267
|
+
data: null,
|
|
268
|
+
isLoading: true,
|
|
269
|
+
error: null,
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// In component:
|
|
273
|
+
if (state.isLoading) return <LoadingState />;
|
|
274
|
+
if (state.error) return <ErrorState message={state.error} />;
|
|
275
|
+
if (!state.data || state.data.length === 0) return <EmptyState />;
|
|
276
|
+
return <DataDisplay data={state.data} />;
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## Rules
|
|
282
|
+
|
|
283
|
+
- Show loading feedback for any operation taking longer than 200ms
|
|
284
|
+
- Prefer skeleton loading over a spinner for content-heavy initial page loads
|
|
285
|
+
- Always provide a way out of an error state (retry, go home)
|
|
286
|
+
- Empty state messages: 1–2 sentences max, action-oriented, sentence case
|
|
287
|
+
- Error messages: name the specific problem — never "An error occurred"
|
|
288
|
+
- `Toast` auto-dismisses — use for confirmations; use `Alert` for errors needing action
|
|
289
|
+
- Full-page state containers: `padding: spacing-64 spacing-32`, `textAlign: center`, inside a `Card`
|
|
290
|
+
- Disable interactive elements during loading to prevent race conditions
|
|
291
|
+
- Use `style={{}}` with `Spacing` and `Colour` tokens for all custom spacing and colour
|
|
292
|
+
|
|
293
|
+
## Anti-patterns
|
|
294
|
+
|
|
295
|
+
- Do not show a spinner for operations under 200ms — causes flicker
|
|
296
|
+
- Do not show an empty container with no state message in a listing page
|
|
297
|
+
- Do not use `Toast` for critical errors — user may miss it
|
|
298
|
+
- Do not use generic error text — always tell the user what failed
|
|
299
|
+
- Do not leave submit buttons enabled during async — always set `disabled` + `loading`
|
|
300
|
+
- Do not hardcode `px` values or hex colours — always use `Spacing` and `Colour` tokens
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# App Setup — Flagship v4
|
|
2
|
+
|
|
3
|
+
Full project setup for `@lifesg/react-design-system` v4. Work through all steps before writing any component code.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Step 1 — Install packages
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @lifesg/react-design-system @lifesg/react-icons @floating-ui/react
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Do NOT install `styled-components` — it is not used in v4.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Step 2 — CDN stylesheets
|
|
18
|
+
|
|
19
|
+
Add these stylesheets to normalise browser styles and display fonts.
|
|
20
|
+
|
|
21
|
+
```html
|
|
22
|
+
<link
|
|
23
|
+
rel="stylesheet"
|
|
24
|
+
type="text/css"
|
|
25
|
+
href="https://assets.life.gov.sg/react-design-system/v4/css/main.css"
|
|
26
|
+
/>
|
|
27
|
+
<link
|
|
28
|
+
rel="stylesheet"
|
|
29
|
+
type="text/css"
|
|
30
|
+
href="https://assets.life.gov.sg/react-design-system/v3/css/open-sans.css"
|
|
31
|
+
/>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Or if you are importing to an existing CSS file:
|
|
35
|
+
|
|
36
|
+
```css
|
|
37
|
+
@import url("https://assets.life.gov.sg/react-design-system/v4/css/main.css");
|
|
38
|
+
@import url("https://assets.life.gov.sg/react-design-system/v3/css/open-sans.css");
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Replace `open-sans` with the font for your theme:
|
|
42
|
+
|
|
43
|
+
| Theme | Font CSS |
|
|
44
|
+
| ---------------------------------------- | ---------------------------------- |
|
|
45
|
+
| lifesg, ccube, mylegacy, oneservice, rbs | `open-sans` |
|
|
46
|
+
| bookingsg, careercompass, smgs | `plus-jakarta-sans` |
|
|
47
|
+
| spf, vica, websg, wise, wogaa | `public-sans` |
|
|
48
|
+
| sgw-digital-lobby, supportgowhere | `libre-franklin` |
|
|
49
|
+
| pa | `lato` |
|
|
50
|
+
| imda | `montserrat` |
|
|
51
|
+
| tote-board | `inter` |
|
|
52
|
+
| a11y-playground | `atkinson-hyperlegible-next` |
|
|
53
|
+
| sportsg-orange | `apfel-grotezk` + `hanken-grotesk` |
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Step 3 — Theme stylesheet
|
|
58
|
+
|
|
59
|
+
In your application's CSS entrypoint, import the theme stylesheet:
|
|
60
|
+
|
|
61
|
+
```css
|
|
62
|
+
@import "@lifesg/react-design-system/theme/styles/lifesg.css";
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
> CSS `@import` from `node_modules` requires your framework or build tool to support it. Vite and Next.js resolve this automatically; other build tools may need a CSS resolver configured.
|
|
66
|
+
|
|
67
|
+
Or in your application's JavaScript entrypoint, import the theme stylesheet:
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
import "@lifesg/react-design-system/theme/styles/lifesg.css";
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Replace `lifesg` with your chosen theme slug: `a11y-playground` · `bookingsg` · `careercompass` · `ccube` · `imda` · `lifesg` · `mylegacy` · `oneservice` · `pa` · `rbs` · `sgw-digital-lobby` · `smgs` · `spf` · `sportsg-orange` · `supportgowhere` · `tote-board` · `vica` · `websg` · `wise` · `wogaa`
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Step 4 — ThemeProvider
|
|
78
|
+
|
|
79
|
+
Wrap your app root in `ThemeProvider`. The `theme` prop is a string matching the slug used in Step 3.
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
import { ThemeProvider } from "@lifesg/react-design-system/theme";
|
|
83
|
+
|
|
84
|
+
const App = () => {
|
|
85
|
+
return (
|
|
86
|
+
<ThemeProvider theme="lifesg">
|
|
87
|
+
<Component />
|
|
88
|
+
</ThemeProvider>
|
|
89
|
+
);
|
|
90
|
+
};
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Dark/light mode:** `mode` prop defaults to `"auto"` (OS preference).
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
<ThemeProvider theme="lifesg" mode="light"> {/* force light */}
|
|
97
|
+
<ThemeProvider theme="lifesg" mode="dark"> {/* force dark */}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Vite
|
|
103
|
+
|
|
104
|
+
No special configuration needed for the design system. Standard React setup:
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
// vite.config.ts
|
|
108
|
+
import { defineConfig } from "vite";
|
|
109
|
+
import react from "@vitejs/plugin-react";
|
|
110
|
+
|
|
111
|
+
export default defineConfig({
|
|
112
|
+
plugins: [react()],
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Next.js App Router
|
|
119
|
+
|
|
120
|
+
No special `next.config.js` is needed. Suggested configuration below.
|
|
121
|
+
|
|
122
|
+
### CSS
|
|
123
|
+
|
|
124
|
+
Create a global CSS file with all three imports — CDN stylesheets and theme:
|
|
125
|
+
|
|
126
|
+
```css
|
|
127
|
+
/* global.css */
|
|
128
|
+
@import url("https://assets.life.gov.sg/react-design-system/v4/css/main.css");
|
|
129
|
+
@import url("https://assets.life.gov.sg/react-design-system/v3/css/open-sans.css");
|
|
130
|
+
@import "@lifesg/react-design-system/theme/styles/lifesg.css";
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Wrap the app in a flex column layout so the footer stays pinned to the bottom on short pages:
|
|
134
|
+
|
|
135
|
+
```css
|
|
136
|
+
body {
|
|
137
|
+
min-height: 100vh;
|
|
138
|
+
display: flex;
|
|
139
|
+
flex-direction: column;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
main {
|
|
143
|
+
flex: 1;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.theme-provider {
|
|
147
|
+
display: flex;
|
|
148
|
+
flex-direction: column;
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### ThemeProvider
|
|
153
|
+
|
|
154
|
+
`ThemeProvider` is a client component, so create a wrapper:
|
|
155
|
+
|
|
156
|
+
```tsx
|
|
157
|
+
// src/app/providers.tsx
|
|
158
|
+
"use client";
|
|
159
|
+
import { ThemeProvider } from "@lifesg/react-design-system/theme";
|
|
160
|
+
|
|
161
|
+
export default function Providers({ children }: { children: React.ReactNode }) {
|
|
162
|
+
return (
|
|
163
|
+
<ThemeProvider theme="lifesg" className="theme-provider">
|
|
164
|
+
{children}
|
|
165
|
+
</ThemeProvider>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Root layout
|
|
171
|
+
|
|
172
|
+
Import the global CSS and the providers wrapper in `layout.tsx`:
|
|
173
|
+
|
|
174
|
+
```tsx
|
|
175
|
+
// src/app/layout.tsx
|
|
176
|
+
import "./global.css";
|
|
177
|
+
import Providers from "./providers";
|
|
178
|
+
|
|
179
|
+
export default function RootLayout({
|
|
180
|
+
children,
|
|
181
|
+
}: {
|
|
182
|
+
children: React.ReactNode;
|
|
183
|
+
}) {
|
|
184
|
+
return (
|
|
185
|
+
<html lang="en">
|
|
186
|
+
<body>
|
|
187
|
+
<Providers>{children}</Providers>
|
|
188
|
+
</body>
|
|
189
|
+
</html>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Verification checklist
|
|
197
|
+
|
|
198
|
+
- [ ] `@lifesg/react-design-system`, `@lifesg/react-icons`, `@floating-ui/react` installed
|
|
199
|
+
- [ ] `styled-components` NOT installed
|
|
200
|
+
- [ ] `main.css` and font CDN links imported in HTML doc or CSS file
|
|
201
|
+
- [ ] `@lifesg/react-design-system/theme/styles/{theme}.css` imported in entrypoint or CSS file
|
|
202
|
+
- [ ] `ThemeProvider` wraps app root with correct theme string
|