@reinvented/design 1.0.0 → 1.1.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 +1 -1
- package/SKILL.md +214 -0
- package/package.json +4 -3
- package/skills/apps/analytics.md +103 -0
- package/skills/apps/booking-scheduling.md +97 -0
- package/skills/apps/content-management.md +52 -0
- package/skills/apps/crm.md +80 -0
- package/skills/apps/e-commerce.md +109 -0
- package/skills/apps/education.md +79 -0
- package/skills/apps/finance.md +68 -0
- package/skills/apps/health-fitness.md +72 -0
- package/skills/apps/marketplace.md +99 -0
- package/skills/apps/messaging.md +84 -0
- package/skills/apps/portfolio-personal.md +90 -0
- package/skills/apps/project-management.md +95 -0
- package/skills/apps/saas-dashboard.md +104 -0
- package/skills/apps/social-platform.md +50 -0
- package/skills/blocks/auth.md +106 -0
- package/skills/blocks/communication.md +98 -0
- package/skills/blocks/content.md +107 -0
- package/skills/blocks/data-management.md +109 -0
- package/skills/blocks/data-viz.md +92 -0
- package/skills/blocks/ecommerce.md +126 -0
- package/skills/blocks/feedback.md +97 -0
- package/skills/blocks/filtering.md +127 -0
- package/skills/blocks/marketing.md +136 -0
- package/skills/blocks/media.md +102 -0
- package/skills/blocks/navigation.md +136 -0
- package/skills/blocks/onboarding.md +75 -0
- package/skills/blocks/profiles.md +131 -0
- package/skills/blocks/scheduling.md +117 -0
- package/skills/blocks/settings.md +102 -0
- package/skills/components/advanced-components.md +142 -0
- package/skills/components/avatar.md +92 -0
- package/skills/components/badge.md +105 -0
- package/skills/components/button.md +87 -0
- package/skills/components/card.md +144 -0
- package/skills/components/chart.md +88 -0
- package/skills/components/dialog.md +109 -0
- package/skills/components/dropdown-menu.md +117 -0
- package/skills/components/extended-components.md +187 -0
- package/skills/components/feedback.md +165 -0
- package/skills/components/form.md +112 -0
- package/skills/components/input.md +107 -0
- package/skills/components/map.md +53 -0
- package/skills/components/navigation.md +73 -0
- package/skills/components/overlay.md +77 -0
- package/skills/components/page-header.md +51 -0
- package/skills/components/select.md +175 -0
- package/skills/components/table.md +102 -0
- package/skills/components/tabs.md +105 -0
- package/skills/components/utilities.md +138 -0
- package/skills/devices/desktop.md +43 -0
- package/skills/devices/mobile.md +77 -0
- package/skills/foundation/design-principles.md +77 -0
- package/skills/foundation/design-tokens.md +121 -0
- package/skills/foundation/mockup-generation.md +118 -0
- package/skills/foundation/rules.md +54 -0
- package/skills/foundation/tailwind-usage.md +204 -0
- package/skills/layouts/dashboard.md +71 -0
- package/skills/layouts/full-page-form.md +75 -0
- package/skills/layouts/list-detail.md +70 -0
- package/skills/layouts/marketing.md +70 -0
- package/skills/layouts/responsive.md +67 -0
- package/skills/layouts/settings-page.md +106 -0
- package/skills/layouts/sidebar.md +73 -0
- package/skills/layouts/topbar.md +68 -0
- package/skills/patterns/auth.md +131 -0
- package/skills/patterns/content-display.md +164 -0
- package/skills/patterns/dashboards.md +104 -0
- package/skills/patterns/data-tables.md +113 -0
- package/skills/patterns/empty-states.md +71 -0
- package/skills/patterns/error-states.md +73 -0
- package/skills/patterns/forms.md +136 -0
- package/skills/patterns/loading-states.md +92 -0
- package/skills/patterns/navigation.md +113 -0
- package/skills/patterns/notifications.md +91 -0
- package/skills/patterns/onboarding.md +42 -0
- package/skills/patterns/search.md +55 -0
- package/skills/patterns/settings.md +132 -0
- package/skills/patterns/user-profiles.md +67 -0
- package/skills/personas/business-operator.md +114 -0
- package/skills/personas/consumer-casual.md +60 -0
- package/skills/personas/consumer-power-user.md +109 -0
- package/skills/personas/creative-professional.md +109 -0
- package/skills/personas/enterprise-admin.md +134 -0
- package/skills/visual/color-usage.md +62 -0
- package/skills/visual/dark-mode.md +50 -0
- package/skills/visual/polish-techniques.md +101 -0
- package/skills/visual/spacing-composition.md +69 -0
- package/skills/visual/transitions-animations.md +66 -0
- package/skills/visual/typography-hierarchy.md +66 -0
- package/DESIGN_GUIDE.md +0 -148
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Pattern: Data Tables
|
|
2
|
+
|
|
3
|
+
Complete table layouts with toolbar, search, filters, sorting indicators, pagination, and row actions.
|
|
4
|
+
|
|
5
|
+
## Standard Data Table
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
<Card>
|
|
9
|
+
<!-- Toolbar -->
|
|
10
|
+
<CardHeader className="pb-4">
|
|
11
|
+
<div className="flex items-center justify-between">
|
|
12
|
+
<CardTitle>Team Members</CardTitle>
|
|
13
|
+
<Button size="sm"><Plus className="w-4 h-4" /> Add Member</Button>
|
|
14
|
+
</div>
|
|
15
|
+
<div className="flex items-center gap-3 mt-3">
|
|
16
|
+
<div className="relative flex-1 max-w-sm">
|
|
17
|
+
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
18
|
+
<Input className="pl-9" placeholder="Search members..." defaultValue="search" />
|
|
19
|
+
</div>
|
|
20
|
+
<Select defaultValue="roleFilter">
|
|
21
|
+
<SelectTrigger className="w-36">
|
|
22
|
+
<SelectValue placeholder="All Roles" />
|
|
23
|
+
</SelectTrigger>
|
|
24
|
+
<SelectContent>
|
|
25
|
+
<SelectItem value="all">All Roles</SelectItem>
|
|
26
|
+
<SelectItem value="admin">Admin</SelectItem>
|
|
27
|
+
<SelectItem value="member">Member</SelectItem>
|
|
28
|
+
</SelectContent>
|
|
29
|
+
</Select>
|
|
30
|
+
</div>
|
|
31
|
+
</CardHeader>
|
|
32
|
+
<!-- Table -->
|
|
33
|
+
<CardContent className="p-0">
|
|
34
|
+
<Table>
|
|
35
|
+
<TableHeader>
|
|
36
|
+
<TableRow>
|
|
37
|
+
<TableHead className="pl-6">Member</TableHead>
|
|
38
|
+
<TableHead>Role</TableHead>
|
|
39
|
+
<TableHead>Status</TableHead>
|
|
40
|
+
<TableHead className="text-right">Last Active</TableHead>
|
|
41
|
+
<TableHead className="w-10 pr-6"></TableHead>
|
|
42
|
+
</TableRow>
|
|
43
|
+
</TableHeader>
|
|
44
|
+
<TableBody>
|
|
45
|
+
<TableRow v-for="member in members" :key="member.id">
|
|
46
|
+
<!-- Avatar + name + email in first column -->
|
|
47
|
+
<TableCell className="pl-6">
|
|
48
|
+
<div className="flex items-center gap-3">
|
|
49
|
+
<div className="w-8 h-8 rounded-full shrink-0 flex items-center justify-center text-xs font-medium bg-muted text-muted-foreground">
|
|
50
|
+
{{ member.name.split(' ').map(n => n[0]).join('') }}
|
|
51
|
+
</div>
|
|
52
|
+
<div className="min-w-0">
|
|
53
|
+
<p className="text-sm font-medium truncate">{{ member.name }}</p>
|
|
54
|
+
<p className="text-xs text-muted-foreground">{{ member.email }}</p>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
</TableCell>
|
|
58
|
+
<TableCell className="text-sm">{{ member.role }}</TableCell>
|
|
59
|
+
<TableCell>
|
|
60
|
+
<Badge :variant="member.status === 'active' ? 'default' : 'secondary'">{{ member.status }}</Badge>
|
|
61
|
+
</TableCell>
|
|
62
|
+
<TableCell className="text-right text-sm text-muted-foreground">{{ member.lastActive }}</TableCell>
|
|
63
|
+
<TableCell className="pr-6">
|
|
64
|
+
<DropdownMenu>
|
|
65
|
+
<DropdownMenuTrigger as-child>
|
|
66
|
+
<Button variant="ghost" size="icon" className="h-8 w-8">
|
|
67
|
+
<MoreHorizontal className="w-4 h-4" />
|
|
68
|
+
</Button>
|
|
69
|
+
</DropdownMenuTrigger>
|
|
70
|
+
<DropdownMenuContent align="end">
|
|
71
|
+
<DropdownMenuItem><Pencil className="w-4 h-4" /> Edit</DropdownMenuItem>
|
|
72
|
+
<DropdownMenuSeparator />
|
|
73
|
+
<DropdownMenuItem className="text-destructive"><Trash2 className="w-4 h-4" /> Remove</DropdownMenuItem>
|
|
74
|
+
</DropdownMenuContent>
|
|
75
|
+
</DropdownMenu>
|
|
76
|
+
</TableCell>
|
|
77
|
+
</TableRow>
|
|
78
|
+
</TableBody>
|
|
79
|
+
</Table>
|
|
80
|
+
</CardContent>
|
|
81
|
+
<!-- Pagination -->
|
|
82
|
+
<CardFooter className="justify-between border-t py-3">
|
|
83
|
+
<p className="text-sm text-muted-foreground">Showing 1–10 of 47 members</p>
|
|
84
|
+
<div className="flex items-center gap-2">
|
|
85
|
+
<Button variant="outline" size="sm" disabled><ChevronLeft className="w-4 h-4" /></Button>
|
|
86
|
+
<Button variant="outline" size="sm"><ChevronRight className="w-4 h-4" /></Button>
|
|
87
|
+
</div>
|
|
88
|
+
</CardFooter>
|
|
89
|
+
</Card>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Key Structural Rules
|
|
93
|
+
|
|
94
|
+
1. **Wrap table in Card** — `CardContent` gets `className="p-0"` to let the table bleed edge-to-edge.
|
|
95
|
+
2. **Cell Alignment** — Because the `CardHeader` has 24px padding (`p-6`) and `Table` cells default to 16px (`p-4`), you must add `className="pl-6"` to the first `TableHead`/`TableCell` and `className="pr-6"` to the last `TableHead`/`TableCell` so the table visually aligns with the header above it.
|
|
96
|
+
3. **Toolbar in CardHeader** — search + filters above the table, primary action button top-right.
|
|
97
|
+
4. **Pagination in CardFooter** — results count left, page controls right, separated by `border-t`.
|
|
98
|
+
5. **First column is the identifier** — always name/title with `font-medium`, often with avatar.
|
|
99
|
+
6. **Last column is actions** — `w-10` header, three-dot DropdownMenu.
|
|
100
|
+
7. **5–8 rows for mockups** — enough to feel real, not so many it's overwhelming.
|
|
101
|
+
|
|
102
|
+
## Bulk Actions
|
|
103
|
+
|
|
104
|
+
When the table supports multi-select, show a floating action bar:
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
<div v-if="selectedCount > 0" className="flex items-center gap-3 px-4 py-2 bg-muted rounded-md">
|
|
108
|
+
<p className="text-sm font-medium">{{ selectedCount }} selected</p>
|
|
109
|
+
<Separator orientation="vertical" className="h-4" />
|
|
110
|
+
<Button variant="ghost" size="sm">Archive</Button>
|
|
111
|
+
<Button variant="ghost" size="sm" className="text-destructive">Delete</Button>
|
|
112
|
+
</div>
|
|
113
|
+
```
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Pattern: Empty States
|
|
2
|
+
|
|
3
|
+
Every empty view must have a well-designed empty state. Never show a blank page.
|
|
4
|
+
|
|
5
|
+
## Standard Empty State
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
<div className="flex flex-col items-center justify-center py-16 text-center">
|
|
9
|
+
<div className="w-12 h-12 rounded-xl bg-muted flex items-center justify-center mb-4">
|
|
10
|
+
<Inbox className="w-6 h-6 text-muted-foreground" />
|
|
11
|
+
</div>
|
|
12
|
+
<h3 className="text-lg font-semibold mb-1">No messages yet</h3>
|
|
13
|
+
<p className="text-sm text-muted-foreground max-w-sm mb-4">
|
|
14
|
+
When you receive messages, they'll appear here. Start a conversation to get going.
|
|
15
|
+
</p>
|
|
16
|
+
<Button>
|
|
17
|
+
<Plus className="w-4 h-4" /> New Message
|
|
18
|
+
</Button>
|
|
19
|
+
</div>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Required Elements
|
|
23
|
+
|
|
24
|
+
Every empty state needs all four:
|
|
25
|
+
|
|
26
|
+
1. **Icon** — A relevant Lucide icon in a muted background container (`w-12 h-12 rounded-xl bg-muted`)
|
|
27
|
+
2. **Title** — Clear, specific headline (`text-lg font-semibold`)
|
|
28
|
+
3. **Description** — Explains what goes here and how to get started (`text-sm text-muted-foreground max-w-sm`)
|
|
29
|
+
4. **CTA Button** — Primary action to create the first item
|
|
30
|
+
|
|
31
|
+
## Context-Specific Examples
|
|
32
|
+
|
|
33
|
+
### Empty Table
|
|
34
|
+
```tsx
|
|
35
|
+
<TableRow>
|
|
36
|
+
<TableCell :colspan="columnCount" className="h-48">
|
|
37
|
+
<div className="flex flex-col items-center justify-center text-center">
|
|
38
|
+
<div className="w-12 h-12 rounded-xl bg-muted flex items-center justify-center mb-4">
|
|
39
|
+
<FileX className="w-6 h-6 text-muted-foreground" />
|
|
40
|
+
</div>
|
|
41
|
+
<p className="text-sm font-medium mb-1">No results found</p>
|
|
42
|
+
<p className="text-xs text-muted-foreground">Try adjusting your search or filters.</p>
|
|
43
|
+
</div>
|
|
44
|
+
</TableCell>
|
|
45
|
+
</TableRow>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Empty Search Results
|
|
49
|
+
No CTA button — instead, suggest adjusting the search:
|
|
50
|
+
```tsx
|
|
51
|
+
<div className="flex flex-col items-center justify-center py-12 text-center">
|
|
52
|
+
<Search className="w-10 h-10 text-muted-foreground mb-4" />
|
|
53
|
+
<h3 className="text-lg font-semibold mb-1">No results for "{{ query }}"</h3>
|
|
54
|
+
<p className="text-sm text-muted-foreground">Check your spelling or try different keywords.</p>
|
|
55
|
+
</div>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Icon Suggestions
|
|
59
|
+
|
|
60
|
+
| Context | Icon |
|
|
61
|
+
|---------|------|
|
|
62
|
+
| Messages/inbox | `Inbox` or `MessageSquare` |
|
|
63
|
+
| Files/documents | `FileText` or `FolderOpen` |
|
|
64
|
+
| Users/team | `Users` or `UserPlus` |
|
|
65
|
+
| Tasks/projects | `CheckSquare` or `Clipboard` |
|
|
66
|
+
| Search results | `Search` |
|
|
67
|
+
| Products/items | `Package` or `ShoppingBag` |
|
|
68
|
+
| Events/calendar | `Calendar` |
|
|
69
|
+
| Analytics/data | `BarChart3` or `LineChart` |
|
|
70
|
+
| Notifications | `Bell` |
|
|
71
|
+
| Errors | `AlertCircle` or `FileX` |
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Pattern: Error States
|
|
2
|
+
|
|
3
|
+
Error handling UI for queries, mutations, forms, and network issues.
|
|
4
|
+
|
|
5
|
+
## Page-Level Error
|
|
6
|
+
|
|
7
|
+
When a data fetch fails and the entire page can't render:
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
<div className="flex flex-col items-center justify-center py-16 text-center">
|
|
11
|
+
<div className="w-12 h-12 rounded-xl bg-destructive/10 flex items-center justify-center mb-4">
|
|
12
|
+
<AlertCircle className="w-6 h-6 text-destructive" />
|
|
13
|
+
</div>
|
|
14
|
+
<h3 className="text-lg font-semibold mb-1">Something went wrong</h3>
|
|
15
|
+
<p className="text-sm text-muted-foreground max-w-sm mb-4">
|
|
16
|
+
We couldn't load this page. Please try again.
|
|
17
|
+
</p>
|
|
18
|
+
<Button variant="outline" onClick="retry">
|
|
19
|
+
<RefreshCw className="w-4 h-4" /> Try Again
|
|
20
|
+
</Button>
|
|
21
|
+
</div>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Inline Error (Alert Banner)
|
|
25
|
+
|
|
26
|
+
For errors within a page that still has other functional content:
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
<Alert variant="destructive">
|
|
30
|
+
<AlertCircle className="h-4 w-4" />
|
|
31
|
+
<AlertTitle>Failed to load recent activity</AlertTitle>
|
|
32
|
+
<AlertDescription className="flex items-center justify-between">
|
|
33
|
+
<span>Please check your connection and try again.</span>
|
|
34
|
+
<Button variant="outline" size="sm" onClick="retry">Retry</Button>
|
|
35
|
+
</AlertDescription>
|
|
36
|
+
</Alert>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Mutation Error (Toast)
|
|
40
|
+
|
|
41
|
+
For save/create/update/delete failures:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
try {
|
|
45
|
+
await saveData()
|
|
46
|
+
toast.success('Changes saved successfully')
|
|
47
|
+
} catch (error) {
|
|
48
|
+
toast.error('Failed to save changes', {
|
|
49
|
+
action: { label: 'Retry', onClick: () => saveData() }
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Form Validation Errors
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
<div className="space-y-2">
|
|
58
|
+
<Label for="email" className="text-destructive">Email</Label>
|
|
59
|
+
<Input id="email" className="border-destructive focus-visible:ring-destructive" defaultValue="email" />
|
|
60
|
+
<p className="text-xs text-destructive">Please enter a valid email address.</p>
|
|
61
|
+
</div>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Decision Tree
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
What failed?
|
|
68
|
+
├─ Page data fetch → Full-page error state
|
|
69
|
+
├─ Section data fetch → Inline Alert with retry
|
|
70
|
+
├─ Save/update/delete → Toast with retry action
|
|
71
|
+
├─ Form validation → Inline field errors (red border + message)
|
|
72
|
+
├─ Network offline → Banner at top of page
|
|
73
|
+
```
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Pattern: Forms
|
|
2
|
+
|
|
3
|
+
Create/edit forms, multi-step wizards, inline editing, and validation patterns.
|
|
4
|
+
|
|
5
|
+
## Standard Create Form (in Dialog)
|
|
6
|
+
|
|
7
|
+
For simple entities with ≤5 fields:
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
<Dialog v-model:open="showCreate">
|
|
11
|
+
<DialogTrigger as-child>
|
|
12
|
+
<Button><Plus className="w-4 h-4" /> New Project</Button>
|
|
13
|
+
</DialogTrigger>
|
|
14
|
+
<DialogContent className="sm:max-w-md">
|
|
15
|
+
<DialogHeader>
|
|
16
|
+
<DialogTitle>Create Project</DialogTitle>
|
|
17
|
+
<DialogDescription>Add a new project to your workspace.</DialogDescription>
|
|
18
|
+
</DialogHeader>
|
|
19
|
+
<form @submit.prevent="handleCreate" className="space-y-4 py-4">
|
|
20
|
+
<div className="space-y-2">
|
|
21
|
+
<Label for="name">Name</Label>
|
|
22
|
+
<Input id="name" defaultValue="form.name" placeholder="My Project" />
|
|
23
|
+
</div>
|
|
24
|
+
<div className="space-y-2">
|
|
25
|
+
<Label for="desc">Description</Label>
|
|
26
|
+
<Textarea id="desc" defaultValue="form.description" placeholder="What is this project about?" rows="3" />
|
|
27
|
+
</div>
|
|
28
|
+
<div className="space-y-2">
|
|
29
|
+
<Label>Priority</Label>
|
|
30
|
+
<Select defaultValue="form.priority">
|
|
31
|
+
<SelectTrigger><SelectValue placeholder="Select priority" /></SelectTrigger>
|
|
32
|
+
<SelectContent>
|
|
33
|
+
<SelectItem value="low">Low</SelectItem>
|
|
34
|
+
<SelectItem value="medium">Medium</SelectItem>
|
|
35
|
+
<SelectItem value="high">High</SelectItem>
|
|
36
|
+
</SelectContent>
|
|
37
|
+
</Select>
|
|
38
|
+
</div>
|
|
39
|
+
</form>
|
|
40
|
+
<DialogFooter>
|
|
41
|
+
<DialogClose as-child>
|
|
42
|
+
<Button variant="outline">Cancel</Button>
|
|
43
|
+
</DialogClose>
|
|
44
|
+
<Button onClick="handleCreate" :disabled="creating">
|
|
45
|
+
<Loader2 v-if="creating" className="w-4 h-4 animate-spin" />
|
|
46
|
+
{{ creating ? 'Creating...' : 'Create Project' }}
|
|
47
|
+
</Button>
|
|
48
|
+
</DialogFooter>
|
|
49
|
+
</DialogContent>
|
|
50
|
+
</Dialog>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Full-Page Form (Complex)
|
|
54
|
+
|
|
55
|
+
For forms with 6+ fields or grouped sections:
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
<div className="max-w-2xl mx-auto px-6 py-8 space-y-8">
|
|
59
|
+
<div>
|
|
60
|
+
<Button variant="ghost" size="sm" className="gap-1 mb-4">
|
|
61
|
+
<ArrowLeft className="w-4 h-4" /> Back
|
|
62
|
+
</Button>
|
|
63
|
+
<h1 className="text-2xl font-bold">Create Organization</h1>
|
|
64
|
+
<p className="text-muted-foreground">Set up your organization details.</p>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<form @submit.prevent="handleSubmit" className="space-y-8">
|
|
68
|
+
<!-- Section 1 -->
|
|
69
|
+
<div className="space-y-4">
|
|
70
|
+
<h2 className="text-lg font-semibold">Basic Information</h2>
|
|
71
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
72
|
+
<div className="space-y-2">
|
|
73
|
+
<Label>Organization Name</Label>
|
|
74
|
+
<Input defaultValue="form.name" />
|
|
75
|
+
</div>
|
|
76
|
+
<div className="space-y-2">
|
|
77
|
+
<Label>Website (optional)</Label>
|
|
78
|
+
<Input defaultValue="form.website" placeholder="https://" />
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
<div className="space-y-2">
|
|
82
|
+
<Label>Description</Label>
|
|
83
|
+
<Textarea defaultValue="form.description" rows="3" />
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<Separator />
|
|
88
|
+
|
|
89
|
+
<!-- Section 2 -->
|
|
90
|
+
<div className="space-y-4">
|
|
91
|
+
<h2 className="text-lg font-semibold">Settings</h2>
|
|
92
|
+
<!-- More fields -->
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
<!-- Actions (sticky or at bottom) -->
|
|
96
|
+
<div className="flex items-center justify-end gap-2 pt-4 border-t">
|
|
97
|
+
<Button variant="outline" type="button">Cancel</Button>
|
|
98
|
+
<Button type="submit" :disabled="saving">
|
|
99
|
+
<Loader2 v-if="saving" className="w-4 h-4 animate-spin" />
|
|
100
|
+
{{ saving ? 'Creating...' : 'Create Organization' }}
|
|
101
|
+
</Button>
|
|
102
|
+
</div>
|
|
103
|
+
</form>
|
|
104
|
+
</div>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Edit Form
|
|
108
|
+
|
|
109
|
+
Same structure as create, but pre-filled with existing data and using "Save Changes" as the submit label.
|
|
110
|
+
|
|
111
|
+
## Inline Edit
|
|
112
|
+
|
|
113
|
+
For single-field updates:
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
<div className="flex items-center gap-2 group">
|
|
117
|
+
<p v-if="!editing" className="text-sm" onClick="editing = true">
|
|
118
|
+
{{ value }}
|
|
119
|
+
</p>
|
|
120
|
+
<Input v-else defaultValue="value" @blur="save" @keyup.enter="save" className="h-7 text-sm" autofocus />
|
|
121
|
+
<Button v-if="!editing" variant="ghost" size="icon" className="h-6 w-6 opacity-0 group-hover:opacity-100"
|
|
122
|
+
onClick="editing = true">
|
|
123
|
+
<Pencil className="w-3 h-3" />
|
|
124
|
+
</Button>
|
|
125
|
+
</div>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Decision Tree
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
How many fields?
|
|
132
|
+
├─ 1 field → Inline edit (click-to-edit)
|
|
133
|
+
├─ 2–5 fields → Dialog
|
|
134
|
+
├─ 6–8 fields → Full-page form with sections
|
|
135
|
+
├─ 9+ fields or rich content → Full-page form with section nav
|
|
136
|
+
```
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Pattern: Loading States
|
|
2
|
+
|
|
3
|
+
Skeleton placeholders, spinners, and progressive loading patterns.
|
|
4
|
+
|
|
5
|
+
## Skeleton Loading (Preferred)
|
|
6
|
+
|
|
7
|
+
Skeletons replace content with gray shapes that match the content's layout. They signal that content is loading without a jarring spinner.
|
|
8
|
+
|
|
9
|
+
### Page with Stat Cards + Table
|
|
10
|
+
```tsx
|
|
11
|
+
<!-- Stat card skeletons -->
|
|
12
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
13
|
+
<Card v-for="i in 4" :key="i">
|
|
14
|
+
<CardHeader className="pb-2">
|
|
15
|
+
<Skeleton className="h-4 w-24" />
|
|
16
|
+
</CardHeader>
|
|
17
|
+
<CardContent>
|
|
18
|
+
<Skeleton className="h-7 w-20 mb-1" />
|
|
19
|
+
<Skeleton className="h-3 w-32" />
|
|
20
|
+
</CardContent>
|
|
21
|
+
</Card>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<!-- Table skeleton -->
|
|
25
|
+
<Card>
|
|
26
|
+
<CardHeader>
|
|
27
|
+
<Skeleton className="h-5 w-32" />
|
|
28
|
+
</CardHeader>
|
|
29
|
+
<CardContent className="space-y-3">
|
|
30
|
+
<div v-for="i in 5" :key="i" className="flex items-center gap-4">
|
|
31
|
+
<Skeleton className="w-8 h-8 rounded-full" />
|
|
32
|
+
<div className="flex-1 space-y-2">
|
|
33
|
+
<Skeleton className="h-4 w-1/3" />
|
|
34
|
+
<Skeleton className="h-3 w-1/4" />
|
|
35
|
+
</div>
|
|
36
|
+
<Skeleton className="h-5 w-16 rounded-full" />
|
|
37
|
+
<Skeleton className="h-4 w-20" />
|
|
38
|
+
</div>
|
|
39
|
+
</CardContent>
|
|
40
|
+
</Card>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Rules for Skeletons
|
|
44
|
+
- **Match the shape**: Round for avatars (`rounded-full`), pill for badges (`rounded-full`), rectangle for text
|
|
45
|
+
- **Vary widths**: Don't make all skeletons the same width — use `w-1/3`, `w-1/2`, `w-3/4` to look natural
|
|
46
|
+
- **Show 3–5 rows**: Don't show 1 skeleton or 20 — 3-5 feels right
|
|
47
|
+
- **No text**: Skeletons are gray blocks, never "Loading..." text
|
|
48
|
+
|
|
49
|
+
## Button Loading State
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
<Button :disabled="saving" onClick="handleSave">
|
|
53
|
+
<Loader2 v-if="saving" className="w-4 h-4 animate-spin" />
|
|
54
|
+
{{ saving ? 'Saving...' : 'Save Changes' }}
|
|
55
|
+
</Button>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The button shows a spinner icon AND is disabled during the operation.
|
|
59
|
+
|
|
60
|
+
## Full-Page Loading
|
|
61
|
+
|
|
62
|
+
For initial page load where no skeleton shape is known:
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
<div className="flex items-center justify-center h-64">
|
|
66
|
+
<Loader2 className="w-6 h-6 animate-spin text-muted-foreground" />
|
|
67
|
+
</div>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Use sparingly — skeletons are almost always better.
|
|
71
|
+
|
|
72
|
+
## Progressive Loading
|
|
73
|
+
|
|
74
|
+
Load and show content as it becomes available:
|
|
75
|
+
```tsx
|
|
76
|
+
<div v-if="stats" className="grid grid-cols-4 gap-4">
|
|
77
|
+
<!-- Stats render immediately -->
|
|
78
|
+
</div>
|
|
79
|
+
<div v-else className="grid grid-cols-4 gap-4">
|
|
80
|
+
<!-- Stat skeletons -->
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<!-- Table loads independently -->
|
|
84
|
+
<Card>
|
|
85
|
+
<CardContent v-if="tableData" className="p-0">
|
|
86
|
+
<Table><!-- ... --></Table>
|
|
87
|
+
</CardContent>
|
|
88
|
+
<CardContent v-else>
|
|
89
|
+
<!-- Table skeleton -->
|
|
90
|
+
</CardContent>
|
|
91
|
+
</Card>
|
|
92
|
+
```
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Pattern: Navigation
|
|
2
|
+
|
|
3
|
+
Navigation structures for apps: sidebars, top bars, mobile nav, and nav composition.
|
|
4
|
+
|
|
5
|
+
## Sidebar Navigation
|
|
6
|
+
|
|
7
|
+
The most common desktop navigation pattern. Vertical nav links on the left, content on the right.
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
<div className="flex h-screen">
|
|
11
|
+
<!-- Sidebar -->
|
|
12
|
+
<aside className="w-64 border-r bg-card flex flex-col shrink-0">
|
|
13
|
+
<!-- Logo -->
|
|
14
|
+
<div className="h-14 flex items-center px-4 border-b">
|
|
15
|
+
<div className="flex items-center gap-2">
|
|
16
|
+
<div className="w-7 h-7 rounded-md bg-primary flex items-center justify-center">
|
|
17
|
+
<Zap className="w-4 h-4 text-primary-foreground" />
|
|
18
|
+
</div>
|
|
19
|
+
<span className="text-sm font-semibold">Acme Inc</span>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
<!-- Nav Links -->
|
|
23
|
+
<nav className="flex-1 p-3 space-y-1">
|
|
24
|
+
<button v-for="item in navItems" :key="item.label"
|
|
25
|
+
className={cn(
|
|
26
|
+
'w-full flex items-center gap-3 px-3 py-2 text-sm rounded-md transition-colors',
|
|
27
|
+
item.active ? 'bg-accent font-medium' : 'text-muted-foreground hover:bg-accent/50 hover:text-foreground'
|
|
28
|
+
)}>
|
|
29
|
+
<component :is="item.icon" className="w-4 h-4" />
|
|
30
|
+
{{ item.label }}
|
|
31
|
+
</button>
|
|
32
|
+
</nav>
|
|
33
|
+
<!-- User menu at bottom -->
|
|
34
|
+
<div className="border-t p-3">
|
|
35
|
+
<div className="flex items-center gap-3 px-3 py-2">
|
|
36
|
+
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-primary/60 to-primary shrink-0" />
|
|
37
|
+
<div className="flex-1 min-w-0">
|
|
38
|
+
<p className="text-sm font-medium truncate">Sarah Chen</p>
|
|
39
|
+
<p className="text-xs text-muted-foreground truncate">sarah@acme.co</p>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</aside>
|
|
44
|
+
<!-- Main Content -->
|
|
45
|
+
<main className="flex-1 overflow-y-auto">
|
|
46
|
+
<div className="max-w-7xl mx-auto px-6 py-8">
|
|
47
|
+
<!-- Page content -->
|
|
48
|
+
</div>
|
|
49
|
+
</main>
|
|
50
|
+
</div>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Sidebar Nav Item States
|
|
54
|
+
| State | Classes |
|
|
55
|
+
|-------|---------|
|
|
56
|
+
| Default | `text-muted-foreground hover:bg-accent/50 hover:text-foreground` |
|
|
57
|
+
| Active | `bg-accent font-medium` (text color inherits foreground) |
|
|
58
|
+
| Disabled | `opacity-50 pointer-events-none` |
|
|
59
|
+
|
|
60
|
+
### Sidebar with Sections
|
|
61
|
+
```tsx
|
|
62
|
+
<nav className="flex-1 p-3 space-y-6">
|
|
63
|
+
<div className="space-y-1">
|
|
64
|
+
<p className="px-3 text-xs font-medium text-muted-foreground uppercase tracking-wider mb-2">Main</p>
|
|
65
|
+
<!-- Main nav items -->
|
|
66
|
+
</div>
|
|
67
|
+
<div className="space-y-1">
|
|
68
|
+
<p className="px-3 text-xs font-medium text-muted-foreground uppercase tracking-wider mb-2">Settings</p>
|
|
69
|
+
<!-- Settings nav items -->
|
|
70
|
+
</div>
|
|
71
|
+
</nav>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Top Navigation Bar
|
|
75
|
+
|
|
76
|
+
For simpler apps or when sidebar isn't appropriate.
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
<header className="h-14 border-b bg-card flex items-center px-6">
|
|
80
|
+
<div className="flex items-center gap-6 w-full max-w-7xl mx-auto">
|
|
81
|
+
<!-- Logo -->
|
|
82
|
+
<div className="flex items-center gap-2 shrink-0">
|
|
83
|
+
<div className="w-7 h-7 rounded-md bg-primary flex items-center justify-center">
|
|
84
|
+
<Zap className="w-4 h-4 text-primary-foreground" />
|
|
85
|
+
</div>
|
|
86
|
+
<span className="text-sm font-semibold">Acme</span>
|
|
87
|
+
</div>
|
|
88
|
+
<!-- Nav links -->
|
|
89
|
+
<nav className="flex items-center gap-1">
|
|
90
|
+
<Button v-for="item in navItems" :key="item.label"
|
|
91
|
+
:variant="item.active ? 'secondary' : 'ghost'" size="sm">
|
|
92
|
+
{{ item.label }}
|
|
93
|
+
</Button>
|
|
94
|
+
</nav>
|
|
95
|
+
<!-- Right side -->
|
|
96
|
+
<div className="ml-auto flex items-center gap-2">
|
|
97
|
+
<Button variant="ghost" size="icon"><Bell className="w-4 h-4" /></Button>
|
|
98
|
+
<Button variant="ghost" size="icon" className="rounded-full">
|
|
99
|
+
<div className="w-7 h-7 rounded-full bg-gradient-to-br from-primary/60 to-primary" />
|
|
100
|
+
</Button>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
</header>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## When to Use Which
|
|
107
|
+
|
|
108
|
+
| Navigation Type | Best For |
|
|
109
|
+
|----------------|----------|
|
|
110
|
+
| Sidebar | SaaS dashboards, admin panels, complex apps with many sections |
|
|
111
|
+
| Top bar | Marketing sites, simple apps, consumer products |
|
|
112
|
+
| Top bar + sidebar | Very complex apps (e.g., settings under an app that uses top bar) |
|
|
113
|
+
| Bottom tabs | Mobile apps (max 5 items) |
|