@turk.net/mui 3.0.7 → 3.0.8
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/.opencode/rules/design-compliance.md +119 -0
- package/.opencode/skills/onehub-design-verification/SKILL.md +315 -0
- package/.opencode/skills/turknet-onehub-component-usage/SKILL.md +16 -16
- package/COMPONENT_GLOSSARY.md +500 -0
- package/COMPONENT_MAP.md +567 -0
- package/DESIGN_RULES.md +399 -0
- package/DESIGN_SOURCES.md +198 -0
- package/DESIGN_TOKENS_MAP.md +271 -0
- package/ESLINT_DESIGN_RULES.md +392 -0
- package/LLM_EXAMPLES.md +183 -169
- package/LLM_GUIDELINES.md +192 -192
- package/PAGE_PATTERNS.md +509 -0
- package/SKILL_SETUP.md +237 -200
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -0
- package/dist/index.mjs.map +1 -1
- package/dist/theme/index.js +1 -0
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +1 -0
- package/dist/theme/index.mjs.map +1 -1
- package/package.json +20 -3
- package/scripts/verify-design.js +351 -0
package/PAGE_PATTERNS.md
ADDED
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
# OneHub Page Patterns
|
|
2
|
+
|
|
3
|
+
> **Purpose:** Standardized page patterns for OneHub modules. When creating a new page, match it to the closest pattern below and follow the exact structure, layout, component set, and spacing grid.
|
|
4
|
+
>
|
|
5
|
+
> **All patterns MUST use OneHub theme rules:** typography variants, spacing via `theme.spacing()`, colors via CSS custom properties.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Pattern 1: List Page (Table + Filters)
|
|
10
|
+
|
|
11
|
+
**Use case:** Task lists, customer lists, ticket lists, lead lists, any tabular data with search/filter.
|
|
12
|
+
|
|
13
|
+
### Layout
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
TabBasedLayout (app level)
|
|
17
|
+
└── DashboardLayout / WithSidebarLayout (page level)
|
|
18
|
+
└── PageContentContainer
|
|
19
|
+
├── PageContentHeader (title + actions)
|
|
20
|
+
└── Content Area
|
|
21
|
+
├── Search & Filter Bar
|
|
22
|
+
├── Active Filter Tags (Chips)
|
|
23
|
+
├── Table Header Row
|
|
24
|
+
├── Table Data Rows
|
|
25
|
+
└── Pagination
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Component Breakdown
|
|
29
|
+
|
|
30
|
+
| Element | Component | Props |
|
|
31
|
+
|---------|-----------|-------|
|
|
32
|
+
| Page title | `Typography` | `variant="display.xs.bold"` |
|
|
33
|
+
| Search input | `TextField` | `color="neutral" size="medium" placeholder="Ara"` |
|
|
34
|
+
| Search icon | `SearchIcon` from icons | `size: 20px` |
|
|
35
|
+
| Filter button | `Button` | `variant="text" size="medium" color="secondary"` |
|
|
36
|
+
| Active filter tags | `Chip` | `variant="tint" color="primary" size="small" onDelete={...}` |
|
|
37
|
+
| Clear filters button | `Button` | `variant="text" size="medium" color="primary"` |
|
|
38
|
+
| Table header | Custom `Box` | `bg: neutral.background[2].rest`, `height: 40px` |
|
|
39
|
+
| Table header text | `Typography` | `variant="text.xs.medium"` |
|
|
40
|
+
| Table row | Custom `Box` | `bg: neutral.background[1].rest`, `height: 56px` |
|
|
41
|
+
| Table primary text | `Typography` | `variant="text.sm.medium" color="neutral.foreground[1].rest"` |
|
|
42
|
+
| Table secondary text | `Typography` | `variant="text.sm.regular" color="neutral.foreground[4].rest"` |
|
|
43
|
+
| Status indicator | `Badge` or `Chip` | Depends on status type |
|
|
44
|
+
| Pagination | `Pagination` | `size="medium" color="primary"` |
|
|
45
|
+
|
|
46
|
+
### Spacing Grid
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
Table cell: paddingLeft/Right: theme.spacing(8) (32px)
|
|
50
|
+
Table header: height: theme.spacing(10) (40px)
|
|
51
|
+
Table row: height: theme.spacing(14) (56px)
|
|
52
|
+
Row gap: gap: theme.spacing(2) (8px)
|
|
53
|
+
Section gap: marginBottom: theme.spacing(4) (16px)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Figma Reference
|
|
57
|
+
|
|
58
|
+
- `My task table` (`364:28717`)
|
|
59
|
+
- `Team task table` (`694:41994`)
|
|
60
|
+
- `Customer table` (`2291:40548`)
|
|
61
|
+
- `Ticket List table` (`9148:52401`)
|
|
62
|
+
|
|
63
|
+
### Example Structure
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
function TaskListPage() {
|
|
67
|
+
return (
|
|
68
|
+
<PageContentContainer>
|
|
69
|
+
<PageContentHeader>
|
|
70
|
+
<Typography variant="display.xs.bold">My Tasks</Typography>
|
|
71
|
+
<Button variant="contained" color="primary" size="medium">
|
|
72
|
+
New Task
|
|
73
|
+
</Button>
|
|
74
|
+
</PageContentHeader>
|
|
75
|
+
|
|
76
|
+
{/* Search & Filter Bar */}
|
|
77
|
+
<Box sx={{ display: 'flex', gap: 2, mb: 2 }}>
|
|
78
|
+
<TextField
|
|
79
|
+
color="neutral"
|
|
80
|
+
size="medium"
|
|
81
|
+
placeholder="Ara"
|
|
82
|
+
InputProps={{ startAdornment: <SearchIcon /> }}
|
|
83
|
+
sx={{ flex: 1 }}
|
|
84
|
+
/>
|
|
85
|
+
<Button variant="text" size="medium" color="secondary" startIcon={<FilterIcon />}>
|
|
86
|
+
Filters
|
|
87
|
+
</Button>
|
|
88
|
+
</Box>
|
|
89
|
+
|
|
90
|
+
{/* Active Filter Tags */}
|
|
91
|
+
<Box sx={{ display: 'flex', gap: 1, mb: 2 }}>
|
|
92
|
+
<Chip variant="tint" color="primary" size="small" label="Today" onDelete={handleRemove} />
|
|
93
|
+
</Box>
|
|
94
|
+
|
|
95
|
+
{/* Table */}
|
|
96
|
+
<Box sx={{ border: 1, borderColor: 'var(--neutral-stroke-3-rest)', borderRadius: 1 }}>
|
|
97
|
+
{/* Header */}
|
|
98
|
+
<Box sx={{
|
|
99
|
+
display: 'flex',
|
|
100
|
+
px: 8,
|
|
101
|
+
height: 40,
|
|
102
|
+
alignItems: 'center',
|
|
103
|
+
bgcolor: 'var(--neutral-background-2-rest)',
|
|
104
|
+
borderBottom: 1,
|
|
105
|
+
borderColor: 'var(--neutral-stroke-3-rest)'
|
|
106
|
+
}}>
|
|
107
|
+
<Typography variant="text.xs.medium" sx={{ minWidth: 140 }}>Date</Typography>
|
|
108
|
+
<Typography variant="text.xs.medium" sx={{ minWidth: 90 }}>Customer</Typography>
|
|
109
|
+
<Typography variant="text.xs.medium" sx={{ flex: 1 }}>Description</Typography>
|
|
110
|
+
<Typography variant="text.xs.medium">Rating</Typography>
|
|
111
|
+
</Box>
|
|
112
|
+
|
|
113
|
+
{/* Rows */}
|
|
114
|
+
{tasks.map(task => (
|
|
115
|
+
<Box key={task.id} sx={{
|
|
116
|
+
display: 'flex',
|
|
117
|
+
px: 8,
|
|
118
|
+
height: 56,
|
|
119
|
+
alignItems: 'center',
|
|
120
|
+
borderBottom: 1,
|
|
121
|
+
borderColor: 'var(--neutral-stroke-3-rest)',
|
|
122
|
+
bgcolor: 'var(--neutral-background-1-rest)'
|
|
123
|
+
}}>
|
|
124
|
+
<Box sx={{ minWidth: 140 }}>
|
|
125
|
+
<Typography variant="text.lg.medium" color="var(--neutral-foreground-2-rest)">06</Typography>
|
|
126
|
+
<Typography variant="text.sm.regular" color="var(--neutral-foreground-4-rest)">WED, THU</Typography>
|
|
127
|
+
</Box>
|
|
128
|
+
<Typography variant="text.sm.regular" color="var(--neutral-foreground-4-rest)" sx={{ minWidth: 90 }}>
|
|
129
|
+
Gizem K.
|
|
130
|
+
</Typography>
|
|
131
|
+
<Typography variant="text.sm.medium" sx={{ flex: 1 }}>{task.description}</Typography>
|
|
132
|
+
<Box sx={{ display: 'flex', gap: 1 }}>
|
|
133
|
+
<FaceHappyIcon />
|
|
134
|
+
<MessageIcon />
|
|
135
|
+
</Box>
|
|
136
|
+
</Box>
|
|
137
|
+
))}
|
|
138
|
+
</Box>
|
|
139
|
+
|
|
140
|
+
{/* Pagination */}
|
|
141
|
+
<Box sx={{ display: 'flex', justifyContent: 'center', mt: 4 }}>
|
|
142
|
+
<Pagination count={totalPages} size="medium" color="primary" />
|
|
143
|
+
</Box>
|
|
144
|
+
</PageContentContainer>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Pattern 2: Detail Page
|
|
152
|
+
|
|
153
|
+
**Use case:** Task detail, customer detail, ticket detail — viewing a single record with multiple sections.
|
|
154
|
+
|
|
155
|
+
### Layout
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
TabBasedLayout
|
|
159
|
+
└── WithSidebarLayout
|
|
160
|
+
└── PageContentContainer
|
|
161
|
+
├── PageContentHeader (back button + title)
|
|
162
|
+
├── Tab Navigation (optional)
|
|
163
|
+
└── Content Sections
|
|
164
|
+
├── Section Card 1
|
|
165
|
+
├── Section Card 2
|
|
166
|
+
└── ...
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Component Breakdown
|
|
170
|
+
|
|
171
|
+
| Element | Component | Props |
|
|
172
|
+
|---------|-----------|-------|
|
|
173
|
+
| Back button | `Button` | `variant="text" size="medium" color="primary"` |
|
|
174
|
+
| Page title | `Typography` | `variant="display.xs.bold"` |
|
|
175
|
+
| Section tabs | `Tabs` + `Tab` | Active: `text.sm.semibold`, Inactive: `text.sm.regular` |
|
|
176
|
+
| Section card | `Paper` | `elevation={0}` |
|
|
177
|
+
| Section title | `Typography` | `variant="text.lg.medium"` |
|
|
178
|
+
| Field label | `Typography` | `variant="text.xs.regular" color="neutral.foreground[2].rest"` |
|
|
179
|
+
| Field value | `Typography` | `variant="text.sm.regular" color="neutral.foreground[1].rest"` |
|
|
180
|
+
| Status badge | `Chip` | `variant="tint" size="small"` |
|
|
181
|
+
| Action buttons | `Button` | `variant="contained" color="primary"` |
|
|
182
|
+
|
|
183
|
+
### Spacing Grid
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
Section gap: marginBottom: theme.spacing(4) (16px)
|
|
187
|
+
Card padding: padding: theme.spacing(4) (16px)
|
|
188
|
+
Field row gap: gap: theme.spacing(2) (8px)
|
|
189
|
+
Field label-value: marginBottom: theme.spacing(1) (4px)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Example Structure
|
|
193
|
+
|
|
194
|
+
```tsx
|
|
195
|
+
function DetailPage() {
|
|
196
|
+
return (
|
|
197
|
+
<PageContentContainer>
|
|
198
|
+
<PageContentHeader>
|
|
199
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
|
200
|
+
<Button variant="text" size="medium" color="primary" startIcon={<ArrowLeftIcon />}>
|
|
201
|
+
Back
|
|
202
|
+
</Button>
|
|
203
|
+
<Typography variant="display.xs.bold">Task Detail</Typography>
|
|
204
|
+
</Box>
|
|
205
|
+
<Box sx={{ display: 'flex', gap: 2 }}>
|
|
206
|
+
<Button variant="outlined" size="medium" color="secondary">Transfer</Button>
|
|
207
|
+
<Button variant="contained" size="medium" color="primary">Complete</Button>
|
|
208
|
+
</Box>
|
|
209
|
+
</PageContentHeader>
|
|
210
|
+
|
|
211
|
+
{/* Tabs */}
|
|
212
|
+
<Tabs value={tab} onChange={handleTabChange} sx={{ mb: 4 }}>
|
|
213
|
+
<Tab label="Overview" />
|
|
214
|
+
<Tab label="History" />
|
|
215
|
+
</Tabs>
|
|
216
|
+
|
|
217
|
+
{/* Content */}
|
|
218
|
+
<Paper elevation={0} sx={{ p: 4, mb: 4 }}>
|
|
219
|
+
<Typography variant="text.lg.medium" sx={{ mb: 4 }}>Customer Info</Typography>
|
|
220
|
+
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 4 }}>
|
|
221
|
+
<Box>
|
|
222
|
+
<Typography variant="text.xs.regular" color="var(--neutral-foreground-2-rest)">Name</Typography>
|
|
223
|
+
<Typography variant="text.sm.regular">Gizem K.</Typography>
|
|
224
|
+
</Box>
|
|
225
|
+
<Box>
|
|
226
|
+
<Typography variant="text.xs.regular" color="var(--neutral-foreground-2-rest)">Status</Typography>
|
|
227
|
+
<Chip variant="tint" color="success" size="small" label="Active" />
|
|
228
|
+
</Box>
|
|
229
|
+
</Box>
|
|
230
|
+
</Paper>
|
|
231
|
+
</PageContentContainer>
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## Pattern 3: Form Page
|
|
239
|
+
|
|
240
|
+
**Use case:** New task, edit task, settings forms, any data entry page.
|
|
241
|
+
|
|
242
|
+
### Layout
|
|
243
|
+
|
|
244
|
+
```
|
|
245
|
+
TabBasedLayout
|
|
246
|
+
└── WithSidebarLayout
|
|
247
|
+
└── PageContentContainer
|
|
248
|
+
├── PageContentHeader (title)
|
|
249
|
+
├── Form (Formik)
|
|
250
|
+
│ ├── Form Section 1
|
|
251
|
+
│ │ ├── Field Row
|
|
252
|
+
│ │ └── ...
|
|
253
|
+
│ └── Form Section 2
|
|
254
|
+
└── Form Actions (Submit + Cancel)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Component Breakdown
|
|
258
|
+
|
|
259
|
+
| Element | Component | Props |
|
|
260
|
+
|---------|-----------|-------|
|
|
261
|
+
| Page title | `Typography` | `variant="display.xs.bold"` |
|
|
262
|
+
| Form wrapper | `form` + `Formik` | Formik `onSubmit` handler |
|
|
263
|
+
| Section title | `Typography` | `variant="text.lg.medium"` |
|
|
264
|
+
| Text input | `TextField` | `color="neutral" size="medium" fullWidth` |
|
|
265
|
+
| Dropdown | `Select` | `color="neutral" size="medium"` |
|
|
266
|
+
| Autocomplete | `Autocomplete` | `size="medium" color="neutral"` |
|
|
267
|
+
| Radio group | `RadioGroup` + `Radio` | `color="primary"` |
|
|
268
|
+
| Checkbox | `Checkbox` + `FormControlLabel` | `color="primary"` |
|
|
269
|
+
| Switch toggle | `Switch` + `FormControlLabel` | `color="primary"` |
|
|
270
|
+
| Date picker | `DatePicker` | `dayjs` adapter |
|
|
271
|
+
| Error message | `FormHelperText` | `error` |
|
|
272
|
+
| Submit button | `Button` | `variant="contained" color="primary" size="medium"` |
|
|
273
|
+
| Cancel button | `Button` | `variant="outlined" color="secondary" size="medium"` |
|
|
274
|
+
|
|
275
|
+
### Spacing Grid
|
|
276
|
+
|
|
277
|
+
```
|
|
278
|
+
Section gap: marginBottom: theme.spacing(4) (16px)
|
|
279
|
+
Field row gap: gap: theme.spacing(2) (8px)
|
|
280
|
+
Label margin: marginBottom: theme.spacing(1) (4px)
|
|
281
|
+
Field fullWidth: fullWidth
|
|
282
|
+
Form actions: gap: theme.spacing(2), justifyContent: flex-end
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Example Structure
|
|
286
|
+
|
|
287
|
+
```tsx
|
|
288
|
+
function CreateTaskForm() {
|
|
289
|
+
const formik = useFormik({
|
|
290
|
+
initialValues: { title: '', customer: null, priority: 'medium', notify: false },
|
|
291
|
+
validationSchema: yup.object({
|
|
292
|
+
title: yup.string().required('Title is required'),
|
|
293
|
+
customer: yup.object().required('Customer is required'),
|
|
294
|
+
}),
|
|
295
|
+
onSubmit: (values) => createTask(values),
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
return (
|
|
299
|
+
<PageContentContainer>
|
|
300
|
+
<PageContentHeader>
|
|
301
|
+
<Typography variant="display.xs.bold">New Task</Typography>
|
|
302
|
+
</PageContentHeader>
|
|
303
|
+
|
|
304
|
+
<form onSubmit={formik.handleSubmit}>
|
|
305
|
+
<Paper elevation={0} sx={{ p: 4, mb: 4 }}>
|
|
306
|
+
<Typography variant="text.lg.medium" sx={{ mb: 4 }}>Task Details</Typography>
|
|
307
|
+
|
|
308
|
+
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
|
309
|
+
<TextField
|
|
310
|
+
name="title"
|
|
311
|
+
label="Title"
|
|
312
|
+
color="neutral"
|
|
313
|
+
size="medium"
|
|
314
|
+
fullWidth
|
|
315
|
+
value={formik.values.title}
|
|
316
|
+
onChange={formik.handleChange}
|
|
317
|
+
error={formik.touched.title && Boolean(formik.errors.title)}
|
|
318
|
+
helperText={formik.touched.title && formik.errors.title}
|
|
319
|
+
/>
|
|
320
|
+
|
|
321
|
+
<Autocomplete
|
|
322
|
+
options={customers}
|
|
323
|
+
size="medium"
|
|
324
|
+
renderInput={(params) => (
|
|
325
|
+
<TextField {...params} label="Customer" color="neutral" />
|
|
326
|
+
)}
|
|
327
|
+
value={formik.values.customer}
|
|
328
|
+
onChange={(_, value) => formik.setFieldValue('customer', value)}
|
|
329
|
+
/>
|
|
330
|
+
|
|
331
|
+
<FormControlLabel
|
|
332
|
+
control={<Switch color="primary" checked={formik.values.notify} />}
|
|
333
|
+
label="Notify team"
|
|
334
|
+
/>
|
|
335
|
+
</Box>
|
|
336
|
+
</Paper>
|
|
337
|
+
|
|
338
|
+
<Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: 2 }}>
|
|
339
|
+
<Button variant="outlined" size="medium" color="secondary">Cancel</Button>
|
|
340
|
+
<Button variant="contained" size="medium" color="primary" type="submit">
|
|
341
|
+
Create
|
|
342
|
+
</Button>
|
|
343
|
+
</Box>
|
|
344
|
+
</form>
|
|
345
|
+
</PageContentContainer>
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## Pattern 4: Dashboard
|
|
353
|
+
|
|
354
|
+
**Use case:** Overview screens with metrics, charts, quick actions.
|
|
355
|
+
|
|
356
|
+
### Layout
|
|
357
|
+
|
|
358
|
+
```
|
|
359
|
+
TabBasedLayout
|
|
360
|
+
└── WithSidebarLayout
|
|
361
|
+
└── PageContentContainer
|
|
362
|
+
├── PageContentHeader (title + date filter)
|
|
363
|
+
├── Metrics Row (Cards)
|
|
364
|
+
├── Charts/Graphs Section
|
|
365
|
+
└── Quick Actions
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Component Breakdown
|
|
369
|
+
|
|
370
|
+
| Element | Component | Props |
|
|
371
|
+
|---------|-----------|-------|
|
|
372
|
+
| Page title | `Typography` | `variant="display.xs.bold"` |
|
|
373
|
+
| Date filter | `Chip` or `Select` | Filter chips shown in Figma |
|
|
374
|
+
| Metric card | `Paper` | `elevation={0}` |
|
|
375
|
+
| Metric value | `Typography` | `variant="display.md.semibold"` |
|
|
376
|
+
| Metric label | `Typography` | `variant="text.xs.regular" color="neutral.foreground[2].rest"` |
|
|
377
|
+
| Chart area | Custom component | Uses ApexCharts or similar |
|
|
378
|
+
|
|
379
|
+
### Spacing Grid
|
|
380
|
+
|
|
381
|
+
```
|
|
382
|
+
Card gap: gap: theme.spacing(2) (8px)
|
|
383
|
+
Card padding: padding: theme.spacing(4) (16px)
|
|
384
|
+
Section gap: marginBottom: theme.spacing(4) (16px)
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
## Pattern 5: Dialog/Modal
|
|
390
|
+
|
|
391
|
+
**Use case:** Confirmation dialogs, quick edit forms, transfer/ticket close.
|
|
392
|
+
|
|
393
|
+
### Component Breakdown
|
|
394
|
+
|
|
395
|
+
| Element | Component | Props |
|
|
396
|
+
|---------|-----------|-------|
|
|
397
|
+
| Dialog | `Dialog` | `maxWidth`, `fullWidth` options |
|
|
398
|
+
| Title | `DialogTitle` | Typography: `text.lg.medium` |
|
|
399
|
+
| Content | `DialogContent` | Typography: `text.md.regular` |
|
|
400
|
+
| Content text | `DialogContentText` | — |
|
|
401
|
+
| Primary action | `Button` | `variant="contained" color="primary"` |
|
|
402
|
+
| Secondary action | `Button` | `variant="outlined" color="secondary"` |
|
|
403
|
+
| Destructive action | `Button` | `variant="contained" color="error"` |
|
|
404
|
+
|
|
405
|
+
### Example Structure
|
|
406
|
+
|
|
407
|
+
```tsx
|
|
408
|
+
<Dialog open={open} onClose={handleClose} maxWidth="sm" fullWidth>
|
|
409
|
+
<DialogTitle>Confirm Action</DialogTitle>
|
|
410
|
+
<DialogContent>
|
|
411
|
+
<DialogContentText>
|
|
412
|
+
Are you sure you want to close this ticket?
|
|
413
|
+
</DialogContentText>
|
|
414
|
+
</DialogContent>
|
|
415
|
+
<DialogActions>
|
|
416
|
+
<Button variant="outlined" size="medium" color="secondary" onClick={handleClose}>
|
|
417
|
+
Cancel
|
|
418
|
+
</Button>
|
|
419
|
+
<Button variant="contained" size="medium" color="error" onClick={handleConfirm}>
|
|
420
|
+
Close Ticket
|
|
421
|
+
</Button>
|
|
422
|
+
</DialogActions>
|
|
423
|
+
</Dialog>
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
## Pattern 6: Notifications
|
|
429
|
+
|
|
430
|
+
**Use case:** Success/error/warning feedback to users.
|
|
431
|
+
|
|
432
|
+
### Component
|
|
433
|
+
|
|
434
|
+
```typescript
|
|
435
|
+
// Use notistack's useSnackbar hook (preferred)
|
|
436
|
+
const { enqueueSnackbar } = useSnackbar();
|
|
437
|
+
|
|
438
|
+
enqueueSnackbar('Operation successful', { variant: 'success' });
|
|
439
|
+
enqueueSnackbar('Something went wrong', { variant: 'error' });
|
|
440
|
+
|
|
441
|
+
// OR: MUI Snackbar + Alert
|
|
442
|
+
<Snackbar open={open} autoHideDuration={6000} onClose={handleClose}>
|
|
443
|
+
<Alert severity="success" onClose={handleClose}>Message</Alert>
|
|
444
|
+
</Snackbar>
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
---
|
|
448
|
+
|
|
449
|
+
## Pattern 7: Empty State
|
|
450
|
+
|
|
451
|
+
**Use case:** When a list or search returns no results.
|
|
452
|
+
|
|
453
|
+
### Example
|
|
454
|
+
|
|
455
|
+
```tsx
|
|
456
|
+
<Box sx={{
|
|
457
|
+
display: 'flex',
|
|
458
|
+
flexDirection: 'column',
|
|
459
|
+
alignItems: 'center',
|
|
460
|
+
justifyContent: 'center',
|
|
461
|
+
py: 16
|
|
462
|
+
}}>
|
|
463
|
+
<EmptyIcon style={{ width: 64, height: 64 }} />
|
|
464
|
+
<Typography variant="text.lg.medium" sx={{ mt: 4 }}>
|
|
465
|
+
No results found
|
|
466
|
+
</Typography>
|
|
467
|
+
<Typography variant="text.sm.regular" color="var(--neutral-foreground-4-rest)" sx={{ mt: 1 }}>
|
|
468
|
+
Try adjusting your search or filter criteria
|
|
469
|
+
</Typography>
|
|
470
|
+
</Box>
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
---
|
|
474
|
+
|
|
475
|
+
## Pattern 8: Loading State
|
|
476
|
+
|
|
477
|
+
**Use case:** While data is being fetched.
|
|
478
|
+
|
|
479
|
+
### Example
|
|
480
|
+
|
|
481
|
+
```tsx
|
|
482
|
+
// Skeleton loader (preferred)
|
|
483
|
+
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
|
484
|
+
<Skeleton variant="rectangular" height={40} />
|
|
485
|
+
<Skeleton variant="rectangular" height={56} />
|
|
486
|
+
<Skeleton variant="rectangular" height={56} />
|
|
487
|
+
<Skeleton variant="rectangular" height={56} />
|
|
488
|
+
</Box>
|
|
489
|
+
|
|
490
|
+
// Or: CircularProgress
|
|
491
|
+
<Box sx={{ display: 'flex', justifyContent: 'center', py: 16 }}>
|
|
492
|
+
<CircularProgress />
|
|
493
|
+
</Box>
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
---
|
|
497
|
+
|
|
498
|
+
## Quick Decision Guide
|
|
499
|
+
|
|
500
|
+
| If you need to... | Use Pattern |
|
|
501
|
+
|-------------------|-------------|
|
|
502
|
+
| Show a list of records | Pattern 1: List Page |
|
|
503
|
+
| View single record details | Pattern 2: Detail Page |
|
|
504
|
+
| Collect user input | Pattern 3: Form Page |
|
|
505
|
+
| Show metrics/overview | Pattern 4: Dashboard |
|
|
506
|
+
| Confirm destructive action | Pattern 5: Dialog/Modal |
|
|
507
|
+
| Show feedback message | Pattern 6: Notifications |
|
|
508
|
+
| Handle no-data state | Pattern 7: Empty State |
|
|
509
|
+
| Handle loading state | Pattern 8: Loading State |
|