@discourser/design-system 0.3.0 → 0.3.1
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/guidelines/Guidelines.md +88 -0
- package/guidelines/components/button.md +314 -0
- package/guidelines/components/card.md +353 -0
- package/guidelines/components/dialog.md +465 -0
- package/guidelines/components/icon-button.md +417 -0
- package/guidelines/components/input.md +499 -0
- package/guidelines/components/switch.md +457 -0
- package/guidelines/design-tokens/colors.md +187 -0
- package/guidelines/design-tokens/elevation.md +274 -0
- package/guidelines/design-tokens/spacing.md +289 -0
- package/guidelines/design-tokens/typography.md +226 -0
- package/guidelines/overview-components.md +156 -0
- package/package.json +3 -2
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Discourser Design System Guidelines
|
|
2
|
+
|
|
3
|
+
This project uses the `@discourser/design-system` package, a Material Design 3 implementation built with Panda CSS and Ark UI.
|
|
4
|
+
|
|
5
|
+
## IMPORTANT: Always Read These First
|
|
6
|
+
|
|
7
|
+
Before writing any code, follow these steps IN ORDER:
|
|
8
|
+
|
|
9
|
+
### Step 1: Read Overview Files (REQUIRED)
|
|
10
|
+
Read ALL files with a name that starts with "overview-":
|
|
11
|
+
- `overview-components.md` - Available components and usage patterns
|
|
12
|
+
|
|
13
|
+
### Step 2: Read Design Token Files (REQUIRED)
|
|
14
|
+
Read ALL files in the `design-tokens/` folder:
|
|
15
|
+
- `design-tokens/colors.md`
|
|
16
|
+
- `design-tokens/typography.md`
|
|
17
|
+
- `design-tokens/spacing.md`
|
|
18
|
+
- `design-tokens/elevation.md`
|
|
19
|
+
|
|
20
|
+
### Step 3: Plan Components Needed (REQUIRED)
|
|
21
|
+
Identify which components you need to use.
|
|
22
|
+
|
|
23
|
+
### Step 4: Read Component Guidelines BEFORE Using Components (REQUIRED)
|
|
24
|
+
BEFORE using ANY component, you MUST read its guidelines file first:
|
|
25
|
+
- Using Button? → Read `components/button.md` FIRST
|
|
26
|
+
- Using Dialog? → Read `components/dialog.md` FIRST
|
|
27
|
+
- Using Input? → Read `components/input.md` FIRST
|
|
28
|
+
- Using Card? → Read `components/card.md` FIRST
|
|
29
|
+
- Using IconButton? → Read `components/icon-button.md` FIRST
|
|
30
|
+
- Using Switch? → Read `components/switch.md` FIRST
|
|
31
|
+
|
|
32
|
+
DO NOT write code using a component until you have read its specific guidelines.
|
|
33
|
+
|
|
34
|
+
## Core Principles
|
|
35
|
+
|
|
36
|
+
- **Always prefer design system components** over native HTML elements
|
|
37
|
+
- **Use semantic tokens** (e.g., `primary`, `onPrimary`) not raw colors
|
|
38
|
+
- **Follow M3 patterns** for variants, sizing, and state layers
|
|
39
|
+
- **Do not override styles** unless absolutely necessary
|
|
40
|
+
- **Never use inline styles** with raw color values
|
|
41
|
+
|
|
42
|
+
## Package Installation
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm install @discourser/design-system react react-dom
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Package Imports
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// Components
|
|
52
|
+
import { Button, Card, Dialog, Input, IconButton, Switch } from '@discourser/design-system';
|
|
53
|
+
|
|
54
|
+
// For advanced styling (use sparingly)
|
|
55
|
+
import { css } from '@discourser/design-system/styled-system/css';
|
|
56
|
+
import { button } from '@discourser/design-system/styled-system/recipes';
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Quick Reference
|
|
60
|
+
|
|
61
|
+
| Component | Variants | Sizes | Guidelines |
|
|
62
|
+
|-----------|----------|-------|------------|
|
|
63
|
+
| Button | filled, outlined, text, elevated, tonal | sm, md, lg | `components/button.md` |
|
|
64
|
+
| Card | elevated, filled, outlined | - | `components/card.md` |
|
|
65
|
+
| IconButton | standard, filled, tonal, outlined | sm, md, lg | `components/icon-button.md` |
|
|
66
|
+
| Input | filled, outlined | sm, md | `components/input.md` |
|
|
67
|
+
| Dialog | - | sm, md, lg, fullscreen | `components/dialog.md` |
|
|
68
|
+
| Switch | - | sm, md | `components/switch.md` |
|
|
69
|
+
|
|
70
|
+
## Theme Support
|
|
71
|
+
|
|
72
|
+
The design system supports light and dark themes. The theme is controlled by the `data-theme` attribute on a parent element (typically `html` or `body`):
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
// Light theme (default)
|
|
76
|
+
<html data-theme="light">
|
|
77
|
+
|
|
78
|
+
// Dark theme
|
|
79
|
+
<html data-theme="dark">
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
All semantic color tokens automatically adapt to the current theme.
|
|
83
|
+
|
|
84
|
+
## Getting Help
|
|
85
|
+
|
|
86
|
+
For questions or issues, visit:
|
|
87
|
+
- GitHub: https://github.com/Tasty-Maker-Studio/Discourser-Design-System
|
|
88
|
+
- Documentation: Read the overview and component-specific guidelines in this folder
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
# Button
|
|
2
|
+
|
|
3
|
+
**Purpose:** Primary interactive element for user actions following Material Design 3 patterns.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { Button } from '@discourser/design-system';
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Variants
|
|
12
|
+
|
|
13
|
+
The Button component supports 5 Material Design 3 variants, each with specific use cases:
|
|
14
|
+
|
|
15
|
+
| Variant | Visual Style | Usage | When to Use |
|
|
16
|
+
|---------|-------------|-------|-------------|
|
|
17
|
+
| `filled` | Solid background with primary color | Primary actions | Submit forms, confirm dialogs, main CTAs |
|
|
18
|
+
| `outlined` | Transparent background with border | Secondary actions | Cancel buttons, back navigation, alternative options |
|
|
19
|
+
| `text` | Transparent background, no border | Tertiary actions | Links, less prominent actions, dialog actions |
|
|
20
|
+
| `elevated` | Elevated surface with subtle shadow | Floating actions | FAB-like buttons, actions that need emphasis but not primary color |
|
|
21
|
+
| `tonal` | Filled with secondary container color | Medium emphasis | Secondary CTAs, soft highlights, supportive actions |
|
|
22
|
+
|
|
23
|
+
### Visual Characteristics
|
|
24
|
+
|
|
25
|
+
- **filled**: Primary color background, white text, slight shadow on hover
|
|
26
|
+
- **outlined**: Transparent background, primary color text, 1px outline border
|
|
27
|
+
- **text**: Transparent background, primary color text, no border
|
|
28
|
+
- **elevated**: Surface container background, primary color text, level 1 shadow
|
|
29
|
+
- **tonal**: Secondary container background, on-secondary-container text
|
|
30
|
+
|
|
31
|
+
## Sizes
|
|
32
|
+
|
|
33
|
+
| Size | Height | Padding (Horizontal) | Font Size | Usage |
|
|
34
|
+
|------|--------|---------------------|-----------|-------|
|
|
35
|
+
| `sm` | 32px | 16px (md) | labelMedium | Compact UI, dense layouts, small dialogs |
|
|
36
|
+
| `md` | 40px | 24px (lg) | labelLarge | Default, most use cases |
|
|
37
|
+
| `lg` | 48px | 32px (xl) | labelLarge | Touch targets, mobile emphasis, hero sections |
|
|
38
|
+
|
|
39
|
+
**Recommendation:** Use `md` for most cases. Use `lg` for mobile-first designs or prominent CTAs.
|
|
40
|
+
|
|
41
|
+
## Props
|
|
42
|
+
|
|
43
|
+
| Prop | Type | Default | Description |
|
|
44
|
+
|------|------|---------|-------------|
|
|
45
|
+
| `variant` | `'filled' \| 'outlined' \| 'text' \| 'elevated' \| 'tonal'` | `'filled'` | Visual style variant |
|
|
46
|
+
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Button size |
|
|
47
|
+
| `leftIcon` | `ReactNode` | - | Icon or element before button text |
|
|
48
|
+
| `rightIcon` | `ReactNode` | - | Icon or element after button text |
|
|
49
|
+
| `disabled` | `boolean` | `false` | Disable button interaction |
|
|
50
|
+
| `onClick` | `(event: MouseEvent) => void` | - | Click handler |
|
|
51
|
+
| `type` | `'button' \| 'submit' \| 'reset'` | `'button'` | HTML button type |
|
|
52
|
+
| `className` | `string` | - | Additional CSS classes (use sparingly) |
|
|
53
|
+
| `children` | `ReactNode` | Required | Button text content |
|
|
54
|
+
|
|
55
|
+
**Note:** Button extends `ButtonHTMLAttributes<HTMLButtonElement>`, so all standard HTML button attributes are supported.
|
|
56
|
+
|
|
57
|
+
## Examples
|
|
58
|
+
|
|
59
|
+
### Basic Usage
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
// Primary action (default)
|
|
63
|
+
<Button>Submit</Button>
|
|
64
|
+
|
|
65
|
+
// Secondary action
|
|
66
|
+
<Button variant="outlined">Cancel</Button>
|
|
67
|
+
|
|
68
|
+
// Tertiary action
|
|
69
|
+
<Button variant="text">Learn More</Button>
|
|
70
|
+
|
|
71
|
+
// Medium emphasis
|
|
72
|
+
<Button variant="tonal">Save Draft</Button>
|
|
73
|
+
|
|
74
|
+
// Floating action
|
|
75
|
+
<Button variant="elevated">Create</Button>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### With Icons
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import { PlusIcon, ArrowRightIcon } from 'your-icon-library';
|
|
82
|
+
|
|
83
|
+
// Icon on left
|
|
84
|
+
<Button leftIcon={<PlusIcon />}>
|
|
85
|
+
Add Item
|
|
86
|
+
</Button>
|
|
87
|
+
|
|
88
|
+
// Icon on right
|
|
89
|
+
<Button rightIcon={<ArrowRightIcon />}>
|
|
90
|
+
Continue
|
|
91
|
+
</Button>
|
|
92
|
+
|
|
93
|
+
// Both icons (rare, but supported)
|
|
94
|
+
<Button leftIcon={<PlusIcon />} rightIcon={<ArrowRightIcon />}>
|
|
95
|
+
Add and Continue
|
|
96
|
+
</Button>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Sizes
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
// Small button (compact UI)
|
|
103
|
+
<Button size="sm">Save</Button>
|
|
104
|
+
|
|
105
|
+
// Default size
|
|
106
|
+
<Button size="md">Submit</Button>
|
|
107
|
+
|
|
108
|
+
// Large button (mobile-friendly)
|
|
109
|
+
<Button size="lg">Get Started</Button>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Form Integration
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
// Submit button
|
|
116
|
+
<form onSubmit={handleSubmit}>
|
|
117
|
+
<Input label="Email" />
|
|
118
|
+
<Button type="submit">Sign Up</Button>
|
|
119
|
+
</form>
|
|
120
|
+
|
|
121
|
+
// Reset button
|
|
122
|
+
<Button type="reset" variant="text">Reset Form</Button>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Disabled State
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
129
|
+
|
|
130
|
+
<Button disabled={isSubmitting}>
|
|
131
|
+
{isSubmitting ? 'Submitting...' : 'Submit'}
|
|
132
|
+
</Button>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Event Handling
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
const handleClick = () => {
|
|
139
|
+
console.log('Button clicked!');
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
<Button onClick={handleClick}>Click Me</Button>
|
|
143
|
+
|
|
144
|
+
// With event parameter
|
|
145
|
+
<Button onClick={(e) => {
|
|
146
|
+
e.preventDefault();
|
|
147
|
+
handleSubmit();
|
|
148
|
+
}}>
|
|
149
|
+
Submit
|
|
150
|
+
</Button>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Common Patterns
|
|
154
|
+
|
|
155
|
+
### Primary/Secondary Button Group
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
<div className={css({ display: 'flex', gap: 'sm' })}>
|
|
159
|
+
<Button variant="outlined">Cancel</Button>
|
|
160
|
+
<Button variant="filled">Confirm</Button>
|
|
161
|
+
</div>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Dialog Actions
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
<Dialog.Content>
|
|
168
|
+
<Dialog.Title>Confirm Action</Dialog.Title>
|
|
169
|
+
<Dialog.Description>Are you sure you want to proceed?</Dialog.Description>
|
|
170
|
+
|
|
171
|
+
<div className={css({ display: 'flex', gap: 'sm', justifyContent: 'flex-end' })}>
|
|
172
|
+
<Button variant="text">Cancel</Button>
|
|
173
|
+
<Button variant="filled">Confirm</Button>
|
|
174
|
+
</div>
|
|
175
|
+
</Dialog.Content>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Loading State
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
const [loading, setLoading] = useState(false);
|
|
182
|
+
|
|
183
|
+
<Button disabled={loading}>
|
|
184
|
+
{loading && <Spinner />}
|
|
185
|
+
{loading ? 'Loading...' : 'Submit'}
|
|
186
|
+
</Button>
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## DO NOT
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
// ❌ Don't use native button element
|
|
193
|
+
<button className="...">Submit</button> // Use <Button> instead
|
|
194
|
+
|
|
195
|
+
// ❌ Don't override button styles with inline styles
|
|
196
|
+
<Button style={{ backgroundColor: 'red' }}>Delete</Button>
|
|
197
|
+
|
|
198
|
+
// ❌ Don't use multiple filled buttons next to each other (unclear hierarchy)
|
|
199
|
+
<div>
|
|
200
|
+
<Button variant="filled">Save</Button>
|
|
201
|
+
<Button variant="filled">Delete</Button> // Use outlined or text instead
|
|
202
|
+
</div>
|
|
203
|
+
|
|
204
|
+
// ❌ Don't use button for navigation (use <a> tag or Next.js Link)
|
|
205
|
+
<Button onClick={() => router.push('/page')}>Go to Page</Button> // Bad
|
|
206
|
+
|
|
207
|
+
// ❌ Don't omit text for accessibility (use IconButton instead)
|
|
208
|
+
<Button><TrashIcon /></Button> // Use <IconButton> for icon-only
|
|
209
|
+
|
|
210
|
+
// ✅ Use IconButton for icon-only actions
|
|
211
|
+
<IconButton aria-label="Delete"><TrashIcon /></IconButton>
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Accessibility
|
|
215
|
+
|
|
216
|
+
The Button component follows WCAG 2.1 Level AA standards:
|
|
217
|
+
|
|
218
|
+
- **Keyboard Navigation**: Focusable via Tab key, activates with Enter/Space
|
|
219
|
+
- **Focus Indicator**: 2px outline on focus-visible
|
|
220
|
+
- **Disabled State**: Uses `disabled` attribute, opacity 0.38, pointer-events: none
|
|
221
|
+
- **Touch Target**: Minimum 44x44px (use `md` or `lg` size)
|
|
222
|
+
- **Color Contrast**: All variants meet 4.5:1 contrast ratio
|
|
223
|
+
|
|
224
|
+
### Accessibility Best Practices
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
// ✅ Always provide meaningful text
|
|
228
|
+
<Button>Submit Form</Button>
|
|
229
|
+
|
|
230
|
+
// ✅ Use aria-label for dynamic content
|
|
231
|
+
<Button aria-label={`Delete ${itemName}`}>Delete</Button>
|
|
232
|
+
|
|
233
|
+
// ✅ Indicate loading state
|
|
234
|
+
<Button aria-busy={loading} disabled={loading}>
|
|
235
|
+
{loading ? 'Loading...' : 'Submit'}
|
|
236
|
+
</Button>
|
|
237
|
+
|
|
238
|
+
// ✅ Use proper button types
|
|
239
|
+
<Button type="submit">Submit</Button>
|
|
240
|
+
<Button type="reset">Reset</Button>
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Variant Selection Guide
|
|
244
|
+
|
|
245
|
+
| Scenario | Recommended Variant | Reasoning |
|
|
246
|
+
|----------|-------------------|-----------|
|
|
247
|
+
| Form submission | `filled` | Primary action, needs highest emphasis |
|
|
248
|
+
| Cancel/Back | `outlined` or `text` | Secondary action, lower emphasis |
|
|
249
|
+
| Dialog confirmation | `filled` | Primary action in dialog |
|
|
250
|
+
| Dialog dismiss | `text` | Tertiary action, minimal emphasis |
|
|
251
|
+
| Save draft | `tonal` | Medium emphasis, not primary action |
|
|
252
|
+
| Delete/Destructive | `filled` or `tonal` | High attention, but consider error colors |
|
|
253
|
+
| Filter/Sort | `text` or `outlined` | Lower emphasis, frequent use |
|
|
254
|
+
| Floating action button | `elevated` | Needs to float above content |
|
|
255
|
+
| Link-like actions | `text` | Minimal emphasis, inline with text |
|
|
256
|
+
|
|
257
|
+
## State Behaviors
|
|
258
|
+
|
|
259
|
+
| State | Visual Change | Behavior |
|
|
260
|
+
|-------|---------------|----------|
|
|
261
|
+
| **Hover** | Opacity change or shadow | `filled`: 92% opacity + level1 shadow<br />`outlined`: 8% primary background<br />`elevated`: Increase shadow to level2 |
|
|
262
|
+
| **Active** | Further opacity/shadow change | `filled`: 88% opacity<br />`outlined`: 12% primary background |
|
|
263
|
+
| **Focus** | 2px outline | Primary color outline, 2px offset |
|
|
264
|
+
| **Disabled** | 38% opacity, no interaction | Cannot be clicked, greyed out appearance |
|
|
265
|
+
|
|
266
|
+
## Responsive Considerations
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
// Mobile-first: Use larger buttons for touch
|
|
270
|
+
<Button size="lg">Submit</Button>
|
|
271
|
+
|
|
272
|
+
// Desktop: Can use smaller sizes
|
|
273
|
+
<Button size={{ base: 'lg', md: 'md' }}>Submit</Button>
|
|
274
|
+
|
|
275
|
+
// Responsive button group
|
|
276
|
+
<div className={css({
|
|
277
|
+
display: 'flex',
|
|
278
|
+
flexDirection: { base: 'column', md: 'row' },
|
|
279
|
+
gap: 'sm'
|
|
280
|
+
})}>
|
|
281
|
+
<Button variant="outlined">Cancel</Button>
|
|
282
|
+
<Button variant="filled">Confirm</Button>
|
|
283
|
+
</div>
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
## Testing
|
|
287
|
+
|
|
288
|
+
When testing Button components:
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
import { render, screen } from '@testing-library/react';
|
|
292
|
+
import userEvent from '@testing-library/user-event';
|
|
293
|
+
|
|
294
|
+
test('button handles click events', async () => {
|
|
295
|
+
const handleClick = vi.fn();
|
|
296
|
+
render(<Button onClick={handleClick}>Click Me</Button>);
|
|
297
|
+
|
|
298
|
+
const button = screen.getByRole('button', { name: 'Click Me' });
|
|
299
|
+
await userEvent.click(button);
|
|
300
|
+
|
|
301
|
+
expect(handleClick).toHaveBeenCalledOnce();
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test('disabled button cannot be clicked', async () => {
|
|
305
|
+
const handleClick = vi.fn();
|
|
306
|
+
render(<Button disabled onClick={handleClick}>Click Me</Button>);
|
|
307
|
+
|
|
308
|
+
const button = screen.getByRole('button', { name: 'Click Me' });
|
|
309
|
+
await userEvent.click(button);
|
|
310
|
+
|
|
311
|
+
expect(handleClick).not.toHaveBeenCalled();
|
|
312
|
+
expect(button).toBeDisabled();
|
|
313
|
+
});
|
|
314
|
+
```
|