@discourser/design-system 0.3.1 → 0.5.0
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/README.md +12 -4
- package/dist/styles.css +5126 -0
- package/guidelines/Guidelines.md +92 -41
- package/guidelines/components/accordion.md +732 -0
- package/guidelines/components/avatar.md +1015 -0
- package/guidelines/components/badge.md +728 -0
- package/guidelines/components/button.md +75 -40
- package/guidelines/components/card.md +84 -25
- package/guidelines/components/checkbox.md +671 -0
- package/guidelines/components/dialog.md +619 -31
- package/guidelines/components/drawer.md +1616 -0
- package/guidelines/components/heading.md +576 -0
- package/guidelines/components/icon-button.md +92 -37
- package/guidelines/components/input-addon.md +685 -0
- package/guidelines/components/input-group.md +830 -0
- package/guidelines/components/input.md +92 -37
- package/guidelines/components/popover.md +1271 -0
- package/guidelines/components/progress.md +836 -0
- package/guidelines/components/radio-group.md +852 -0
- package/guidelines/components/select.md +1662 -0
- package/guidelines/components/skeleton.md +802 -0
- package/guidelines/components/slider.md +911 -0
- package/guidelines/components/spinner.md +783 -0
- package/guidelines/components/switch.md +105 -38
- package/guidelines/components/tabs.md +1488 -0
- package/guidelines/components/textarea.md +495 -0
- package/guidelines/components/toast.md +784 -0
- package/guidelines/components/tooltip.md +912 -0
- package/guidelines/design-tokens/colors.md +309 -72
- package/guidelines/design-tokens/elevation.md +615 -45
- package/guidelines/design-tokens/spacing.md +654 -74
- package/guidelines/design-tokens/typography.md +432 -50
- package/guidelines/overview-components.md +60 -8
- package/guidelines/overview-imports.md +314 -0
- package/guidelines/overview-patterns.md +3852 -0
- package/package.json +4 -2
|
@@ -5,6 +5,7 @@ The design system uses Material Design 3 elevation system combining surface tint
|
|
|
5
5
|
## What is Elevation?
|
|
6
6
|
|
|
7
7
|
Elevation creates visual hierarchy by making elements appear to float above the background. M3 uses two techniques:
|
|
8
|
+
|
|
8
9
|
1. **Surface Tints**: Background color changes (via `surfaceContainer*` tokens)
|
|
9
10
|
2. **Shadows**: Subtle shadows for additional depth (optional)
|
|
10
11
|
|
|
@@ -12,14 +13,14 @@ Elevation creates visual hierarchy by making elements appear to float above the
|
|
|
12
13
|
|
|
13
14
|
## Elevation Levels
|
|
14
15
|
|
|
15
|
-
| Token
|
|
16
|
-
|
|
17
|
-
| `level0` | none
|
|
18
|
-
| `level1` | `0px 1px 2px rgba(0,0,0,0.3), 0px 1px 3px 1px rgba(0,0,0,0.15)`
|
|
19
|
-
| `level2` | `0px 1px 2px rgba(0,0,0,0.3), 0px 2px 6px 2px rgba(0,0,0,0.15)`
|
|
20
|
-
| `level3` | `0px 4px 8px 3px rgba(0,0,0,0.15), 0px 1px 3px rgba(0,0,0,0.3)`
|
|
16
|
+
| Token | Shadow Value | Usage |
|
|
17
|
+
| -------- | ---------------------------------------------------------------- | ------------------------------- |
|
|
18
|
+
| `level0` | none | Flat elements, no elevation |
|
|
19
|
+
| `level1` | `0px 1px 2px rgba(0,0,0,0.3), 0px 1px 3px 1px rgba(0,0,0,0.15)` | Cards (low elevation) |
|
|
20
|
+
| `level2` | `0px 1px 2px rgba(0,0,0,0.3), 0px 2px 6px 2px rgba(0,0,0,0.15)` | Raised cards, FAB (resting) |
|
|
21
|
+
| `level3` | `0px 4px 8px 3px rgba(0,0,0,0.15), 0px 1px 3px rgba(0,0,0,0.3)` | Dialogs, menus |
|
|
21
22
|
| `level4` | `0px 6px 10px 4px rgba(0,0,0,0.15), 0px 2px 3px rgba(0,0,0,0.3)` | FAB (hover), navigation drawers |
|
|
22
|
-
| `level5` | `0px 8px 12px 6px rgba(0,0,0,0.15), 0px 4px 4px rgba(0,0,0,0.3)` | Modals, navigation bars
|
|
23
|
+
| `level5` | `0px 8px 12px 6px rgba(0,0,0,0.15), 0px 4px 4px rgba(0,0,0,0.3)` | Modals, navigation bars |
|
|
23
24
|
|
|
24
25
|
## M3 Elevation Strategy
|
|
25
26
|
|
|
@@ -54,30 +55,30 @@ Shadows can be added for additional depth, but use sparingly:
|
|
|
54
55
|
// Card with subtle shadow
|
|
55
56
|
const card = css({
|
|
56
57
|
bg: 'surfaceContainerLow',
|
|
57
|
-
boxShadow: 'level1'
|
|
58
|
+
boxShadow: 'level1', // Optional shadow
|
|
58
59
|
});
|
|
59
60
|
|
|
60
61
|
// Dialog with shadow
|
|
61
62
|
const dialog = css({
|
|
62
63
|
bg: 'surfaceContainerHigh',
|
|
63
|
-
boxShadow: 'level3'
|
|
64
|
+
boxShadow: 'level3',
|
|
64
65
|
});
|
|
65
66
|
```
|
|
66
67
|
|
|
67
68
|
## Component Elevation Mapping
|
|
68
69
|
|
|
69
|
-
| Component
|
|
70
|
-
|
|
71
|
-
| Page background
|
|
72
|
-
| Filled Card
|
|
73
|
-
| Outlined Card
|
|
74
|
-
| Elevated Card
|
|
75
|
-
| Input (filled)
|
|
76
|
-
| Button (filled)
|
|
77
|
-
| Button (elevated) | `surfaceContainerLow`
|
|
78
|
-
| Dialog
|
|
79
|
-
| Menu
|
|
80
|
-
| Navigation Drawer | `surfaceContainerLow`
|
|
70
|
+
| Component | Surface Container | Shadow Level | Elevation |
|
|
71
|
+
| ----------------- | ------------------------- | ------------ | --------- |
|
|
72
|
+
| Page background | `surface` | `level0` | 0dp |
|
|
73
|
+
| Filled Card | `surface` | `level0` | 0dp |
|
|
74
|
+
| Outlined Card | `surface` | `level0` | 0dp |
|
|
75
|
+
| Elevated Card | `surfaceContainerLow` | `level1` | 1dp |
|
|
76
|
+
| Input (filled) | `surfaceContainerHighest` | `level0` | 0dp |
|
|
77
|
+
| Button (filled) | `primary` | `level0` | 0dp |
|
|
78
|
+
| Button (elevated) | `surfaceContainerLow` | `level1` | 1dp |
|
|
79
|
+
| Dialog | `surfaceContainerHigh` | `level3` | 3dp |
|
|
80
|
+
| Menu | `surfaceContainer` | `level2` | 2dp |
|
|
81
|
+
| Navigation Drawer | `surfaceContainerLow` | `level1` | 1dp |
|
|
81
82
|
|
|
82
83
|
## Usage Patterns
|
|
83
84
|
|
|
@@ -88,20 +89,20 @@ const dialog = css({
|
|
|
88
89
|
const filledCard = css({
|
|
89
90
|
bg: 'surface',
|
|
90
91
|
borderColor: 'outlineVariant',
|
|
91
|
-
borderWidth: '1px'
|
|
92
|
+
borderWidth: '1px',
|
|
92
93
|
});
|
|
93
94
|
|
|
94
95
|
// Elevated card
|
|
95
96
|
const elevatedCard = css({
|
|
96
97
|
bg: 'surfaceContainerLow',
|
|
97
|
-
boxShadow: 'level1'
|
|
98
|
+
boxShadow: 'level1', // Optional
|
|
98
99
|
});
|
|
99
100
|
|
|
100
101
|
// Outlined card (no elevation)
|
|
101
102
|
const outlinedCard = css({
|
|
102
103
|
bg: 'surface',
|
|
103
104
|
borderColor: 'outline',
|
|
104
|
-
borderWidth: '1px'
|
|
105
|
+
borderWidth: '1px',
|
|
105
106
|
});
|
|
106
107
|
```
|
|
107
108
|
|
|
@@ -112,19 +113,19 @@ const outlinedCard = css({
|
|
|
112
113
|
const dialog = css({
|
|
113
114
|
bg: 'surfaceContainerHigh',
|
|
114
115
|
boxShadow: 'level3',
|
|
115
|
-
borderRadius: 'large'
|
|
116
|
+
borderRadius: 'large',
|
|
116
117
|
});
|
|
117
118
|
|
|
118
119
|
// Full-screen modal
|
|
119
120
|
const modal = css({
|
|
120
121
|
bg: 'surfaceContainerHighest',
|
|
121
|
-
boxShadow: 'level5'
|
|
122
|
+
boxShadow: 'level5',
|
|
122
123
|
});
|
|
123
124
|
|
|
124
125
|
// Scrim (overlay behind dialog)
|
|
125
126
|
const scrim = css({
|
|
126
127
|
bg: 'scrim',
|
|
127
|
-
opacity: 0.32
|
|
128
|
+
opacity: 0.32,
|
|
128
129
|
});
|
|
129
130
|
```
|
|
130
131
|
|
|
@@ -138,8 +139,8 @@ const button = css({
|
|
|
138
139
|
bg: 'surfaceContainerLow',
|
|
139
140
|
boxShadow: 'level1',
|
|
140
141
|
_hover: {
|
|
141
|
-
boxShadow: 'level2'
|
|
142
|
-
}
|
|
142
|
+
boxShadow: 'level2', // Increase shadow on hover
|
|
143
|
+
},
|
|
143
144
|
});
|
|
144
145
|
|
|
145
146
|
// Card hover
|
|
@@ -148,8 +149,8 @@ const card = css({
|
|
|
148
149
|
boxShadow: 'level1',
|
|
149
150
|
transition: 'box-shadow 0.2s',
|
|
150
151
|
_hover: {
|
|
151
|
-
boxShadow: 'level2'
|
|
152
|
-
}
|
|
152
|
+
boxShadow: 'level2',
|
|
153
|
+
},
|
|
153
154
|
});
|
|
154
155
|
```
|
|
155
156
|
|
|
@@ -158,24 +159,24 @@ const card = css({
|
|
|
158
159
|
```typescript
|
|
159
160
|
// ❌ NEVER use arbitrary shadow values
|
|
160
161
|
const wrong = css({
|
|
161
|
-
boxShadow: '0 4px 6px rgba(0,0,0,0.1)'
|
|
162
|
+
boxShadow: '0 4px 6px rgba(0,0,0,0.1)',
|
|
162
163
|
});
|
|
163
164
|
|
|
164
165
|
// ❌ NEVER use heavy shadows (old Material Design style)
|
|
165
166
|
const wrong = css({
|
|
166
|
-
boxShadow: '0 10px 40px rgba(0,0,0,0.5)'
|
|
167
|
+
boxShadow: '0 10px 40px rgba(0,0,0,0.5)',
|
|
167
168
|
});
|
|
168
169
|
|
|
169
170
|
// ❌ NEVER use elevation without surface tints
|
|
170
171
|
const wrong = css({
|
|
171
|
-
bg: '#FFFFFF',
|
|
172
|
-
boxShadow: 'level1'
|
|
172
|
+
bg: '#FFFFFF', // Raw color
|
|
173
|
+
boxShadow: 'level1',
|
|
173
174
|
});
|
|
174
175
|
|
|
175
176
|
// ✅ ALWAYS use surface containers + optional shadows
|
|
176
177
|
const correct = css({
|
|
177
178
|
bg: 'surfaceContainerLow',
|
|
178
|
-
boxShadow: 'level1'
|
|
179
|
+
boxShadow: 'level1',
|
|
179
180
|
});
|
|
180
181
|
```
|
|
181
182
|
|
|
@@ -197,26 +198,594 @@ Page Background (surface, level0)
|
|
|
197
198
|
└─ Button in Dialog (primary, level0)
|
|
198
199
|
```
|
|
199
200
|
|
|
201
|
+
## How Elevation Tokens Work With Other Tokens
|
|
202
|
+
|
|
203
|
+
Elevation tokens are rarely used alone. Here are real-world examples showing how shadows and surface tints combine with color, spacing, and typography tokens to create visual hierarchy:
|
|
204
|
+
|
|
205
|
+
### Elevated Card with Content Hierarchy
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
import { Card } from '@discourser/design-system';
|
|
209
|
+
import { css } from '@discourser/design-system/styled-system/css';
|
|
210
|
+
|
|
211
|
+
<Card variant="elevated">
|
|
212
|
+
{/* Card automatically combines: */}
|
|
213
|
+
{/* bg: 'surfaceContainerLow' - Color token for elevation */}
|
|
214
|
+
{/* boxShadow: 'level1' - Elevation token for depth */}
|
|
215
|
+
{/* borderRadius: 'l3' - Border radius - 12px */}
|
|
216
|
+
|
|
217
|
+
<div className={css({
|
|
218
|
+
p: 'lg', // Spacing - 24px padding
|
|
219
|
+
display: 'flex',
|
|
220
|
+
flexDirection: 'column',
|
|
221
|
+
gap: 'md' // Spacing - 16px between sections
|
|
222
|
+
})}>
|
|
223
|
+
|
|
224
|
+
<div className={css({
|
|
225
|
+
display: 'flex',
|
|
226
|
+
alignItems: 'center',
|
|
227
|
+
gap: 'sm', // Spacing - 8px between icon and title
|
|
228
|
+
mb: 'sm' // Spacing - 8px bottom margin
|
|
229
|
+
})}>
|
|
230
|
+
<div className={css({
|
|
231
|
+
bg: 'primaryContainer', // Color - icon container
|
|
232
|
+
color: 'onPrimaryContainer', // Color - icon color
|
|
233
|
+
p: 'sm', // Spacing - 8px padding
|
|
234
|
+
borderRadius: 'full', // Border radius - circular
|
|
235
|
+
boxShadow: 'level0' // Elevation - flat (no shadow)
|
|
236
|
+
})}>
|
|
237
|
+
<StarIcon size="20px" />
|
|
238
|
+
</div>
|
|
239
|
+
|
|
240
|
+
<h3 className={css({
|
|
241
|
+
textStyle: 'titleLarge', // Typography - 22px/28px
|
|
242
|
+
color: 'onSurface' // Color - primary text
|
|
243
|
+
})}>
|
|
244
|
+
Featured Content
|
|
245
|
+
</h3>
|
|
246
|
+
</div>
|
|
247
|
+
|
|
248
|
+
<p className={css({
|
|
249
|
+
textStyle: 'bodyMedium', // Typography - 14px/20px
|
|
250
|
+
color: 'onSurfaceVariant', // Color - secondary text
|
|
251
|
+
mb: 'md' // Spacing - 16px bottom margin
|
|
252
|
+
})}>
|
|
253
|
+
This card demonstrates how elevation creates visual hierarchy when
|
|
254
|
+
combined with proper spacing and typography.
|
|
255
|
+
</p>
|
|
256
|
+
|
|
257
|
+
<div className={css({
|
|
258
|
+
display: 'flex',
|
|
259
|
+
gap: 'sm' // Spacing - 8px between buttons
|
|
260
|
+
})}>
|
|
261
|
+
<Button variant="filled">
|
|
262
|
+
{/* Button sits on elevated surface */}
|
|
263
|
+
{/* bg: 'primary' - Color */}
|
|
264
|
+
{/* boxShadow: 'level0' - No additional shadow needed */}
|
|
265
|
+
{/* px: 'lg' - Spacing (24px) */}
|
|
266
|
+
Learn More
|
|
267
|
+
</Button>
|
|
268
|
+
</div>
|
|
269
|
+
</div>
|
|
270
|
+
</Card>
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Dialog Over Page Content (Multi-Layer Elevation)
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
import { Dialog, Button } from '@discourser/design-system';
|
|
277
|
+
import { css } from '@discourser/design-system/styled-system/css';
|
|
278
|
+
|
|
279
|
+
<Dialog.Root>
|
|
280
|
+
{/* Layer 1: Page Background (level0) */}
|
|
281
|
+
<div className={css({
|
|
282
|
+
bg: 'surface', // Color - page background (level0)
|
|
283
|
+
p: 'xl' // Spacing - 32px padding
|
|
284
|
+
})}>
|
|
285
|
+
<Card variant="elevated">
|
|
286
|
+
{/* Layer 2: Elevated Card (level1) */}
|
|
287
|
+
{/* bg: 'surfaceContainerLow' - Color for level1 */}
|
|
288
|
+
{/* boxShadow: 'level1' - Elevation shadow */}
|
|
289
|
+
<div className={css({ p: 'lg' })}>
|
|
290
|
+
<h2 className={css({
|
|
291
|
+
textStyle: 'headlineMedium', // Typography - 28px/36px
|
|
292
|
+
color: 'onSurface' // Color - primary text
|
|
293
|
+
})}>
|
|
294
|
+
Page Content
|
|
295
|
+
</h2>
|
|
296
|
+
</div>
|
|
297
|
+
</Card>
|
|
298
|
+
</div>
|
|
299
|
+
|
|
300
|
+
{/* Layer 3: Scrim/Backdrop (over everything) */}
|
|
301
|
+
<Dialog.Backdrop className={css({
|
|
302
|
+
bg: 'scrim', // Color - black overlay
|
|
303
|
+
opacity: 0.32 // Semi-transparent
|
|
304
|
+
})} />
|
|
305
|
+
|
|
306
|
+
{/* Layer 4: Dialog (highest elevation - level3) */}
|
|
307
|
+
<Dialog.Content className={css({
|
|
308
|
+
bg: 'surfaceContainerHigh', // Color - high elevation surface
|
|
309
|
+
color: 'onSurface', // Color - primary text
|
|
310
|
+
borderRadius: 'l4', // Border radius - 28px
|
|
311
|
+
boxShadow: 'level3', // Elevation - dialog shadow (floats above all)
|
|
312
|
+
p: 'xl', // Spacing - 32px padding
|
|
313
|
+
maxWidth: '480px',
|
|
314
|
+
display: 'flex',
|
|
315
|
+
flexDirection: 'column',
|
|
316
|
+
gap: 'lg' // Spacing - 24px between sections
|
|
317
|
+
})}>
|
|
318
|
+
|
|
319
|
+
<Dialog.Title className={css({
|
|
320
|
+
textStyle: 'headlineSmall', // Typography - 24px/32px
|
|
321
|
+
color: 'onSurface', // Color - primary text on elevated surface
|
|
322
|
+
mb: 'sm' // Spacing - 8px bottom margin
|
|
323
|
+
})}>
|
|
324
|
+
Confirm Action
|
|
325
|
+
</Dialog.Title>
|
|
326
|
+
|
|
327
|
+
<Dialog.Description className={css({
|
|
328
|
+
textStyle: 'bodyMedium', // Typography - 14px/20px
|
|
329
|
+
color: 'onSurfaceVariant' // Color - secondary text
|
|
330
|
+
})}>
|
|
331
|
+
This dialog demonstrates the highest elevation level, floating above
|
|
332
|
+
the backdrop and all page content.
|
|
333
|
+
</Dialog.Description>
|
|
334
|
+
|
|
335
|
+
<div className={css({
|
|
336
|
+
display: 'flex',
|
|
337
|
+
gap: 'sm', // Spacing - 8px between buttons
|
|
338
|
+
justifyContent: 'flex-end',
|
|
339
|
+
mt: 'md' // Spacing - 16px top margin
|
|
340
|
+
})}>
|
|
341
|
+
<Button variant="text">
|
|
342
|
+
{/* No elevation change needed on elevated surface */}
|
|
343
|
+
{/* color: 'primary' - Color */}
|
|
344
|
+
Cancel
|
|
345
|
+
</Button>
|
|
346
|
+
<Button variant="filled">
|
|
347
|
+
{/* bg: 'primary' - Color */}
|
|
348
|
+
{/* boxShadow: 'level0' - No shadow (already on elevated surface) */}
|
|
349
|
+
Confirm
|
|
350
|
+
</Button>
|
|
351
|
+
</div>
|
|
352
|
+
</Dialog.Content>
|
|
353
|
+
</Dialog.Root>
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Interactive Elevation Changes (Hover States)
|
|
357
|
+
|
|
358
|
+
```typescript
|
|
359
|
+
import { css } from '@discourser/design-system/styled-system/css';
|
|
360
|
+
|
|
361
|
+
<div className={css({
|
|
362
|
+
display: 'grid',
|
|
363
|
+
gridTemplateColumns: { base: '1fr', md: 'repeat(3, 1fr)' },
|
|
364
|
+
gap: 'lg', // Spacing - 24px between cards
|
|
365
|
+
p: 'xl' // Spacing - 32px padding
|
|
366
|
+
})}>
|
|
367
|
+
{products.map(product => (
|
|
368
|
+
<article key={product.id} className={css({
|
|
369
|
+
bg: 'surfaceContainerLow', // Color - elevated surface (level1)
|
|
370
|
+
borderRadius: 'l3', // Border radius - 12px
|
|
371
|
+
boxShadow: 'level1', // Elevation - default card shadow
|
|
372
|
+
p: 'lg', // Spacing - 24px padding
|
|
373
|
+
transition: 'box-shadow 0.2s cubic-bezier(0.4, 0, 0.2, 1)', // M3 motion
|
|
374
|
+
cursor: 'pointer',
|
|
375
|
+
_hover: {
|
|
376
|
+
boxShadow: 'level2' // Elevation - increase on hover
|
|
377
|
+
},
|
|
378
|
+
_active: {
|
|
379
|
+
boxShadow: 'level0' // Elevation - decrease on click
|
|
380
|
+
}
|
|
381
|
+
})}>
|
|
382
|
+
|
|
383
|
+
<div className={css({
|
|
384
|
+
bg: 'surfaceContainer', // Color - slightly higher elevation
|
|
385
|
+
borderRadius: 'l2', // Border radius - 8px
|
|
386
|
+
p: 'md', // Spacing - 16px padding
|
|
387
|
+
mb: 'md' // Spacing - 16px bottom margin
|
|
388
|
+
})}>
|
|
389
|
+
<img
|
|
390
|
+
src={product.image}
|
|
391
|
+
alt={product.name}
|
|
392
|
+
className={css({
|
|
393
|
+
width: '100%',
|
|
394
|
+
height: 'auto',
|
|
395
|
+
borderRadius: 'l1' // Border radius - 4px
|
|
396
|
+
})}
|
|
397
|
+
/>
|
|
398
|
+
</div>
|
|
399
|
+
|
|
400
|
+
<h3 className={css({
|
|
401
|
+
textStyle: 'titleMedium', // Typography - 16px/24px
|
|
402
|
+
color: 'onSurface', // Color - primary text
|
|
403
|
+
mb: 'xs' // Spacing - 4px bottom margin
|
|
404
|
+
})}>
|
|
405
|
+
{product.name}
|
|
406
|
+
</h3>
|
|
407
|
+
|
|
408
|
+
<p className={css({
|
|
409
|
+
textStyle: 'bodySmall', // Typography - 12px/16px
|
|
410
|
+
color: 'onSurfaceVariant', // Color - secondary text
|
|
411
|
+
mb: 'md' // Spacing - 16px bottom margin
|
|
412
|
+
})}>
|
|
413
|
+
{product.description}
|
|
414
|
+
</p>
|
|
415
|
+
|
|
416
|
+
<div className={css({
|
|
417
|
+
display: 'flex',
|
|
418
|
+
justifyContent: 'space-between',
|
|
419
|
+
alignItems: 'center'
|
|
420
|
+
})}>
|
|
421
|
+
<span className={css({
|
|
422
|
+
textStyle: 'titleLarge', // Typography - 22px/28px
|
|
423
|
+
color: 'primary' // Color - brand color
|
|
424
|
+
})}>
|
|
425
|
+
${product.price}
|
|
426
|
+
</span>
|
|
427
|
+
|
|
428
|
+
<Button variant="filled" size="sm">
|
|
429
|
+
{/* px: 'md' - Spacing (16px) */}
|
|
430
|
+
{/* bg: 'primary' - Color */}
|
|
431
|
+
{/* No additional shadow - already on elevated card */}
|
|
432
|
+
Add to Cart
|
|
433
|
+
</Button>
|
|
434
|
+
</div>
|
|
435
|
+
</article>
|
|
436
|
+
))}
|
|
437
|
+
</div>
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
### Floating Action Button (FAB) with Elevation States
|
|
441
|
+
|
|
442
|
+
```typescript
|
|
443
|
+
import { css } from '@discourser/design-system/styled-system/css';
|
|
444
|
+
|
|
445
|
+
<button className={css({
|
|
446
|
+
position: 'fixed',
|
|
447
|
+
bottom: 'xl', // Spacing - 32px from bottom
|
|
448
|
+
right: 'xl', // Spacing - 32px from right
|
|
449
|
+
width: '56px', // Size - standard FAB size
|
|
450
|
+
height: '56px', // Size - standard FAB size
|
|
451
|
+
bg: 'primary', // Color - primary brand color
|
|
452
|
+
color: 'onPrimary', // Color - white icon
|
|
453
|
+
borderRadius: 'l3', // Border radius - 12px (M3 style)
|
|
454
|
+
boxShadow: 'level2', // Elevation - FAB resting state (floats)
|
|
455
|
+
display: 'flex',
|
|
456
|
+
alignItems: 'center',
|
|
457
|
+
justifyContent: 'center',
|
|
458
|
+
transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)', // M3 motion
|
|
459
|
+
cursor: 'pointer',
|
|
460
|
+
_hover: {
|
|
461
|
+
boxShadow: 'level4', // Elevation - increase significantly on hover
|
|
462
|
+
transform: 'scale(1.05)' // Slight scale increase
|
|
463
|
+
},
|
|
464
|
+
_active: {
|
|
465
|
+
boxShadow: 'level2', // Elevation - return to resting
|
|
466
|
+
transform: 'scale(0.95)' // Slight press effect
|
|
467
|
+
}
|
|
468
|
+
})}>
|
|
469
|
+
<AddIcon size="24px" /> {/* Icon size: 24px */}
|
|
470
|
+
</button>
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
### Menu/Dropdown with Proper Elevation
|
|
474
|
+
|
|
475
|
+
```typescript
|
|
476
|
+
import { Menu } from '@discourser/design-system';
|
|
477
|
+
import { css } from '@discourser/design-system/styled-system/css';
|
|
478
|
+
|
|
479
|
+
<Menu.Root>
|
|
480
|
+
<Menu.Trigger asChild>
|
|
481
|
+
<Button variant="outlined">
|
|
482
|
+
{/* Trigger button: */}
|
|
483
|
+
{/* borderColor: 'outline' - Color */}
|
|
484
|
+
{/* px: 'lg' - Spacing (24px) */}
|
|
485
|
+
Options
|
|
486
|
+
</Button>
|
|
487
|
+
</Menu.Trigger>
|
|
488
|
+
|
|
489
|
+
<Menu.Content className={css({
|
|
490
|
+
bg: 'surfaceContainer', // Color - menu elevation (level2/3)
|
|
491
|
+
borderRadius: 'l2', // Border radius - 8px
|
|
492
|
+
boxShadow: 'level2', // Elevation - menu floats above content
|
|
493
|
+
p: 'xs', // Spacing - 4px padding
|
|
494
|
+
minWidth: '200px',
|
|
495
|
+
border: 'none'
|
|
496
|
+
})}>
|
|
497
|
+
|
|
498
|
+
<Menu.Item className={css({
|
|
499
|
+
px: 'md', // Spacing - 16px horizontal padding
|
|
500
|
+
py: 'sm', // Spacing - 8px vertical padding
|
|
501
|
+
borderRadius: 'l1', // Border radius - 4px
|
|
502
|
+
textStyle: 'bodyMedium', // Typography - 14px/20px
|
|
503
|
+
color: 'onSurface', // Color - primary text
|
|
504
|
+
display: 'flex',
|
|
505
|
+
alignItems: 'center',
|
|
506
|
+
gap: 'sm', // Spacing - 8px between icon and text
|
|
507
|
+
_hover: {
|
|
508
|
+
bg: 'surfaceContainerHighest' // Color - hover background (higher than menu)
|
|
509
|
+
},
|
|
510
|
+
_focus: {
|
|
511
|
+
bg: 'surfaceContainerHighest',
|
|
512
|
+
outline: '2px solid',
|
|
513
|
+
outlineColor: 'primary', // Color - focus ring
|
|
514
|
+
outlineOffset: '-2px'
|
|
515
|
+
}
|
|
516
|
+
})}>
|
|
517
|
+
<EditIcon size="18px" />
|
|
518
|
+
Edit
|
|
519
|
+
</Menu.Item>
|
|
520
|
+
|
|
521
|
+
<Menu.Item className={css({
|
|
522
|
+
px: 'md',
|
|
523
|
+
py: 'sm',
|
|
524
|
+
borderRadius: 'l1',
|
|
525
|
+
textStyle: 'bodyMedium',
|
|
526
|
+
color: 'onSurface',
|
|
527
|
+
display: 'flex',
|
|
528
|
+
alignItems: 'center',
|
|
529
|
+
gap: 'sm'
|
|
530
|
+
})}>
|
|
531
|
+
<ShareIcon size="18px" />
|
|
532
|
+
Share
|
|
533
|
+
</Menu.Item>
|
|
534
|
+
|
|
535
|
+
<Menu.Separator className={css({
|
|
536
|
+
my: 'xs', // Spacing - 4px vertical margin
|
|
537
|
+
height: '1px',
|
|
538
|
+
bg: 'outlineVariant' // Color - subtle divider
|
|
539
|
+
})} />
|
|
540
|
+
|
|
541
|
+
<Menu.Item className={css({
|
|
542
|
+
px: 'md',
|
|
543
|
+
py: 'sm',
|
|
544
|
+
borderRadius: 'l1',
|
|
545
|
+
textStyle: 'bodyMedium',
|
|
546
|
+
color: 'error', // Color - destructive action
|
|
547
|
+
display: 'flex',
|
|
548
|
+
alignItems: 'center',
|
|
549
|
+
gap: 'sm',
|
|
550
|
+
_hover: {
|
|
551
|
+
bg: 'errorContainer', // Color - error background on hover
|
|
552
|
+
color: 'onErrorContainer' // Color - maintain contrast
|
|
553
|
+
}
|
|
554
|
+
})}>
|
|
555
|
+
<DeleteIcon size="18px" />
|
|
556
|
+
Delete
|
|
557
|
+
</Menu.Item>
|
|
558
|
+
</Menu.Content>
|
|
559
|
+
</Menu.Root>
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
### Snackbar/Toast with Inverse Surface
|
|
563
|
+
|
|
564
|
+
```typescript
|
|
565
|
+
import { css } from '@discourser/design-system/styled-system/css';
|
|
566
|
+
|
|
567
|
+
<div className={css({
|
|
568
|
+
position: 'fixed',
|
|
569
|
+
bottom: 'lg', // Spacing - 24px from bottom
|
|
570
|
+
left: '50%',
|
|
571
|
+
transform: 'translateX(-50%)',
|
|
572
|
+
bg: 'inverseSurface', // Color - inverse surface (dark in light mode)
|
|
573
|
+
color: 'inverseOnSurface', // Color - light text on dark background
|
|
574
|
+
boxShadow: 'level2', // Elevation - floats above content
|
|
575
|
+
borderRadius: 'l1', // Border radius - 4px
|
|
576
|
+
px: 'md', // Spacing - 16px horizontal padding
|
|
577
|
+
py: 'sm', // Spacing - 8px vertical padding
|
|
578
|
+
display: 'flex',
|
|
579
|
+
alignItems: 'center',
|
|
580
|
+
gap: 'md', // Spacing - 16px between icon and content
|
|
581
|
+
minWidth: '344px',
|
|
582
|
+
maxWidth: '672px'
|
|
583
|
+
})}>
|
|
584
|
+
|
|
585
|
+
<CheckCircleIcon className={css({
|
|
586
|
+
color: 'inversePrimary', // Color - brand color on inverse surface
|
|
587
|
+
flexShrink: 0
|
|
588
|
+
})} size="20px" />
|
|
589
|
+
|
|
590
|
+
<div className={css({ flex: 1 })}>
|
|
591
|
+
<p className={css({
|
|
592
|
+
textStyle: 'bodyMedium', // Typography - 14px/20px
|
|
593
|
+
color: 'inverseOnSurface' // Color - light text
|
|
594
|
+
})}>
|
|
595
|
+
Your changes have been saved successfully!
|
|
596
|
+
</p>
|
|
597
|
+
</div>
|
|
598
|
+
|
|
599
|
+
<Button variant="text" size="sm" className={css({
|
|
600
|
+
color: 'inversePrimary', // Color - action button on inverse
|
|
601
|
+
px: 'sm', // Spacing - 8px horizontal
|
|
602
|
+
_hover: {
|
|
603
|
+
bg: 'rgba(255, 255, 255, 0.08)' // Subtle hover on inverse
|
|
604
|
+
}
|
|
605
|
+
})}>
|
|
606
|
+
Undo
|
|
607
|
+
</Button>
|
|
608
|
+
|
|
609
|
+
<IconButton variant="ghost" size="sm" className={css({
|
|
610
|
+
color: 'inverseOnSurface', // Color - close button
|
|
611
|
+
_hover: {
|
|
612
|
+
bg: 'rgba(255, 255, 255, 0.08)'
|
|
613
|
+
}
|
|
614
|
+
})}>
|
|
615
|
+
<CloseIcon size="18px" />
|
|
616
|
+
</IconButton>
|
|
617
|
+
</div>
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
### Navigation Drawer with Scrim
|
|
621
|
+
|
|
622
|
+
```typescript
|
|
623
|
+
import { css } from '@discourser/design-system/styled-system/css';
|
|
624
|
+
|
|
625
|
+
<>
|
|
626
|
+
{/* Scrim overlay */}
|
|
627
|
+
<div className={css({
|
|
628
|
+
position: 'fixed',
|
|
629
|
+
inset: 0,
|
|
630
|
+
bg: 'scrim', // Color - black overlay
|
|
631
|
+
opacity: isOpen ? 0.32 : 0,
|
|
632
|
+
transition: 'opacity 0.2s',
|
|
633
|
+
pointerEvents: isOpen ? 'auto' : 'none'
|
|
634
|
+
})} />
|
|
635
|
+
|
|
636
|
+
{/* Navigation drawer */}
|
|
637
|
+
<nav className={css({
|
|
638
|
+
position: 'fixed',
|
|
639
|
+
top: 0,
|
|
640
|
+
left: 0,
|
|
641
|
+
height: '100vh',
|
|
642
|
+
width: '280px',
|
|
643
|
+
bg: 'surfaceContainerLow', // Color - elevated drawer surface
|
|
644
|
+
boxShadow: 'level1', // Elevation - drawer shadow
|
|
645
|
+
transform: isOpen ? 'translateX(0)' : 'translateX(-100%)',
|
|
646
|
+
transition: 'transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)', // M3 motion
|
|
647
|
+
p: 'md', // Spacing - 16px padding
|
|
648
|
+
display: 'flex',
|
|
649
|
+
flexDirection: 'column',
|
|
650
|
+
gap: 'lg' // Spacing - 24px between sections
|
|
651
|
+
})}>
|
|
652
|
+
|
|
653
|
+
<div className={css({
|
|
654
|
+
display: 'flex',
|
|
655
|
+
alignItems: 'center',
|
|
656
|
+
gap: 'md', // Spacing - 16px between logo and title
|
|
657
|
+
pb: 'md', // Spacing - 16px bottom padding
|
|
658
|
+
borderBottomWidth: '1px',
|
|
659
|
+
borderBottomColor: 'outlineVariant' // Color - divider
|
|
660
|
+
})}>
|
|
661
|
+
<Logo size="32px" />
|
|
662
|
+
<h2 className={css({
|
|
663
|
+
textStyle: 'titleLarge', // Typography - 22px/28px
|
|
664
|
+
color: 'onSurface' // Color - primary text
|
|
665
|
+
})}>
|
|
666
|
+
App Name
|
|
667
|
+
</h2>
|
|
668
|
+
</div>
|
|
669
|
+
|
|
670
|
+
<ul className={css({
|
|
671
|
+
display: 'flex',
|
|
672
|
+
flexDirection: 'column',
|
|
673
|
+
gap: 'xxs' // Spacing - 2px between nav items
|
|
674
|
+
})}>
|
|
675
|
+
<li>
|
|
676
|
+
<a className={css({
|
|
677
|
+
display: 'flex',
|
|
678
|
+
alignItems: 'center',
|
|
679
|
+
gap: 'md', // Spacing - 16px between icon and label
|
|
680
|
+
px: 'md', // Spacing - 16px horizontal padding
|
|
681
|
+
py: 'sm', // Spacing - 8px vertical padding
|
|
682
|
+
borderRadius: 'l1', // Border radius - 4px
|
|
683
|
+
textStyle: 'labelLarge', // Typography - 14px/20px
|
|
684
|
+
color: 'onSurface', // Color - text
|
|
685
|
+
_hover: {
|
|
686
|
+
bg: 'surfaceContainerHighest' // Color - hover (higher than drawer)
|
|
687
|
+
}
|
|
688
|
+
})}>
|
|
689
|
+
<HomeIcon size="24px" />
|
|
690
|
+
Home
|
|
691
|
+
</a>
|
|
692
|
+
</li>
|
|
693
|
+
</ul>
|
|
694
|
+
</nav>
|
|
695
|
+
</>
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
### Multi-Token Elevation Pattern Summary
|
|
699
|
+
|
|
700
|
+
When combining elevation with other tokens, follow these patterns:
|
|
701
|
+
|
|
702
|
+
**Elevated Surface Pattern:**
|
|
703
|
+
|
|
704
|
+
```typescript
|
|
705
|
+
{
|
|
706
|
+
bg: 'surfaceContainer*', // Color - elevation surface
|
|
707
|
+
boxShadow: 'level1-3', // Elevation - subtle shadow
|
|
708
|
+
borderRadius: 'l1-l4', // Border radius
|
|
709
|
+
p: 'md-xl', // Spacing - internal padding
|
|
710
|
+
color: 'onSurface' // Color - text maintains contrast
|
|
711
|
+
}
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
**Interactive Elevation Pattern:**
|
|
715
|
+
|
|
716
|
+
```typescript
|
|
717
|
+
{
|
|
718
|
+
bg: 'surfaceContainerLow', // Color - base elevation
|
|
719
|
+
boxShadow: 'level1', // Elevation - resting state
|
|
720
|
+
transition: 'box-shadow 0.2s', // M3 motion timing
|
|
721
|
+
_hover: {
|
|
722
|
+
boxShadow: 'level2' // Elevation - increase on hover
|
|
723
|
+
},
|
|
724
|
+
_active: {
|
|
725
|
+
boxShadow: 'level0' // Elevation - decrease on press
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
**Overlay Pattern:**
|
|
731
|
+
|
|
732
|
+
```typescript
|
|
733
|
+
{
|
|
734
|
+
// Backdrop:
|
|
735
|
+
bg: 'scrim', // Color - black overlay
|
|
736
|
+
opacity: 0.32, // Semi-transparent
|
|
737
|
+
|
|
738
|
+
// Elevated content:
|
|
739
|
+
bg: 'surfaceContainerHigh', // Color - high elevation
|
|
740
|
+
boxShadow: 'level3-5', // Elevation - floats above backdrop
|
|
741
|
+
p: 'xl', // Spacing - generous padding
|
|
742
|
+
borderRadius: 'l3-l4' // Border radius - soft corners
|
|
743
|
+
}
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
**Layering Hierarchy:**
|
|
747
|
+
|
|
748
|
+
```typescript
|
|
749
|
+
// Level 0 (Page):
|
|
750
|
+
{ bg: 'surface', boxShadow: 'level0' }
|
|
751
|
+
|
|
752
|
+
// Level 1 (Cards):
|
|
753
|
+
{ bg: 'surfaceContainerLow', boxShadow: 'level1' }
|
|
754
|
+
|
|
755
|
+
// Level 2 (Menus):
|
|
756
|
+
{ bg: 'surfaceContainer', boxShadow: 'level2' }
|
|
757
|
+
|
|
758
|
+
// Level 3 (Dialogs):
|
|
759
|
+
{ bg: 'surfaceContainerHigh', boxShadow: 'level3' }
|
|
760
|
+
|
|
761
|
+
// Level 4-5 (Modals):
|
|
762
|
+
{ bg: 'surfaceContainerHighest', boxShadow: 'level4-5' }
|
|
763
|
+
```
|
|
764
|
+
|
|
200
765
|
## Accessibility
|
|
201
766
|
|
|
202
767
|
### Color Contrast
|
|
768
|
+
|
|
203
769
|
Surface tint changes must maintain adequate contrast:
|
|
770
|
+
|
|
204
771
|
- Text on elevated surfaces still uses `onSurface` or `onSurfaceVariant`
|
|
205
772
|
- The design system automatically handles this
|
|
206
773
|
|
|
207
774
|
### Motion and Transitions
|
|
775
|
+
|
|
208
776
|
When elevation changes on interaction:
|
|
209
777
|
|
|
210
778
|
```typescript
|
|
211
779
|
const card = css({
|
|
212
780
|
transition: 'box-shadow 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
|
|
213
781
|
_hover: {
|
|
214
|
-
boxShadow: 'level2'
|
|
215
|
-
}
|
|
782
|
+
boxShadow: 'level2',
|
|
783
|
+
},
|
|
216
784
|
});
|
|
217
785
|
```
|
|
218
786
|
|
|
219
787
|
Use M3 motion tokens for consistent timing:
|
|
788
|
+
|
|
220
789
|
- `fast` (100ms) - Subtle changes
|
|
221
790
|
- `normal` (200ms) - Standard transitions
|
|
222
791
|
- `slow` (300ms) - Emphasized changes
|
|
@@ -224,6 +793,7 @@ Use M3 motion tokens for consistent timing:
|
|
|
224
793
|
## Dark Mode
|
|
225
794
|
|
|
226
795
|
Elevation behaves differently in dark mode:
|
|
796
|
+
|
|
227
797
|
- **Light mode**: Higher elevation = lighter background
|
|
228
798
|
- **Dark mode**: Higher elevation = lighter background (more tint)
|
|
229
799
|
|
|
@@ -265,10 +835,10 @@ Only use manual elevation tokens when creating custom layouts outside of compone
|
|
|
265
835
|
|
|
266
836
|
## Common Elevation Mistakes
|
|
267
837
|
|
|
268
|
-
| ❌ Wrong
|
|
269
|
-
|
|
270
|
-
| Dialog at level1
|
|
271
|
-
| Heavy `boxShadow: '0 10px 50px'`
|
|
272
|
-
| Card at level5
|
|
273
|
-
| `bg: '#FFF', boxShadow: 'level1'` | `bg: 'surfaceContainerLow'`
|
|
274
|
-
| No elevation for dialogs
|
|
838
|
+
| ❌ Wrong | ✅ Right | Why |
|
|
839
|
+
| --------------------------------- | ---------------------------- | -------------------------------------- |
|
|
840
|
+
| Dialog at level1 | Dialog at level3 | Dialogs should float above content |
|
|
841
|
+
| Heavy `boxShadow: '0 10px 50px'` | `boxShadow: 'level1'` | M3 uses subtle shadows |
|
|
842
|
+
| Card at level5 | Card at level1 | Cards shouldn't float too high |
|
|
843
|
+
| `bg: '#FFF', boxShadow: 'level1'` | `bg: 'surfaceContainerLow'` | Use surface containers, not raw colors |
|
|
844
|
+
| No elevation for dialogs | `bg: 'surfaceContainerHigh'` | Dialogs need visual separation |
|