@reinvented/design 0.1.0 → 0.2.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/DESIGN_GUIDE.md +148 -0
- package/README.md +39 -162
- package/docs/components/alert-dialog.md +32 -0
- package/docs/components/avatar.md +14 -0
- package/docs/components/badge.md +24 -0
- package/docs/components/button.md +69 -0
- package/docs/components/card.md +49 -0
- package/docs/components/dialog.md +46 -0
- package/docs/components/dropdown-menu.md +32 -0
- package/docs/components/index.md +69 -0
- package/docs/components/input.md +34 -0
- package/docs/components/remaining-components.md +253 -0
- package/docs/components/scroll-area.md +17 -0
- package/docs/components/select.md +31 -0
- package/docs/components/separator.md +14 -0
- package/docs/components/sheet.md +32 -0
- package/docs/components/skeleton.md +20 -0
- package/docs/components/table.md +33 -0
- package/docs/components/tabs.md +23 -0
- package/docs/conventions.md +74 -0
- package/docs/layouts/dashboard.md +70 -0
- package/docs/layouts/detail-page.md +83 -0
- package/docs/layouts/index.md +45 -0
- package/docs/layouts/list-page.md +107 -0
- package/docs/layouts/settings-page.md +79 -0
- package/docs/layouts/step-wizard.md +73 -0
- package/docs/patterns/index.md +39 -0
- package/docs/rules.md +43 -0
- package/docs/visual-polish.md +141 -0
- package/package.json +40 -61
- package/src/components/ui/accordion/Accordion.vue +13 -0
- package/src/components/ui/accordion/AccordionContent.vue +20 -0
- package/src/components/ui/accordion/AccordionItem.vue +15 -0
- package/src/components/ui/accordion/AccordionTrigger.vue +25 -0
- package/src/components/ui/accordion/index.ts +4 -0
- package/src/components/ui/alert/Alert.vue +38 -0
- package/src/components/ui/alert/AlertDescription.vue +12 -0
- package/src/components/ui/alert/AlertTitle.vue +12 -0
- package/src/components/ui/alert/index.ts +3 -0
- package/src/components/ui/alert-dialog/AlertDialog.vue +13 -0
- package/src/components/ui/alert-dialog/AlertDialogAction.vue +21 -0
- package/src/components/ui/alert-dialog/AlertDialogCancel.vue +21 -0
- package/src/components/ui/alert-dialog/AlertDialogContent.vue +39 -0
- package/src/components/ui/alert-dialog/AlertDialogDescription.vue +15 -0
- package/src/components/ui/alert-dialog/AlertDialogFooter.vue +12 -0
- package/src/components/ui/alert-dialog/AlertDialogHeader.vue +12 -0
- package/src/components/ui/alert-dialog/AlertDialogTitle.vue +15 -0
- package/src/components/ui/alert-dialog/AlertDialogTrigger.vue +11 -0
- package/src/components/ui/alert-dialog/index.ts +9 -0
- package/src/components/ui/avatar/Avatar.vue +14 -0
- package/src/components/ui/avatar/index.ts +1 -0
- package/src/components/ui/badge/Badge.vue +27 -0
- package/src/components/ui/badge/index.ts +1 -0
- package/src/components/ui/breadcrumb/Breadcrumb.vue +6 -0
- package/src/components/ui/breadcrumb/BreadcrumbEllipsis.vue +12 -0
- package/src/components/ui/breadcrumb/BreadcrumbItem.vue +6 -0
- package/src/components/ui/breadcrumb/BreadcrumbLink.vue +20 -0
- package/src/components/ui/breadcrumb/BreadcrumbList.vue +6 -0
- package/src/components/ui/breadcrumb/BreadcrumbPage.vue +6 -0
- package/src/components/ui/breadcrumb/BreadcrumbSeparator.vue +11 -0
- package/src/components/ui/breadcrumb/index.ts +7 -0
- package/src/components/ui/button/Button.vue +65 -0
- package/src/components/ui/button/index.ts +1 -0
- package/src/components/ui/card/Card.vue +13 -0
- package/src/components/ui/card/CardContent.vue +7 -0
- package/src/components/ui/card/CardDescription.vue +7 -0
- package/src/components/ui/card/CardFooter.vue +7 -0
- package/src/components/ui/card/CardHeader.vue +9 -0
- package/src/components/ui/card/CardTitle.vue +7 -0
- package/src/components/ui/card/index.ts +6 -0
- package/src/components/ui/checkbox/Checkbox.vue +25 -0
- package/src/components/ui/checkbox/index.ts +1 -0
- package/src/components/ui/collapsible/Collapsible.vue +13 -0
- package/src/components/ui/collapsible/index.ts +2 -0
- package/src/components/ui/command/Command.vue +16 -0
- package/src/components/ui/command/CommandEmpty.vue +5 -0
- package/src/components/ui/command/CommandGroup.vue +22 -0
- package/src/components/ui/command/CommandInput.vue +21 -0
- package/src/components/ui/command/CommandItem.vue +22 -0
- package/src/components/ui/command/CommandList.vue +17 -0
- package/src/components/ui/command/CommandSeparator.vue +5 -0
- package/src/components/ui/command/index.ts +7 -0
- package/src/components/ui/context-menu/ContextMenuContent.vue +24 -0
- package/src/components/ui/context-menu/ContextMenuItem.vue +16 -0
- package/src/components/ui/context-menu/ContextMenuLabel.vue +9 -0
- package/src/components/ui/context-menu/ContextMenuSeparator.vue +9 -0
- package/src/components/ui/context-menu/ContextMenuSubContent.vue +14 -0
- package/src/components/ui/context-menu/index.ts +9 -0
- package/src/components/ui/dialog/Dialog.vue +14 -0
- package/src/components/ui/dialog/DialogClose.vue +12 -0
- package/src/components/ui/dialog/DialogContent.vue +48 -0
- package/src/components/ui/dialog/DialogDescription.vue +23 -0
- package/src/components/ui/dialog/DialogFooter.vue +12 -0
- package/src/components/ui/dialog/DialogHeader.vue +12 -0
- package/src/components/ui/dialog/DialogScrollContent.vue +47 -0
- package/src/components/ui/dialog/DialogTitle.vue +23 -0
- package/src/components/ui/dialog/DialogTrigger.vue +12 -0
- package/src/components/ui/dialog/index.ts +9 -0
- package/src/components/ui/dropdown-menu/DropdownMenu.vue +13 -0
- package/src/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +28 -0
- package/src/components/ui/dropdown-menu/DropdownMenuContent.vue +33 -0
- package/src/components/ui/dropdown-menu/DropdownMenuGroup.vue +11 -0
- package/src/components/ui/dropdown-menu/DropdownMenuItem.vue +27 -0
- package/src/components/ui/dropdown-menu/DropdownMenuLabel.vue +23 -0
- package/src/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +13 -0
- package/src/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +27 -0
- package/src/components/ui/dropdown-menu/DropdownMenuSeparator.vue +13 -0
- package/src/components/ui/dropdown-menu/DropdownMenuShortcut.vue +12 -0
- package/src/components/ui/dropdown-menu/DropdownMenuSub.vue +13 -0
- package/src/components/ui/dropdown-menu/DropdownMenuSubContent.vue +27 -0
- package/src/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +23 -0
- package/src/components/ui/dropdown-menu/DropdownMenuTrigger.vue +11 -0
- package/src/components/ui/dropdown-menu/index.ts +14 -0
- package/src/components/ui/form/FormControl.vue +3 -0
- package/src/components/ui/form/FormDescription.vue +6 -0
- package/src/components/ui/form/FormItem.vue +6 -0
- package/src/components/ui/form/FormLabel.vue +10 -0
- package/src/components/ui/form/FormMessage.vue +10 -0
- package/src/components/ui/form/index.ts +9 -0
- package/src/components/ui/hover-card/HoverCard.vue +13 -0
- package/src/components/ui/hover-card/HoverCardContent.vue +26 -0
- package/src/components/ui/hover-card/HoverCardTrigger.vue +11 -0
- package/src/components/ui/hover-card/index.ts +3 -0
- package/src/components/ui/input/Input.vue +23 -0
- package/src/components/ui/input/index.ts +1 -0
- package/src/components/ui/label/Label.vue +18 -0
- package/src/components/ui/label/index.ts +1 -0
- package/src/components/ui/lib/utils.ts +2 -0
- package/src/components/ui/menubar/MenubarContent.vue +15 -0
- package/src/components/ui/menubar/MenubarItem.vue +13 -0
- package/src/components/ui/menubar/MenubarTrigger.vue +13 -0
- package/src/components/ui/menubar/index.ts +5 -0
- package/src/components/ui/navigation-menu/NavigationMenuContent.vue +14 -0
- package/src/components/ui/navigation-menu/NavigationMenuTrigger.vue +15 -0
- package/src/components/ui/navigation-menu/index.ts +4 -0
- package/src/components/ui/pagination/PaginationContent.vue +13 -0
- package/src/components/ui/pagination/PaginationEllipsis.vue +12 -0
- package/src/components/ui/pagination/PaginationNext.vue +14 -0
- package/src/components/ui/pagination/PaginationPrev.vue +14 -0
- package/src/components/ui/pagination/index.ts +6 -0
- package/src/components/ui/popover/Popover.vue +13 -0
- package/src/components/ui/popover/PopoverContent.vue +27 -0
- package/src/components/ui/popover/PopoverTrigger.vue +11 -0
- package/src/components/ui/popover/index.ts +3 -0
- package/src/components/ui/progress/Progress.vue +21 -0
- package/src/components/ui/progress/index.ts +1 -0
- package/src/components/ui/radio-group/RadioGroup.vue +16 -0
- package/src/components/ui/radio-group/RadioGroupItem.vue +24 -0
- package/src/components/ui/radio-group/index.ts +2 -0
- package/src/components/ui/scroll-area/ScrollArea.vue +13 -0
- package/src/components/ui/scroll-area/index.ts +1 -0
- package/src/components/ui/select/Select.vue +13 -0
- package/src/components/ui/select/SelectContent.vue +40 -0
- package/src/components/ui/select/SelectGroup.vue +15 -0
- package/src/components/ui/select/SelectItem.vue +30 -0
- package/src/components/ui/select/SelectLabel.vue +15 -0
- package/src/components/ui/select/SelectSeparator.vue +13 -0
- package/src/components/ui/select/SelectTrigger.vue +23 -0
- package/src/components/ui/select/SelectValue.vue +11 -0
- package/src/components/ui/select/index.ts +8 -0
- package/src/components/ui/separator/Separator.vue +16 -0
- package/src/components/ui/separator/index.ts +1 -0
- package/src/components/ui/sheet/Sheet.vue +13 -0
- package/src/components/ui/sheet/SheetClose.vue +11 -0
- package/src/components/ui/sheet/SheetContent.vue +65 -0
- package/src/components/ui/sheet/SheetDescription.vue +15 -0
- package/src/components/ui/sheet/SheetFooter.vue +12 -0
- package/src/components/ui/sheet/SheetHeader.vue +12 -0
- package/src/components/ui/sheet/SheetTitle.vue +15 -0
- package/src/components/ui/sheet/SheetTrigger.vue +11 -0
- package/src/components/ui/sheet/index.ts +8 -0
- package/src/components/ui/skeleton/Skeleton.vue +9 -0
- package/src/components/ui/skeleton/index.ts +1 -0
- package/src/components/ui/slider/Slider.vue +26 -0
- package/src/components/ui/slider/index.ts +1 -0
- package/src/components/ui/switch/Switch.vue +24 -0
- package/src/components/ui/switch/index.ts +1 -0
- package/src/components/ui/table/Table.vue +13 -0
- package/src/components/ui/table/TableBody.vue +6 -0
- package/src/components/ui/table/TableCaption.vue +6 -0
- package/src/components/ui/table/TableCell.vue +6 -0
- package/src/components/ui/table/TableFooter.vue +6 -0
- package/src/components/ui/table/TableHead.vue +6 -0
- package/src/components/ui/table/TableHeader.vue +6 -0
- package/src/components/ui/table/TableRow.vue +6 -0
- package/src/components/ui/table/index.ts +8 -0
- package/src/components/ui/tabs/Tabs.vue +13 -0
- package/src/components/ui/tabs/TabsContent.vue +21 -0
- package/src/components/ui/tabs/TabsList.vue +21 -0
- package/src/components/ui/tabs/TabsTrigger.vue +21 -0
- package/src/components/ui/tabs/index.ts +4 -0
- package/src/components/ui/textarea/Textarea.vue +29 -0
- package/src/components/ui/textarea/index.ts +1 -0
- package/src/components/ui/toggle/Toggle.vue +40 -0
- package/src/components/ui/toggle/index.ts +1 -0
- package/src/components/ui/toggle-group/ToggleGroup.vue +16 -0
- package/src/components/ui/toggle-group/ToggleGroupItem.vue +21 -0
- package/src/components/ui/toggle-group/index.ts +2 -0
- package/src/components/ui/tooltip/Tooltip.vue +13 -0
- package/src/components/ui/tooltip/TooltipContent.vue +27 -0
- package/src/components/ui/tooltip/TooltipProvider.vue +12 -0
- package/src/components/ui/tooltip/TooltipTrigger.vue +11 -0
- package/src/components/ui/tooltip/index.ts +4 -0
- package/src/env.d.ts +7 -0
- package/src/index.ts +63 -0
- package/src/lib/utils.ts +7 -0
- package/src/patterns/DetailView.vue +46 -0
- package/src/patterns/EmptyState.vue +27 -0
- package/src/patterns/FormView.vue +34 -0
- package/src/patterns/ListView.vue +45 -0
- package/src/styles/index.css +4 -0
- package/src/styles/tokens.css +144 -0
- package/tailwind.config.js +108 -0
- package/tsconfig.json +21 -0
- package/dist/index.css +0 -1890
- package/dist/index.d.ts +0 -406
- package/dist/index.js +0 -1721
- package/dist/index.js.map +0 -1
- package/tailwind.config.ts +0 -174
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Detail Page Layout
|
|
2
|
+
|
|
3
|
+
## When to Use
|
|
4
|
+
- Viewing a single item's complete information
|
|
5
|
+
- Item detail with related data sections
|
|
6
|
+
- Edit-in-place forms
|
|
7
|
+
|
|
8
|
+
## Skeleton Code
|
|
9
|
+
|
|
10
|
+
```vue
|
|
11
|
+
<script setup>
|
|
12
|
+
import {
|
|
13
|
+
Button, Badge, Separator, Tabs, TabsContent, TabsList, TabsTrigger,
|
|
14
|
+
Card, CardContent, CardHeader, CardTitle, Skeleton,
|
|
15
|
+
Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator,
|
|
16
|
+
} from '@reinvented/design'
|
|
17
|
+
import { ArrowLeft, Pencil, Trash2 } from 'lucide-vue-next'
|
|
18
|
+
|
|
19
|
+
const item = ref(null)
|
|
20
|
+
const loading = ref(true)
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8 space-y-6">
|
|
25
|
+
|
|
26
|
+
<!-- Breadcrumb -->
|
|
27
|
+
<Breadcrumb>
|
|
28
|
+
<BreadcrumbList>
|
|
29
|
+
<BreadcrumbItem><BreadcrumbLink href="/items">Items</BreadcrumbLink></BreadcrumbItem>
|
|
30
|
+
<BreadcrumbSeparator />
|
|
31
|
+
<BreadcrumbItem><BreadcrumbPage>{{ item?.name }}</BreadcrumbPage></BreadcrumbItem>
|
|
32
|
+
</BreadcrumbList>
|
|
33
|
+
</Breadcrumb>
|
|
34
|
+
|
|
35
|
+
<!-- Page Header -->
|
|
36
|
+
<div class="flex items-center justify-between">
|
|
37
|
+
<div class="flex items-center gap-3">
|
|
38
|
+
<h1 class="text-2xl font-bold tracking-tight">{{ item?.name }}</h1>
|
|
39
|
+
<Badge>{{ item?.status }}</Badge>
|
|
40
|
+
</div>
|
|
41
|
+
<div class="flex items-center gap-2">
|
|
42
|
+
<Button variant="outline"><Pencil class="mr-2 h-4 w-4" /> Edit</Button>
|
|
43
|
+
<Button variant="destructive"><Trash2 class="mr-2 h-4 w-4" /> Delete</Button>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<Separator />
|
|
48
|
+
|
|
49
|
+
<!-- Tabbed Content -->
|
|
50
|
+
<Tabs default-value="overview">
|
|
51
|
+
<TabsList>
|
|
52
|
+
<TabsTrigger value="overview">Overview</TabsTrigger>
|
|
53
|
+
<TabsTrigger value="activity">Activity</TabsTrigger>
|
|
54
|
+
<TabsTrigger value="settings">Settings</TabsTrigger>
|
|
55
|
+
</TabsList>
|
|
56
|
+
|
|
57
|
+
<TabsContent value="overview" class="space-y-6">
|
|
58
|
+
<!-- Detail Cards -->
|
|
59
|
+
<div class="grid gap-6 md:grid-cols-2">
|
|
60
|
+
<Card>
|
|
61
|
+
<CardHeader><CardTitle>Details</CardTitle></CardHeader>
|
|
62
|
+
<CardContent>
|
|
63
|
+
<dl class="space-y-3">
|
|
64
|
+
<div>
|
|
65
|
+
<dt class="text-sm text-muted-foreground">Created</dt>
|
|
66
|
+
<dd class="text-sm font-medium">{{ item?.createdAt }}</dd>
|
|
67
|
+
</div>
|
|
68
|
+
</dl>
|
|
69
|
+
</CardContent>
|
|
70
|
+
</Card>
|
|
71
|
+
</div>
|
|
72
|
+
</TabsContent>
|
|
73
|
+
</Tabs>
|
|
74
|
+
</div>
|
|
75
|
+
</template>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Key Patterns
|
|
79
|
+
- **Breadcrumb** at the top for back navigation
|
|
80
|
+
- **Header**: Title + badge + action buttons
|
|
81
|
+
- **Tabbed sections** for detailed content
|
|
82
|
+
- **Detail cards** in a 2-column grid for metadata
|
|
83
|
+
- **Separator** between header and tabbed content
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Layout Documentation
|
|
2
|
+
|
|
3
|
+
## Decision Tree
|
|
4
|
+
|
|
5
|
+
Use this tree to choose the right layout for each view:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Is this a list of items?
|
|
9
|
+
├─ Yes → List Page (list-page.md)
|
|
10
|
+
├─ No
|
|
11
|
+
│ ├─ Is this showing a single item's details?
|
|
12
|
+
│ │ ├─ Yes → Detail Page (detail-page.md)
|
|
13
|
+
│ │ ├─ No
|
|
14
|
+
│ │ │ ├─ Is this a dashboard/overview?
|
|
15
|
+
│ │ │ │ ├─ Yes → Dashboard (dashboard.md)
|
|
16
|
+
│ │ │ │ ├─ No
|
|
17
|
+
│ │ │ │ │ ├─ Is this a settings/configuration page?
|
|
18
|
+
│ │ │ │ │ │ ├─ Yes → Settings Page (settings-page.md)
|
|
19
|
+
│ │ │ │ │ │ ├─ No
|
|
20
|
+
│ │ │ │ │ │ │ ├─ Is this a multi-step process?
|
|
21
|
+
│ │ │ │ │ │ │ │ ├─ Yes → Step Wizard (step-wizard.md)
|
|
22
|
+
│ │ │ │ │ │ │ │ ├─ No
|
|
23
|
+
│ │ │ │ │ │ │ │ │ ├─ Is this a simple form?
|
|
24
|
+
│ │ │ │ │ │ │ │ │ │ ├─ ≤5 fields → Modal Form (use Dialog)
|
|
25
|
+
│ │ │ │ │ │ │ │ │ │ ├─ >5 fields → Full Page Form (detail-page.md variant)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Available Layouts
|
|
29
|
+
|
|
30
|
+
| Layout | File | Use for |
|
|
31
|
+
|--------|------|---------|
|
|
32
|
+
| [List Page](list-page.md) | ✅ | Filterable, searchable lists of items |
|
|
33
|
+
| [Detail Page](detail-page.md) | ✅ | Single item view with actions |
|
|
34
|
+
| [Dashboard](dashboard.md) | ✅ | Overview grids with stats and summaries |
|
|
35
|
+
| [Settings Page](settings-page.md) | ✅ | Configuration with sidebar nav |
|
|
36
|
+
| [Step Wizard](step-wizard.md) | ✅ | Multi-step guided processes |
|
|
37
|
+
| Modal Form | — | Use Dialog component (≤5 fields) |
|
|
38
|
+
|
|
39
|
+
## General Layout Rules
|
|
40
|
+
|
|
41
|
+
1. **Max content width**: `max-w-7xl mx-auto` for main content
|
|
42
|
+
2. **Page padding**: `px-4 sm:px-6 lg:px-8`
|
|
43
|
+
3. **Section spacing**: `space-y-6` between major sections
|
|
44
|
+
4. **Page header pattern**: Title + description + primary action
|
|
45
|
+
5. **Responsive**: All layouts must work at mobile (320px+), tablet (768px+), desktop (1024px+)
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# List Page Layout
|
|
2
|
+
|
|
3
|
+
## When to Use
|
|
4
|
+
- Browse a collection of items (tasks, users, apps)
|
|
5
|
+
- Filterable, searchable, sortable data
|
|
6
|
+
- Paginated or infinite-scroll lists
|
|
7
|
+
|
|
8
|
+
## Skeleton Code
|
|
9
|
+
|
|
10
|
+
```vue
|
|
11
|
+
<script setup>
|
|
12
|
+
import { ref } from 'vue'
|
|
13
|
+
import {
|
|
14
|
+
Button, Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue,
|
|
15
|
+
Card, CardContent, Badge, Skeleton,
|
|
16
|
+
DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger,
|
|
17
|
+
} from '@reinvented/design'
|
|
18
|
+
import { Plus, Search, MoreHorizontal } from 'lucide-vue-next'
|
|
19
|
+
|
|
20
|
+
const search = ref('')
|
|
21
|
+
const filter = ref('all')
|
|
22
|
+
const items = ref([]) // from GraphQL query
|
|
23
|
+
const loading = ref(true)
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<template>
|
|
27
|
+
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8 space-y-6">
|
|
28
|
+
|
|
29
|
+
<!-- Page Header -->
|
|
30
|
+
<div class="flex items-center justify-between">
|
|
31
|
+
<div>
|
|
32
|
+
<h1 class="text-2xl font-bold tracking-tight">Items</h1>
|
|
33
|
+
<p class="text-muted-foreground">Manage your items.</p>
|
|
34
|
+
</div>
|
|
35
|
+
<Button><Plus class="mr-2 h-4 w-4" /> New Item</Button>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<!-- Filters Bar -->
|
|
39
|
+
<div class="flex items-center gap-4">
|
|
40
|
+
<div class="relative flex-1 max-w-sm">
|
|
41
|
+
<Search class="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
42
|
+
<Input v-model="search" class="pl-9" placeholder="Search items..." />
|
|
43
|
+
</div>
|
|
44
|
+
<Select v-model="filter">
|
|
45
|
+
<SelectTrigger class="w-[150px]">
|
|
46
|
+
<SelectValue placeholder="Filter" />
|
|
47
|
+
</SelectTrigger>
|
|
48
|
+
<SelectContent>
|
|
49
|
+
<SelectItem value="all">All</SelectItem>
|
|
50
|
+
<SelectItem value="active">Active</SelectItem>
|
|
51
|
+
<SelectItem value="archived">Archived</SelectItem>
|
|
52
|
+
</SelectContent>
|
|
53
|
+
</Select>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<!-- Loading State -->
|
|
57
|
+
<div v-if="loading" class="space-y-3">
|
|
58
|
+
<Skeleton v-for="i in 5" :key="i" class="h-16 w-full rounded-lg" />
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<!-- Empty State -->
|
|
62
|
+
<div v-else-if="items.length === 0" class="flex flex-col items-center justify-center py-12 text-center">
|
|
63
|
+
<div class="rounded-full bg-muted p-4 mb-4">
|
|
64
|
+
<Search class="h-8 w-8 text-muted-foreground" />
|
|
65
|
+
</div>
|
|
66
|
+
<h3 class="text-lg font-semibold">No items found</h3>
|
|
67
|
+
<p class="text-muted-foreground mt-1">Get started by creating your first item.</p>
|
|
68
|
+
<Button class="mt-4"><Plus class="mr-2 h-4 w-4" /> Create Item</Button>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<!-- Data State -->
|
|
72
|
+
<div v-else class="space-y-3">
|
|
73
|
+
<Card v-for="item in items" :key="item.id" class="hover:shadow-sm transition-shadow">
|
|
74
|
+
<CardContent class="flex items-center justify-between p-4">
|
|
75
|
+
<div class="flex items-center gap-3">
|
|
76
|
+
<div>
|
|
77
|
+
<p class="font-medium">{{ item.name }}</p>
|
|
78
|
+
<p class="text-sm text-muted-foreground">{{ item.description }}</p>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
<div class="flex items-center gap-2">
|
|
82
|
+
<Badge>{{ item.status }}</Badge>
|
|
83
|
+
<DropdownMenu>
|
|
84
|
+
<DropdownMenuTrigger as-child>
|
|
85
|
+
<Button variant="ghost" size="icon">
|
|
86
|
+
<MoreHorizontal class="h-4 w-4" />
|
|
87
|
+
</Button>
|
|
88
|
+
</DropdownMenuTrigger>
|
|
89
|
+
<DropdownMenuContent align="end">
|
|
90
|
+
<DropdownMenuItem>Edit</DropdownMenuItem>
|
|
91
|
+
<DropdownMenuItem class="text-destructive">Delete</DropdownMenuItem>
|
|
92
|
+
</DropdownMenuContent>
|
|
93
|
+
</DropdownMenu>
|
|
94
|
+
</div>
|
|
95
|
+
</CardContent>
|
|
96
|
+
</Card>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
</template>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Key Patterns
|
|
103
|
+
- **Header**: Title + description + primary CTA
|
|
104
|
+
- **Filters**: Search + dropdown filters in a row
|
|
105
|
+
- **Three states**: Loading (skeletons), empty, data
|
|
106
|
+
- **Item cards**: Name + description + badge + action menu
|
|
107
|
+
- **Empty state**: Icon + message + CTA
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Settings Page Layout
|
|
2
|
+
|
|
3
|
+
## When to Use
|
|
4
|
+
- User preferences
|
|
5
|
+
- App configuration
|
|
6
|
+
- Account management
|
|
7
|
+
|
|
8
|
+
## Skeleton Code
|
|
9
|
+
|
|
10
|
+
```vue
|
|
11
|
+
<script setup>
|
|
12
|
+
import {
|
|
13
|
+
Button, Input, Label, Separator, Switch, Textarea,
|
|
14
|
+
Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,
|
|
15
|
+
} from '@reinvented/design'
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<template>
|
|
19
|
+
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
|
20
|
+
<div class="flex flex-col md:flex-row gap-8">
|
|
21
|
+
|
|
22
|
+
<!-- Sidebar Nav -->
|
|
23
|
+
<nav class="w-full md:w-48 space-y-1">
|
|
24
|
+
<a v-for="section in sections" :key="section.id"
|
|
25
|
+
:href="`#${section.id}`"
|
|
26
|
+
class="block rounded-md px-3 py-2 text-sm font-medium text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
27
|
+
:class="{ 'bg-muted text-foreground': activeSection === section.id }">
|
|
28
|
+
{{ section.label }}
|
|
29
|
+
</a>
|
|
30
|
+
</nav>
|
|
31
|
+
|
|
32
|
+
<!-- Settings Content -->
|
|
33
|
+
<div class="flex-1 space-y-6 max-w-2xl">
|
|
34
|
+
<Card id="profile">
|
|
35
|
+
<CardHeader>
|
|
36
|
+
<CardTitle>Profile</CardTitle>
|
|
37
|
+
<CardDescription>Update your personal information.</CardDescription>
|
|
38
|
+
</CardHeader>
|
|
39
|
+
<CardContent class="space-y-4">
|
|
40
|
+
<div class="space-y-2">
|
|
41
|
+
<Label for="name">Name</Label>
|
|
42
|
+
<Input id="name" placeholder="Your name" />
|
|
43
|
+
</div>
|
|
44
|
+
<div class="space-y-2">
|
|
45
|
+
<Label for="bio">Bio</Label>
|
|
46
|
+
<Textarea id="bio" placeholder="About you" />
|
|
47
|
+
</div>
|
|
48
|
+
</CardContent>
|
|
49
|
+
<CardFooter>
|
|
50
|
+
<Button>Save Changes</Button>
|
|
51
|
+
</CardFooter>
|
|
52
|
+
</Card>
|
|
53
|
+
|
|
54
|
+
<Card id="notifications">
|
|
55
|
+
<CardHeader>
|
|
56
|
+
<CardTitle>Notifications</CardTitle>
|
|
57
|
+
<CardDescription>Choose what notifications you receive.</CardDescription>
|
|
58
|
+
</CardHeader>
|
|
59
|
+
<CardContent class="space-y-4">
|
|
60
|
+
<div class="flex items-center justify-between">
|
|
61
|
+
<div>
|
|
62
|
+
<p class="text-sm font-medium">Email notifications</p>
|
|
63
|
+
<p class="text-sm text-muted-foreground">Receive updates via email.</p>
|
|
64
|
+
</div>
|
|
65
|
+
<Switch />
|
|
66
|
+
</div>
|
|
67
|
+
</CardContent>
|
|
68
|
+
</Card>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
</template>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Key Patterns
|
|
76
|
+
- **Sidebar nav** on left, content on right
|
|
77
|
+
- **Each section** is a Card with header + form fields + save button
|
|
78
|
+
- **Switch rows**: Label + description on left, Switch on right
|
|
79
|
+
- **Mobile**: sidebar stacks above content
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Step Wizard Layout
|
|
2
|
+
|
|
3
|
+
## When to Use
|
|
4
|
+
- Multi-step processes (onboarding, app creation, checkout)
|
|
5
|
+
- 3–7 steps where each depends on the previous
|
|
6
|
+
|
|
7
|
+
## Skeleton Code
|
|
8
|
+
|
|
9
|
+
```vue
|
|
10
|
+
<script setup>
|
|
11
|
+
import { ref, computed } from 'vue'
|
|
12
|
+
import { Button, Card, CardContent, CardFooter, CardHeader, CardTitle, Progress } from '@reinvented/design'
|
|
13
|
+
import { Check } from 'lucide-vue-next'
|
|
14
|
+
|
|
15
|
+
const currentStep = ref(0)
|
|
16
|
+
const steps = [
|
|
17
|
+
{ title: 'Details', component: 'StepDetails' },
|
|
18
|
+
{ title: 'Configuration', component: 'StepConfig' },
|
|
19
|
+
{ title: 'Review', component: 'StepReview' },
|
|
20
|
+
]
|
|
21
|
+
const progress = computed(() => ((currentStep.value + 1) / steps.length) * 100)
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<template>
|
|
25
|
+
<div class="max-w-2xl mx-auto px-4 py-8 space-y-8">
|
|
26
|
+
|
|
27
|
+
<!-- Step Indicator -->
|
|
28
|
+
<div class="space-y-4">
|
|
29
|
+
<Progress :model-value="progress" />
|
|
30
|
+
<div class="flex justify-between">
|
|
31
|
+
<div v-for="(step, i) in steps" :key="i" class="flex items-center gap-2 text-sm">
|
|
32
|
+
<div
|
|
33
|
+
class="flex h-6 w-6 items-center justify-center rounded-full text-xs font-medium"
|
|
34
|
+
:class="i <= currentStep ? 'bg-primary text-primary-foreground' : 'bg-muted text-muted-foreground'"
|
|
35
|
+
>
|
|
36
|
+
<Check v-if="i < currentStep" class="h-3 w-3" />
|
|
37
|
+
<span v-else>{{ i + 1 }}</span>
|
|
38
|
+
</div>
|
|
39
|
+
<span :class="i <= currentStep ? 'text-foreground' : 'text-muted-foreground'">{{ step.title }}</span>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<!-- Step Content -->
|
|
45
|
+
<Card>
|
|
46
|
+
<CardHeader>
|
|
47
|
+
<CardTitle>{{ steps[currentStep].title }}</CardTitle>
|
|
48
|
+
</CardHeader>
|
|
49
|
+
<CardContent>
|
|
50
|
+
<component :is="steps[currentStep].component" />
|
|
51
|
+
</CardContent>
|
|
52
|
+
<CardFooter class="flex justify-between">
|
|
53
|
+
<Button variant="outline" :disabled="currentStep === 0" @click="currentStep--">
|
|
54
|
+
Previous
|
|
55
|
+
</Button>
|
|
56
|
+
<Button v-if="currentStep < steps.length - 1" @click="currentStep++">
|
|
57
|
+
Next
|
|
58
|
+
</Button>
|
|
59
|
+
<Button v-else @click="submit">
|
|
60
|
+
Complete
|
|
61
|
+
</Button>
|
|
62
|
+
</CardFooter>
|
|
63
|
+
</Card>
|
|
64
|
+
</div>
|
|
65
|
+
</template>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Key Patterns
|
|
69
|
+
- **Progress bar** + step circles at top
|
|
70
|
+
- **Single card** with dynamic content
|
|
71
|
+
- **Previous/Next** buttons in card footer
|
|
72
|
+
- **Narrower max-width** than other layouts (`max-w-2xl`)
|
|
73
|
+
- Completed steps show ✓, current step highlighted
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Patterns Index
|
|
2
|
+
|
|
3
|
+
Reusable UI patterns — compositions of DS components for common use cases. Each pattern has a dedicated file with skeleton code.
|
|
4
|
+
|
|
5
|
+
This directory grows via harmonization: when agents discover patterns used across multiple apps, they're promoted here.
|
|
6
|
+
|
|
7
|
+
## Available Patterns
|
|
8
|
+
|
|
9
|
+
*No patterns promoted yet. Agents should propose new patterns via PRs to this repo.*
|
|
10
|
+
|
|
11
|
+
## How Patterns Get Added
|
|
12
|
+
|
|
13
|
+
1. Agent builds an app and identifies a reusable UI pattern
|
|
14
|
+
2. Agent opens a PR to this repo with a new `patterns/<name>.md` file
|
|
15
|
+
3. Human or harmonization agent reviews
|
|
16
|
+
4. On merge, all future agent runs benefit from the pattern
|
|
17
|
+
|
|
18
|
+
## Pattern File Format
|
|
19
|
+
|
|
20
|
+
Each pattern file should include:
|
|
21
|
+
|
|
22
|
+
```markdown
|
|
23
|
+
# Pattern: [Name]
|
|
24
|
+
|
|
25
|
+
## When to Use
|
|
26
|
+
[Describe the use case]
|
|
27
|
+
|
|
28
|
+
## Skeleton Code
|
|
29
|
+
[Working Vue code with TODO comments for app-specific parts]
|
|
30
|
+
|
|
31
|
+
## Props / Configuration
|
|
32
|
+
[What's customizable]
|
|
33
|
+
|
|
34
|
+
## Related Components
|
|
35
|
+
[Which DS components this pattern uses]
|
|
36
|
+
|
|
37
|
+
## Apps Using This
|
|
38
|
+
[List of apps where this pattern was first used or is in use]
|
|
39
|
+
```
|
package/docs/rules.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Design Rules
|
|
2
|
+
|
|
3
|
+
Non-negotiable rules for all Reinvented apps. These are enforced by linting where possible.
|
|
4
|
+
|
|
5
|
+
## Components
|
|
6
|
+
|
|
7
|
+
1. **No raw `<button>`** — use `<Button>` from `@reinvented/design`
|
|
8
|
+
2. **No raw `<input>`** — use `<Input>` from `@reinvented/design`
|
|
9
|
+
3. **No raw `<select>`** — use `<Select>` from `@reinvented/design`
|
|
10
|
+
4. **No raw `<textarea>`** — use `<Textarea>` from `@reinvented/design`
|
|
11
|
+
5. **No `alert()`, `confirm()`, `prompt()`** — use `<Dialog>` / `<AlertDialog>` / `<Toast>`
|
|
12
|
+
|
|
13
|
+
## Styling
|
|
14
|
+
|
|
15
|
+
6. **No hardcoded colors** — use CSS variables (`bg-background`, `text-foreground`, `bg-primary`, etc.)
|
|
16
|
+
7. **No arbitrary Tailwind values** — no `text-[#333]` or `p-[13px]`. Use only token-based utilities
|
|
17
|
+
8. **No inline styles** — all styling through Tailwind utilities and DS component props
|
|
18
|
+
9. **No `@apply` blocks** — prefer utility classes in templates
|
|
19
|
+
|
|
20
|
+
## Icons & Media
|
|
21
|
+
|
|
22
|
+
10. **No emojis** — use Lucide icons exclusively via `lucide-vue-next`
|
|
23
|
+
11. **Default icon size**: `w-4 h-4` in buttons/nav, `w-12 h-12` in empty states
|
|
24
|
+
|
|
25
|
+
## Layout
|
|
26
|
+
|
|
27
|
+
12. **Modals for create/edit by default** — full pages only for forms with 8+ fields or rich content editing
|
|
28
|
+
13. **Single-column responsive** — no split-screen layouts
|
|
29
|
+
14. **Mobile-first** — no horizontal scrolling, minimum 44px touch targets
|
|
30
|
+
15. **No custom scrollbars** — use `<ScrollArea>` from DS
|
|
31
|
+
|
|
32
|
+
## States
|
|
33
|
+
|
|
34
|
+
16. **Every view must have**: loading state, empty state, error state, data state
|
|
35
|
+
17. **Every mutation button**: shows spinner during request, disabled while loading
|
|
36
|
+
18. **Every list**: has empty state with descriptive text and CTA
|
|
37
|
+
|
|
38
|
+
## Data
|
|
39
|
+
|
|
40
|
+
19. **No `SELECT *`** — explicitly list columns
|
|
41
|
+
20. **Parameterized queries only** — no string interpolation for user input
|
|
42
|
+
21. **UUID primary keys** everywhere
|
|
43
|
+
22. **RLS on every table** — no exceptions
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Visual Polish Guide
|
|
2
|
+
|
|
3
|
+
Standards for animations, transitions, loading patterns, and interaction polish.
|
|
4
|
+
|
|
5
|
+
## Transition Durations
|
|
6
|
+
|
|
7
|
+
| Duration | Use |
|
|
8
|
+
|----------|-----|
|
|
9
|
+
| `duration-150` (150ms) | Hover states, color changes, opacity |
|
|
10
|
+
| `duration-200` (200ms) | Modals open/close, tooltips, popovers |
|
|
11
|
+
| `duration-300` (300ms) | Page transitions, slide-ins, expanded sections |
|
|
12
|
+
|
|
13
|
+
**Easing**: Use `ease-out` for entrances, `ease-in` for exits, `ease-in-out` for state changes.
|
|
14
|
+
|
|
15
|
+
## Modal Animations
|
|
16
|
+
|
|
17
|
+
```vue
|
|
18
|
+
<!-- Dialog with fade + scale -->
|
|
19
|
+
<DialogContent class="
|
|
20
|
+
data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95
|
|
21
|
+
data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95
|
|
22
|
+
duration-200
|
|
23
|
+
">
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## List Item Animations
|
|
27
|
+
|
|
28
|
+
```vue
|
|
29
|
+
<TransitionGroup name="list" tag="div">
|
|
30
|
+
<Card v-for="item in items" :key="item.id">
|
|
31
|
+
<!-- content -->
|
|
32
|
+
</Card>
|
|
33
|
+
</TransitionGroup>
|
|
34
|
+
|
|
35
|
+
<style>
|
|
36
|
+
.list-enter-active { transition: all 0.3s ease-out; }
|
|
37
|
+
.list-leave-active { transition: all 0.2s ease-in; }
|
|
38
|
+
.list-enter-from { opacity: 0; transform: translateY(8px); }
|
|
39
|
+
.list-leave-to { opacity: 0; transform: translateX(-16px); }
|
|
40
|
+
.list-move { transition: transform 0.3s ease; }
|
|
41
|
+
</style>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Button Loading State
|
|
45
|
+
|
|
46
|
+
```vue
|
|
47
|
+
<script setup>
|
|
48
|
+
import { ref } from 'vue'
|
|
49
|
+
import { Button } from '@reinvented/design'
|
|
50
|
+
import { Loader2 } from 'lucide-vue-next'
|
|
51
|
+
|
|
52
|
+
const saving = ref(false)
|
|
53
|
+
async function handleSave() {
|
|
54
|
+
saving.value = true
|
|
55
|
+
try {
|
|
56
|
+
await saveMutation()
|
|
57
|
+
} finally {
|
|
58
|
+
saving.value = false
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
<template>
|
|
64
|
+
<Button :disabled="saving" @click="handleSave">
|
|
65
|
+
<Loader2 v-if="saving" class="w-4 h-4 mr-2 animate-spin" />
|
|
66
|
+
{{ saving ? 'Saving...' : 'Save' }}
|
|
67
|
+
</Button>
|
|
68
|
+
</template>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Skeleton Loading
|
|
72
|
+
|
|
73
|
+
Skeletons must match the shape of the actual content:
|
|
74
|
+
|
|
75
|
+
```vue
|
|
76
|
+
<!-- Skeleton for a card list -->
|
|
77
|
+
<div class="space-y-4">
|
|
78
|
+
<div v-for="i in 3" :key="i" class="p-4 border rounded-lg">
|
|
79
|
+
<div class="flex items-center gap-3">
|
|
80
|
+
<Skeleton class="w-10 h-10 rounded-full" />
|
|
81
|
+
<div class="flex-1 space-y-2">
|
|
82
|
+
<Skeleton class="h-4 w-3/4" />
|
|
83
|
+
<Skeleton class="h-3 w-1/2" />
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Toast Notifications
|
|
91
|
+
|
|
92
|
+
```vue
|
|
93
|
+
<script setup>
|
|
94
|
+
import { toast } from '@reinvented/design'
|
|
95
|
+
|
|
96
|
+
// Success
|
|
97
|
+
toast.success('Task created')
|
|
98
|
+
|
|
99
|
+
// Error with action
|
|
100
|
+
toast.error('Failed to save', {
|
|
101
|
+
action: { label: 'Retry', onClick: () => handleSave() }
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
// Promise-based (auto shows loading → success/error)
|
|
105
|
+
toast.promise(saveMutation(), {
|
|
106
|
+
loading: 'Saving...',
|
|
107
|
+
success: 'Saved!',
|
|
108
|
+
error: 'Failed to save'
|
|
109
|
+
})
|
|
110
|
+
</script>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Focus Management
|
|
114
|
+
|
|
115
|
+
- **Modal open**: Focus first interactive element inside the modal
|
|
116
|
+
- **Modal close**: Return focus to the trigger element
|
|
117
|
+
- **After delete**: Focus the next item in the list (or empty state)
|
|
118
|
+
- **After create**: Focus the newly created item (or close modal and show toast)
|
|
119
|
+
- **Tab order**: Logical reading order, top-to-bottom, left-to-right
|
|
120
|
+
|
|
121
|
+
## Hover Effects
|
|
122
|
+
|
|
123
|
+
```vue
|
|
124
|
+
<!-- Card with hover lift -->
|
|
125
|
+
<Card class="transition-shadow duration-150 hover:shadow-md cursor-pointer">
|
|
126
|
+
|
|
127
|
+
<!-- Button with subtle scale on press -->
|
|
128
|
+
<Button class="active:scale-[0.98] transition-transform duration-150">
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Optimistic Updates
|
|
132
|
+
|
|
133
|
+
Use for:
|
|
134
|
+
- Toggles (like/unlike, pin/unpin)
|
|
135
|
+
- Status changes (mark complete)
|
|
136
|
+
- Reordering (drag and drop)
|
|
137
|
+
|
|
138
|
+
Do NOT use for:
|
|
139
|
+
- Creation (need server-generated ID)
|
|
140
|
+
- Deletion (hard to undo if server rejects)
|
|
141
|
+
- Complex mutations (multiple side effects)
|