@coston/ui 0.1.1 → 0.2.2
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 +75 -10
- package/dist/{button-CJvoztqg.js → button-BJdMtkHX.js} +3 -3
- package/dist/button-BJdMtkHX.js.map +1 -0
- package/dist/{button-QxkOGnNm.d.ts → button-CyD56dqi.d.ts} +5 -5
- package/dist/{button-QxkOGnNm.d.ts.map → button-CyD56dqi.d.ts.map} +1 -1
- package/dist/components/accordion.d.ts.map +1 -1
- package/dist/components/accordion.js +4 -4
- package/dist/components/accordion.js.map +1 -1
- package/dist/components/alert-dialog.d.ts +3 -3
- package/dist/components/alert-dialog.d.ts.map +1 -1
- package/dist/components/alert-dialog.js +5 -5
- package/dist/components/alert-dialog.js.map +1 -1
- package/dist/components/alert.d.ts +2 -2
- package/dist/components/alert.js +3 -3
- package/dist/components/alert.js.map +1 -1
- package/dist/components/avatar.d.ts.map +1 -1
- package/dist/components/avatar.js +2 -2
- package/dist/components/avatar.js.map +1 -1
- package/dist/components/badge.d.ts +5 -5
- package/dist/components/badge.d.ts.map +1 -1
- package/dist/components/badge.js +4 -1
- package/dist/components/badge.js.map +1 -1
- package/dist/components/breadcrumb.d.ts +3 -3
- package/dist/components/breadcrumb.js +1 -1
- package/dist/components/button.d.ts +1 -1
- package/dist/components/button.js +2 -2
- package/dist/components/calendar.d.ts +2 -2
- package/dist/components/calendar.d.ts.map +1 -1
- package/dist/components/calendar.js +1 -1
- package/dist/components/card.d.ts +4 -1
- package/dist/components/card.d.ts.map +1 -1
- package/dist/components/card.js +5 -5
- package/dist/components/card.js.map +1 -1
- package/dist/components/carousel.d.ts +1 -1
- package/dist/components/carousel.js +2 -2
- package/dist/components/chart.d.ts +1 -1
- package/dist/components/chart.d.ts.map +1 -1
- package/dist/components/chart.js +1 -1
- package/dist/components/checkbox.js +3 -3
- package/dist/components/checkbox.js.map +1 -1
- package/dist/components/command.d.ts +75 -10
- package/dist/components/command.d.ts.map +1 -1
- package/dist/components/command.js +3 -3
- package/dist/components/command.js.map +1 -1
- package/dist/components/context-menu.d.ts +2 -2
- package/dist/components/context-menu.d.ts.map +1 -1
- package/dist/components/context-menu.js +3 -3
- package/dist/components/context-menu.js.map +1 -1
- package/dist/components/dialog.d.ts +3 -3
- package/dist/components/dialog.js +2 -2
- package/dist/components/drawer.d.ts +13 -11
- package/dist/components/drawer.d.ts.map +1 -1
- package/dist/components/drawer.js +2 -2
- package/dist/components/drawer.js.map +1 -1
- package/dist/components/dropdown-menu.d.ts +2 -2
- package/dist/components/dropdown-menu.d.ts.map +1 -1
- package/dist/components/dropdown-menu.js +3 -3
- package/dist/components/dropdown-menu.js.map +1 -1
- package/dist/components/hover-card.js +2 -2
- package/dist/components/hover-card.js.map +1 -1
- package/dist/components/input.js +1 -1
- package/dist/components/label.d.ts +2 -2
- package/dist/components/label.js +1 -1
- package/dist/components/menubar.d.ts +3 -3
- package/dist/components/menubar.js +4 -4
- package/dist/components/menubar.js.map +1 -1
- package/dist/components/navigation-menu.d.ts +2 -2
- package/dist/components/navigation-menu.js +2 -2
- package/dist/components/navigation-menu.js.map +1 -1
- package/dist/components/pagination.d.ts +7 -7
- package/dist/components/pagination.js +2 -2
- package/dist/components/popover.js +2 -2
- package/dist/components/popover.js.map +1 -1
- package/dist/components/progress.js +2 -2
- package/dist/components/progress.js.map +1 -1
- package/dist/components/radio-group.js +1 -1
- package/dist/components/resizable.d.ts +5 -5
- package/dist/components/resizable.d.ts.map +1 -1
- package/dist/components/resizable.js +4 -4
- package/dist/components/resizable.js.map +1 -1
- package/dist/components/scroll-area.js +1 -1
- package/dist/components/select.js +2 -2
- package/dist/components/select.js.map +1 -1
- package/dist/components/separator.js +2 -2
- package/dist/components/sheet.d.ts +6 -6
- package/dist/components/sheet.d.ts.map +1 -1
- package/dist/components/sheet.js +8 -8
- package/dist/components/sheet.js.map +1 -1
- package/dist/components/sidebar.d.ts +1 -1
- package/dist/components/sidebar.js +5 -5
- package/dist/components/sidebar.js.map +1 -1
- package/dist/components/skeleton.d.ts +2 -2
- package/dist/components/skeleton.js +1 -1
- package/dist/components/slider.js +3 -3
- package/dist/components/slider.js.map +1 -1
- package/dist/components/sonner.d.ts +2 -2
- package/dist/components/sonner.d.ts.map +1 -1
- package/dist/components/switch.js +3 -3
- package/dist/components/switch.js.map +1 -1
- package/dist/components/table.d.ts.map +1 -1
- package/dist/components/table.js +2 -2
- package/dist/components/table.js.map +1 -1
- package/dist/components/tabs.js +2 -2
- package/dist/components/tabs.js.map +1 -1
- package/dist/components/textarea.js +1 -1
- package/dist/components/toggle-group.d.ts +3 -3
- package/dist/components/toggle-group.js +2 -2
- package/dist/components/toggle.d.ts +3 -3
- package/dist/components/toggle.js +2 -2
- package/dist/components/tooltip.js +2 -2
- package/dist/{dialog-B_8jhMOs.js → dialog-COSqOqbH.js} +6 -6
- package/dist/dialog-COSqOqbH.js.map +1 -0
- package/dist/lib/utils.d.ts +2 -1
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +2 -2
- package/dist/{separator-9lzFsfCC.js → separator-C3mBJ8ik.js} +2 -2
- package/dist/{separator-9lzFsfCC.js.map → separator-C3mBJ8ik.js.map} +1 -1
- package/dist/{toggle-BpKiTVe4.js → toggle-CD9xIRlr.js} +3 -3
- package/dist/toggle-CD9xIRlr.js.map +1 -0
- package/dist/{tooltip-CXS6wR7g.js → tooltip-PxI7r4yP.js} +3 -3
- package/dist/tooltip-PxI7r4yP.js.map +1 -0
- package/dist/{utils-C0f9Ma6r.js → utils-CyPJ3VV3.js} +3 -2
- package/dist/utils-CyPJ3VV3.js.map +1 -0
- package/llms.txt +989 -0
- package/package.json +52 -14
- package/source.css +40 -0
- package/dist/button-CJvoztqg.js.map +0 -1
- package/dist/dialog-B_8jhMOs.js.map +0 -1
- package/dist/toggle-BpKiTVe4.js.map +0 -1
- package/dist/tooltip-CXS6wR7g.js.map +0 -1
- package/dist/utils-C0f9Ma6r.js.map +0 -1
- package/src/components/accordion.tsx +0 -51
- package/src/components/alert-dialog.tsx +0 -115
- package/src/components/alert.tsx +0 -49
- package/src/components/aspect-ratio.tsx +0 -5
- package/src/components/avatar.tsx +0 -44
- package/src/components/badge.tsx +0 -32
- package/src/components/breadcrumb.tsx +0 -101
- package/src/components/button.tsx +0 -48
- package/src/components/calendar.tsx +0 -76
- package/src/components/card.tsx +0 -60
- package/src/components/carousel.tsx +0 -240
- package/src/components/chart.tsx +0 -232
- package/src/components/checkbox.tsx +0 -25
- package/src/components/collapsible.tsx +0 -11
- package/src/components/command.tsx +0 -136
- package/src/components/context-menu.tsx +0 -187
- package/src/components/dialog.tsx +0 -105
- package/src/components/drawer.tsx +0 -98
- package/src/components/dropdown-menu.tsx +0 -185
- package/src/components/hover-card.tsx +0 -29
- package/src/components/input.tsx +0 -24
- package/src/components/label.tsx +0 -19
- package/src/components/menubar.tsx +0 -219
- package/src/components/navigation-menu.tsx +0 -120
- package/src/components/pagination.tsx +0 -98
- package/src/components/popover.tsx +0 -27
- package/src/components/progress.tsx +0 -23
- package/src/components/radio-group.tsx +0 -35
- package/src/components/resizable.tsx +0 -38
- package/src/components/scroll-area.tsx +0 -43
- package/src/components/select.tsx +0 -147
- package/src/components/separator.tsx +0 -23
- package/src/components/sheet.tsx +0 -122
- package/src/components/sidebar.tsx +0 -507
- package/src/components/skeleton.tsx +0 -7
- package/src/components/slider.tsx +0 -23
- package/src/components/sonner.tsx +0 -26
- package/src/components/switch.tsx +0 -26
- package/src/components/table.tsx +0 -95
- package/src/components/tabs.tsx +0 -53
- package/src/components/textarea.tsx +0 -20
- package/src/components/toggle-group.tsx +0 -54
- package/src/components/toggle.tsx +0 -40
- package/src/components/tooltip.tsx +0 -27
- package/src/hooks/use-mobile.tsx +0 -19
- package/src/lib/utils.ts +0 -6
package/llms.txt
ADDED
|
@@ -0,0 +1,989 @@
|
|
|
1
|
+
# @coston/ui
|
|
2
|
+
|
|
3
|
+
> React UI component library built on Radix UI and Tailwind CSS.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
### Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @coston/ui @coston/design-tokens
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Peer dependencies: `react`, `react-dom`
|
|
14
|
+
|
|
15
|
+
### CSS (required — components render unstyled without this)
|
|
16
|
+
|
|
17
|
+
Add to your root CSS file:
|
|
18
|
+
|
|
19
|
+
```css
|
|
20
|
+
@import 'tailwindcss';
|
|
21
|
+
@import '@coston/design-tokens/tailwind.css';
|
|
22
|
+
@import '@coston/ui/source.css';
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Import pattern
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
// Each component is a named export from a separate entry point
|
|
29
|
+
import { Button } from '@coston/ui/button';
|
|
30
|
+
import { Card, CardHeader, CardTitle, CardContent } from '@coston/ui/card';
|
|
31
|
+
import { cn } from '@coston/ui/lib/utils';
|
|
32
|
+
import { useIsMobile } from '@coston/ui/hooks/use-mobile';
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Do NOT
|
|
36
|
+
|
|
37
|
+
- Do not import from the package root: `import { Button } from '@coston/ui'` — there is no root export
|
|
38
|
+
- Do not use default imports: `import Button from '@coston/ui/button'` — all exports are named
|
|
39
|
+
- Do not skip the CSS setup — components will render without styles
|
|
40
|
+
- Do not import `@coston/ui/source.css` without also importing `@coston/design-tokens/tailwind.css`
|
|
41
|
+
|
|
42
|
+
## Optional peer dependencies
|
|
43
|
+
|
|
44
|
+
| Component | Package |
|
|
45
|
+
| --------- | ------- |
|
|
46
|
+
| Chart | `recharts` |
|
|
47
|
+
| Carousel | `embla-carousel-react` |
|
|
48
|
+
| Sonner | `sonner` |
|
|
49
|
+
| Drawer | `vaul` |
|
|
50
|
+
| Command | `cmdk` |
|
|
51
|
+
| Resizable | `react-resizable-panels` |
|
|
52
|
+
| Calendar | `react-day-picker`, `date-fns` |
|
|
53
|
+
|
|
54
|
+
## Components and exports
|
|
55
|
+
|
|
56
|
+
### accordion
|
|
57
|
+
Import: `@coston/ui/accordion`
|
|
58
|
+
Exports: Accordion, AccordionItem, AccordionTrigger, AccordionContent
|
|
59
|
+
|
|
60
|
+
### alert-dialog
|
|
61
|
+
Import: `@coston/ui/alert-dialog`
|
|
62
|
+
Exports: AlertDialog, AlertDialogPortal, AlertDialogOverlay, AlertDialogTrigger, AlertDialogContent, AlertDialogHeader, AlertDialogFooter, AlertDialogTitle, AlertDialogDescription, AlertDialogAction, AlertDialogCancel
|
|
63
|
+
|
|
64
|
+
### alert
|
|
65
|
+
Import: `@coston/ui/alert`
|
|
66
|
+
Exports: Alert, AlertTitle, AlertDescription
|
|
67
|
+
|
|
68
|
+
### aspect-ratio
|
|
69
|
+
Import: `@coston/ui/aspect-ratio`
|
|
70
|
+
Exports: AspectRatio
|
|
71
|
+
|
|
72
|
+
### avatar
|
|
73
|
+
Import: `@coston/ui/avatar`
|
|
74
|
+
Exports: Avatar, AvatarImage, AvatarFallback
|
|
75
|
+
|
|
76
|
+
### badge
|
|
77
|
+
Import: `@coston/ui/badge`
|
|
78
|
+
Exports: Badge, badgeVariants
|
|
79
|
+
|
|
80
|
+
### breadcrumb
|
|
81
|
+
Import: `@coston/ui/breadcrumb`
|
|
82
|
+
Exports: Breadcrumb, BreadcrumbList, BreadcrumbItem, BreadcrumbLink, BreadcrumbPage, BreadcrumbSeparator, BreadcrumbEllipsis
|
|
83
|
+
|
|
84
|
+
### button
|
|
85
|
+
Import: `@coston/ui/button`
|
|
86
|
+
Exports: Button, buttonVariants
|
|
87
|
+
|
|
88
|
+
### calendar
|
|
89
|
+
Import: `@coston/ui/calendar`
|
|
90
|
+
Exports: Calendar
|
|
91
|
+
|
|
92
|
+
### card
|
|
93
|
+
Import: `@coston/ui/card`
|
|
94
|
+
Exports: Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent
|
|
95
|
+
|
|
96
|
+
### carousel
|
|
97
|
+
Import: `@coston/ui/carousel`
|
|
98
|
+
Exports: CarouselApi, Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext
|
|
99
|
+
|
|
100
|
+
### chart
|
|
101
|
+
Import: `@coston/ui/chart`
|
|
102
|
+
Exports: ChartContainer, ChartTooltip, ChartTooltipContent, ChartConfig
|
|
103
|
+
|
|
104
|
+
### checkbox
|
|
105
|
+
Import: `@coston/ui/checkbox`
|
|
106
|
+
Exports: Checkbox
|
|
107
|
+
|
|
108
|
+
### collapsible
|
|
109
|
+
Import: `@coston/ui/collapsible`
|
|
110
|
+
Exports: Collapsible, CollapsibleTrigger, CollapsibleContent
|
|
111
|
+
|
|
112
|
+
### command
|
|
113
|
+
Import: `@coston/ui/command`
|
|
114
|
+
Exports: Command, CommandDialog, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, CommandSeparator, CommandShortcut
|
|
115
|
+
|
|
116
|
+
### context-menu
|
|
117
|
+
Import: `@coston/ui/context-menu`
|
|
118
|
+
Exports: ContextMenu, ContextMenuTrigger, ContextMenuContent, ContextMenuItem, ContextMenuCheckboxItem, ContextMenuRadioItem, ContextMenuLabel, ContextMenuSeparator, ContextMenuShortcut, ContextMenuGroup, ContextMenuPortal, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuRadioGroup
|
|
119
|
+
|
|
120
|
+
### dialog
|
|
121
|
+
Import: `@coston/ui/dialog`
|
|
122
|
+
Exports: Dialog, DialogPortal, DialogOverlay, DialogClose, DialogTrigger, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription
|
|
123
|
+
|
|
124
|
+
### drawer
|
|
125
|
+
Import: `@coston/ui/drawer`
|
|
126
|
+
Exports: Drawer, DrawerPortal, DrawerOverlay, DrawerTrigger, DrawerClose, DrawerContent, DrawerHeader, DrawerFooter, DrawerTitle, DrawerDescription
|
|
127
|
+
|
|
128
|
+
### dropdown-menu
|
|
129
|
+
Import: `@coston/ui/dropdown-menu`
|
|
130
|
+
Exports: DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuGroup, DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuRadioGroup
|
|
131
|
+
|
|
132
|
+
### hover-card
|
|
133
|
+
Import: `@coston/ui/hover-card`
|
|
134
|
+
Exports: HoverCard, HoverCardTrigger, HoverCardContent
|
|
135
|
+
|
|
136
|
+
### input
|
|
137
|
+
Import: `@coston/ui/input`
|
|
138
|
+
Exports: Input
|
|
139
|
+
|
|
140
|
+
### label
|
|
141
|
+
Import: `@coston/ui/label`
|
|
142
|
+
Exports: Label
|
|
143
|
+
|
|
144
|
+
### menubar
|
|
145
|
+
Import: `@coston/ui/menubar`
|
|
146
|
+
Exports: Menubar, MenubarMenu, MenubarTrigger, MenubarContent, MenubarItem, MenubarSeparator, MenubarLabel, MenubarCheckboxItem, MenubarRadioGroup, MenubarRadioItem, MenubarPortal, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarGroup, MenubarShortcut
|
|
147
|
+
|
|
148
|
+
### navigation-menu
|
|
149
|
+
Import: `@coston/ui/navigation-menu`
|
|
150
|
+
Exports: navigationMenuTriggerStyle, NavigationMenu, NavigationMenuList, NavigationMenuItem, NavigationMenuContent, NavigationMenuTrigger, NavigationMenuLink, NavigationMenuIndicator, NavigationMenuViewport
|
|
151
|
+
|
|
152
|
+
### pagination
|
|
153
|
+
Import: `@coston/ui/pagination`
|
|
154
|
+
Exports: Pagination, PaginationContent, PaginationItem, PaginationLink, PaginationPrevious, PaginationNext, PaginationEllipsis
|
|
155
|
+
|
|
156
|
+
### popover
|
|
157
|
+
Import: `@coston/ui/popover`
|
|
158
|
+
Exports: Popover, PopoverTrigger, PopoverContent
|
|
159
|
+
|
|
160
|
+
### progress
|
|
161
|
+
Import: `@coston/ui/progress`
|
|
162
|
+
Exports: Progress
|
|
163
|
+
|
|
164
|
+
### radio-group
|
|
165
|
+
Import: `@coston/ui/radio-group`
|
|
166
|
+
Exports: RadioGroup, RadioGroupItem
|
|
167
|
+
|
|
168
|
+
### resizable
|
|
169
|
+
Import: `@coston/ui/resizable`
|
|
170
|
+
Exports: ResizablePanelGroup, ResizablePanel, ResizableHandle
|
|
171
|
+
|
|
172
|
+
### scroll-area
|
|
173
|
+
Import: `@coston/ui/scroll-area`
|
|
174
|
+
Exports: ScrollArea, ScrollBar
|
|
175
|
+
|
|
176
|
+
### select
|
|
177
|
+
Import: `@coston/ui/select`
|
|
178
|
+
Exports: Select, SelectGroup, SelectValue, SelectTrigger, SelectContent, SelectLabel, SelectItem, SelectSeparator, SelectScrollUpButton, SelectScrollDownButton
|
|
179
|
+
|
|
180
|
+
### separator
|
|
181
|
+
Import: `@coston/ui/separator`
|
|
182
|
+
Exports: Separator
|
|
183
|
+
|
|
184
|
+
### sheet
|
|
185
|
+
Import: `@coston/ui/sheet`
|
|
186
|
+
Exports: Sheet, SheetPortal, SheetOverlay, SheetTrigger, SheetClose, SheetContent, SheetHeader, SheetFooter, SheetTitle, SheetDescription
|
|
187
|
+
|
|
188
|
+
### sidebar
|
|
189
|
+
Import: `@coston/ui/sidebar`
|
|
190
|
+
Exports: Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuButton, SidebarMenuItem, SidebarProvider, SidebarSeparator, SidebarTrigger, useSidebar
|
|
191
|
+
|
|
192
|
+
### skeleton
|
|
193
|
+
Import: `@coston/ui/skeleton`
|
|
194
|
+
Exports: Skeleton
|
|
195
|
+
|
|
196
|
+
### slider
|
|
197
|
+
Import: `@coston/ui/slider`
|
|
198
|
+
Exports: Slider
|
|
199
|
+
|
|
200
|
+
### sonner
|
|
201
|
+
Import: `@coston/ui/sonner`
|
|
202
|
+
Exports: Toaster
|
|
203
|
+
|
|
204
|
+
### switch
|
|
205
|
+
Import: `@coston/ui/switch`
|
|
206
|
+
Exports: Switch
|
|
207
|
+
|
|
208
|
+
### table
|
|
209
|
+
Import: `@coston/ui/table`
|
|
210
|
+
Exports: Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption
|
|
211
|
+
|
|
212
|
+
### tabs
|
|
213
|
+
Import: `@coston/ui/tabs`
|
|
214
|
+
Exports: Tabs, TabsList, TabsTrigger, TabsContent
|
|
215
|
+
|
|
216
|
+
### textarea
|
|
217
|
+
Import: `@coston/ui/textarea`
|
|
218
|
+
Exports: Textarea
|
|
219
|
+
|
|
220
|
+
### toggle-group
|
|
221
|
+
Import: `@coston/ui/toggle-group`
|
|
222
|
+
Exports: ToggleGroup, ToggleGroupItem
|
|
223
|
+
|
|
224
|
+
### toggle
|
|
225
|
+
Import: `@coston/ui/toggle`
|
|
226
|
+
Exports: Toggle, toggleVariants
|
|
227
|
+
|
|
228
|
+
### tooltip
|
|
229
|
+
Import: `@coston/ui/tooltip`
|
|
230
|
+
Exports: Tooltip, TooltipTrigger, TooltipContent, TooltipProvider
|
|
231
|
+
|
|
232
|
+
### Utilities
|
|
233
|
+
|
|
234
|
+
- `cn(...classValues)` — `@coston/ui/lib/utils`
|
|
235
|
+
- `useIsMobile()` — `@coston/ui/hooks/use-mobile`
|
|
236
|
+
|
|
237
|
+
## Usage patterns
|
|
238
|
+
|
|
239
|
+
### confirmation flow
|
|
240
|
+
```tsx
|
|
241
|
+
import { useState } from 'react';
|
|
242
|
+
import { toast } from 'sonner';
|
|
243
|
+
import {
|
|
244
|
+
AlertDialog,
|
|
245
|
+
AlertDialogAction,
|
|
246
|
+
AlertDialogCancel,
|
|
247
|
+
AlertDialogContent,
|
|
248
|
+
AlertDialogDescription,
|
|
249
|
+
AlertDialogFooter,
|
|
250
|
+
AlertDialogHeader,
|
|
251
|
+
AlertDialogTitle,
|
|
252
|
+
AlertDialogTrigger,
|
|
253
|
+
} from '@coston/ui/alert-dialog';
|
|
254
|
+
import { Button } from '@coston/ui/button';
|
|
255
|
+
import { Input } from '@coston/ui/input';
|
|
256
|
+
import { Label } from '@coston/ui/label';
|
|
257
|
+
|
|
258
|
+
function DestructiveConfirm() {
|
|
259
|
+
return (
|
|
260
|
+
<AlertDialog>
|
|
261
|
+
<AlertDialogTrigger asChild>
|
|
262
|
+
<Button variant="destructive">Delete project</Button>
|
|
263
|
+
</AlertDialogTrigger>
|
|
264
|
+
<AlertDialogContent>
|
|
265
|
+
<AlertDialogHeader>
|
|
266
|
+
<AlertDialogTitle>Delete project?</AlertDialogTitle>
|
|
267
|
+
<AlertDialogDescription>
|
|
268
|
+
This action cannot be undone. All project data will be permanently removed.
|
|
269
|
+
</AlertDialogDescription>
|
|
270
|
+
</AlertDialogHeader>
|
|
271
|
+
<AlertDialogFooter>
|
|
272
|
+
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
273
|
+
<AlertDialogAction
|
|
274
|
+
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
|
275
|
+
onClick={() => toast.error('Project deleted')}
|
|
276
|
+
>
|
|
277
|
+
Delete
|
|
278
|
+
</AlertDialogAction>
|
|
279
|
+
</AlertDialogFooter>
|
|
280
|
+
</AlertDialogContent>
|
|
281
|
+
</AlertDialog>
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function TypeToConfirm() {
|
|
286
|
+
const [value, setValue] = useState('');
|
|
287
|
+
const confirmed = value === 'DELETE';
|
|
288
|
+
|
|
289
|
+
return (
|
|
290
|
+
<AlertDialog onOpenChange={() => setValue('')}>
|
|
291
|
+
<AlertDialogTrigger asChild>
|
|
292
|
+
<Button variant="outline">Delete account</Button>
|
|
293
|
+
</AlertDialogTrigger>
|
|
294
|
+
<AlertDialogContent>
|
|
295
|
+
<AlertDialogHeader>
|
|
296
|
+
<AlertDialogTitle>Delete your account?</AlertDialogTitle>
|
|
297
|
+
<AlertDialogDescription>
|
|
298
|
+
This permanently deletes your account and all associated data. Type{' '}
|
|
299
|
+
<strong>DELETE</strong> to confirm.
|
|
300
|
+
</AlertDialogDescription>
|
|
301
|
+
</AlertDialogHeader>
|
|
302
|
+
<div className="grid gap-2 py-2">
|
|
303
|
+
<Label htmlFor="confirm-input">Confirmation</Label>
|
|
304
|
+
<Input
|
|
305
|
+
id="confirm-input"
|
|
306
|
+
value={value}
|
|
307
|
+
onChange={e => setValue(e.target.value)}
|
|
308
|
+
placeholder="Type DELETE"
|
|
309
|
+
/>
|
|
310
|
+
</div>
|
|
311
|
+
<AlertDialogFooter>
|
|
312
|
+
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
313
|
+
<AlertDialogAction
|
|
314
|
+
disabled={!confirmed}
|
|
315
|
+
className="bg-destructive text-destructive-foreground hover:bg-destructive/90 disabled:opacity-50"
|
|
316
|
+
onClick={() => toast.success('Account deleted')}
|
|
317
|
+
>
|
|
318
|
+
Delete account
|
|
319
|
+
</AlertDialogAction>
|
|
320
|
+
</AlertDialogFooter>
|
|
321
|
+
</AlertDialogContent>
|
|
322
|
+
</AlertDialog>
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export function ConfirmationFlow() {
|
|
327
|
+
return (
|
|
328
|
+
<div className="flex flex-wrap gap-4 p-6">
|
|
329
|
+
<DestructiveConfirm />
|
|
330
|
+
<TypeToConfirm />
|
|
331
|
+
</div>
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### contextual header
|
|
337
|
+
```tsx
|
|
338
|
+
import {
|
|
339
|
+
Breadcrumb,
|
|
340
|
+
BreadcrumbItem,
|
|
341
|
+
BreadcrumbLink,
|
|
342
|
+
BreadcrumbList,
|
|
343
|
+
BreadcrumbPage,
|
|
344
|
+
BreadcrumbSeparator,
|
|
345
|
+
} from '@coston/ui/breadcrumb';
|
|
346
|
+
import { Separator } from '@coston/ui/separator';
|
|
347
|
+
import { SidebarTrigger } from '@coston/ui/sidebar';
|
|
348
|
+
import { Tabs, TabsList, TabsTrigger } from '@coston/ui/tabs';
|
|
349
|
+
|
|
350
|
+
const tabItems = ['Overview', 'Details', 'Projects', 'Notes'];
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Contextual Header
|
|
354
|
+
*
|
|
355
|
+
* Header with breadcrumb trail and inline sub-navigation tabs.
|
|
356
|
+
* Use for record detail pages (CRM accounts, project views, etc.)
|
|
357
|
+
* where the user navigated from a parent list into a specific record.
|
|
358
|
+
*
|
|
359
|
+
* Desktop: single row — SidebarTrigger | Breadcrumb | Tabs
|
|
360
|
+
* Mobile: two rows — [SidebarTrigger | Breadcrumb] / [scrollable Tabs]
|
|
361
|
+
*/
|
|
362
|
+
export function ContextualHeader() {
|
|
363
|
+
return (
|
|
364
|
+
<div className="flex shrink-0 flex-col border-b border-border/50">
|
|
365
|
+
{/* Primary row: trigger + breadcrumb (+ inline tabs on sm+) */}
|
|
366
|
+
<div className="flex h-12 items-center gap-1 px-4 lg:gap-2 lg:px-6">
|
|
367
|
+
<SidebarTrigger className="-ml-1" />
|
|
368
|
+
<Separator orientation="vertical" className="mx-2 data-[orientation=vertical]:h-4" />
|
|
369
|
+
<Breadcrumb>
|
|
370
|
+
<BreadcrumbList>
|
|
371
|
+
<BreadcrumbItem>
|
|
372
|
+
<BreadcrumbLink href="#">External</BreadcrumbLink>
|
|
373
|
+
</BreadcrumbItem>
|
|
374
|
+
<BreadcrumbSeparator />
|
|
375
|
+
<BreadcrumbItem>
|
|
376
|
+
<BreadcrumbPage>Acme Inc.</BreadcrumbPage>
|
|
377
|
+
</BreadcrumbItem>
|
|
378
|
+
</BreadcrumbList>
|
|
379
|
+
</Breadcrumb>
|
|
380
|
+
|
|
381
|
+
{/* Tabs inline — only on sm+ */}
|
|
382
|
+
<div className="ml-2 hidden items-center sm:flex">
|
|
383
|
+
<Separator orientation="vertical" className="mx-2 data-[orientation=vertical]:h-4" />
|
|
384
|
+
<Tabs defaultValue="overview">
|
|
385
|
+
<TabsList>
|
|
386
|
+
{tabItems.map(tab => (
|
|
387
|
+
<TabsTrigger key={tab} value={tab.toLowerCase()}>
|
|
388
|
+
{tab}
|
|
389
|
+
</TabsTrigger>
|
|
390
|
+
))}
|
|
391
|
+
</TabsList>
|
|
392
|
+
</Tabs>
|
|
393
|
+
</div>
|
|
394
|
+
</div>
|
|
395
|
+
|
|
396
|
+
{/* Secondary row: tabs on mobile only */}
|
|
397
|
+
<div className="flex items-center overflow-x-auto border-t border-border/30 px-3 pb-1 pt-0.5 sm:hidden">
|
|
398
|
+
<Tabs defaultValue="overview">
|
|
399
|
+
<TabsList>
|
|
400
|
+
{tabItems.map(tab => (
|
|
401
|
+
<TabsTrigger key={tab} value={tab.toLowerCase()}>
|
|
402
|
+
{tab}
|
|
403
|
+
</TabsTrigger>
|
|
404
|
+
))}
|
|
405
|
+
</TabsList>
|
|
406
|
+
</Tabs>
|
|
407
|
+
</div>
|
|
408
|
+
</div>
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### data table toolbar
|
|
414
|
+
```tsx
|
|
415
|
+
import { useState } from 'react';
|
|
416
|
+
import { DownloadIcon, PlusIcon, SearchIcon } from 'lucide-react';
|
|
417
|
+
import { Badge } from '@coston/ui/badge';
|
|
418
|
+
import { Button } from '@coston/ui/button';
|
|
419
|
+
import { Input } from '@coston/ui/input';
|
|
420
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@coston/ui/select';
|
|
421
|
+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@coston/ui/table';
|
|
422
|
+
|
|
423
|
+
type Status = 'Active' | 'Inactive' | 'Archived';
|
|
424
|
+
|
|
425
|
+
type Row = {
|
|
426
|
+
id: number;
|
|
427
|
+
name: string;
|
|
428
|
+
email: string;
|
|
429
|
+
role: string;
|
|
430
|
+
status: Status;
|
|
431
|
+
createdAt: string;
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
const data: Row[] = [
|
|
435
|
+
{
|
|
436
|
+
id: 1,
|
|
437
|
+
name: 'Alice Martin',
|
|
438
|
+
email: 'alice@example.com',
|
|
439
|
+
role: 'Admin',
|
|
440
|
+
status: 'Active',
|
|
441
|
+
createdAt: 'Jan 12, 2024',
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
id: 2,
|
|
445
|
+
name: 'Bob Chen',
|
|
446
|
+
email: 'bob@example.com',
|
|
447
|
+
role: 'Editor',
|
|
448
|
+
status: 'Active',
|
|
449
|
+
createdAt: 'Feb 3, 2024',
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
id: 3,
|
|
453
|
+
name: 'Carol Davis',
|
|
454
|
+
email: 'carol@example.com',
|
|
455
|
+
role: 'Viewer',
|
|
456
|
+
status: 'Inactive',
|
|
457
|
+
createdAt: 'Mar 8, 2024',
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
id: 4,
|
|
461
|
+
name: 'Dan Kim',
|
|
462
|
+
email: 'dan@example.com',
|
|
463
|
+
role: 'Editor',
|
|
464
|
+
status: 'Active',
|
|
465
|
+
createdAt: 'Mar 19, 2024',
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
id: 5,
|
|
469
|
+
name: 'Eva Rossi',
|
|
470
|
+
email: 'eva@example.com',
|
|
471
|
+
role: 'Admin',
|
|
472
|
+
status: 'Archived',
|
|
473
|
+
createdAt: 'Apr 2, 2024',
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
id: 6,
|
|
477
|
+
name: 'Frank Lee',
|
|
478
|
+
email: 'frank@example.com',
|
|
479
|
+
role: 'Viewer',
|
|
480
|
+
status: 'Active',
|
|
481
|
+
createdAt: 'Apr 15, 2024',
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
id: 7,
|
|
485
|
+
name: 'Grace Park',
|
|
486
|
+
email: 'grace@example.com',
|
|
487
|
+
role: 'Editor',
|
|
488
|
+
status: 'Inactive',
|
|
489
|
+
createdAt: 'May 7, 2024',
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
id: 8,
|
|
493
|
+
name: 'Henry Wu',
|
|
494
|
+
email: 'henry@example.com',
|
|
495
|
+
role: 'Viewer',
|
|
496
|
+
status: 'Active',
|
|
497
|
+
createdAt: 'May 20, 2024',
|
|
498
|
+
},
|
|
499
|
+
];
|
|
500
|
+
|
|
501
|
+
const statusVariant: Record<Status, 'default' | 'secondary' | 'outline'> = {
|
|
502
|
+
Active: 'default',
|
|
503
|
+
Inactive: 'secondary',
|
|
504
|
+
Archived: 'outline',
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
const PAGE_SIZE = 5;
|
|
508
|
+
|
|
509
|
+
export function DataTableToolbar() {
|
|
510
|
+
const [search, setSearch] = useState('');
|
|
511
|
+
const [status, setStatus] = useState('all');
|
|
512
|
+
const [page, setPage] = useState(0);
|
|
513
|
+
|
|
514
|
+
const filtered = data.filter(row => {
|
|
515
|
+
const matchesSearch =
|
|
516
|
+
row.name.toLowerCase().includes(search.toLowerCase()) ||
|
|
517
|
+
row.email.toLowerCase().includes(search.toLowerCase());
|
|
518
|
+
const matchesStatus = status === 'all' || row.status === status;
|
|
519
|
+
return matchesSearch && matchesStatus;
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
const pageCount = Math.ceil(filtered.length / PAGE_SIZE);
|
|
523
|
+
const paged = filtered.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE);
|
|
524
|
+
|
|
525
|
+
return (
|
|
526
|
+
<div className="flex flex-col gap-4 p-4">
|
|
527
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
528
|
+
<div className="relative min-w-[160px] flex-1">
|
|
529
|
+
<SearchIcon className="absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
|
530
|
+
<Input
|
|
531
|
+
placeholder="Search users…"
|
|
532
|
+
value={search}
|
|
533
|
+
onChange={e => {
|
|
534
|
+
setSearch(e.target.value);
|
|
535
|
+
setPage(0);
|
|
536
|
+
}}
|
|
537
|
+
className="pl-8"
|
|
538
|
+
/>
|
|
539
|
+
</div>
|
|
540
|
+
<Select
|
|
541
|
+
value={status}
|
|
542
|
+
onValueChange={v => {
|
|
543
|
+
setStatus(v);
|
|
544
|
+
setPage(0);
|
|
545
|
+
}}
|
|
546
|
+
>
|
|
547
|
+
<SelectTrigger className="w-[140px]">
|
|
548
|
+
<SelectValue />
|
|
549
|
+
</SelectTrigger>
|
|
550
|
+
<SelectContent>
|
|
551
|
+
<SelectItem value="all">All statuses</SelectItem>
|
|
552
|
+
<SelectItem value="Active">Active</SelectItem>
|
|
553
|
+
<SelectItem value="Inactive">Inactive</SelectItem>
|
|
554
|
+
<SelectItem value="Archived">Archived</SelectItem>
|
|
555
|
+
</SelectContent>
|
|
556
|
+
</Select>
|
|
557
|
+
<Button variant="outline" size="sm">
|
|
558
|
+
<PlusIcon className="h-4 w-4" />
|
|
559
|
+
Add user
|
|
560
|
+
</Button>
|
|
561
|
+
<Button variant="ghost" size="sm">
|
|
562
|
+
<DownloadIcon className="h-4 w-4" />
|
|
563
|
+
Export
|
|
564
|
+
</Button>
|
|
565
|
+
</div>
|
|
566
|
+
|
|
567
|
+
<div className="rounded-md border border-border">
|
|
568
|
+
<Table>
|
|
569
|
+
<TableHeader>
|
|
570
|
+
<TableRow>
|
|
571
|
+
<TableHead>Name</TableHead>
|
|
572
|
+
<TableHead className="hidden sm:table-cell">Email</TableHead>
|
|
573
|
+
<TableHead className="hidden md:table-cell">Role</TableHead>
|
|
574
|
+
<TableHead>Status</TableHead>
|
|
575
|
+
<TableHead className="hidden lg:table-cell">Created</TableHead>
|
|
576
|
+
</TableRow>
|
|
577
|
+
</TableHeader>
|
|
578
|
+
<TableBody>
|
|
579
|
+
{paged.length ? (
|
|
580
|
+
paged.map(row => (
|
|
581
|
+
<TableRow key={row.id}>
|
|
582
|
+
<TableCell className="font-medium">{row.name}</TableCell>
|
|
583
|
+
<TableCell className="hidden text-muted-foreground sm:table-cell">
|
|
584
|
+
{row.email}
|
|
585
|
+
</TableCell>
|
|
586
|
+
<TableCell className="hidden md:table-cell">{row.role}</TableCell>
|
|
587
|
+
<TableCell>
|
|
588
|
+
<Badge variant={statusVariant[row.status]}>{row.status}</Badge>
|
|
589
|
+
</TableCell>
|
|
590
|
+
<TableCell className="hidden text-muted-foreground lg:table-cell">
|
|
591
|
+
{row.createdAt}
|
|
592
|
+
</TableCell>
|
|
593
|
+
</TableRow>
|
|
594
|
+
))
|
|
595
|
+
) : (
|
|
596
|
+
<TableRow>
|
|
597
|
+
<TableCell colSpan={5} className="h-24 text-center text-muted-foreground">
|
|
598
|
+
No results found.
|
|
599
|
+
</TableCell>
|
|
600
|
+
</TableRow>
|
|
601
|
+
)}
|
|
602
|
+
</TableBody>
|
|
603
|
+
</Table>
|
|
604
|
+
</div>
|
|
605
|
+
|
|
606
|
+
<div className="flex items-center justify-between text-sm text-muted-foreground">
|
|
607
|
+
<span>
|
|
608
|
+
{filtered.length} result{filtered.length !== 1 ? 's' : ''}
|
|
609
|
+
</span>
|
|
610
|
+
<div className="flex items-center gap-2">
|
|
611
|
+
<Button
|
|
612
|
+
variant="outline"
|
|
613
|
+
size="sm"
|
|
614
|
+
disabled={page === 0}
|
|
615
|
+
onClick={() => setPage(p => p - 1)}
|
|
616
|
+
>
|
|
617
|
+
Previous
|
|
618
|
+
</Button>
|
|
619
|
+
<span>
|
|
620
|
+
Page {page + 1} of {Math.max(1, pageCount)}
|
|
621
|
+
</span>
|
|
622
|
+
<Button
|
|
623
|
+
variant="outline"
|
|
624
|
+
size="sm"
|
|
625
|
+
disabled={page >= pageCount - 1}
|
|
626
|
+
onClick={() => setPage(p => p + 1)}
|
|
627
|
+
>
|
|
628
|
+
Next
|
|
629
|
+
</Button>
|
|
630
|
+
</div>
|
|
631
|
+
</div>
|
|
632
|
+
</div>
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
### empty state
|
|
638
|
+
```tsx
|
|
639
|
+
import { SearchIcon, FolderIcon, AlertCircleIcon } from 'lucide-react';
|
|
640
|
+
import { Button } from '@coston/ui/button';
|
|
641
|
+
|
|
642
|
+
const variants = [
|
|
643
|
+
{
|
|
644
|
+
icon: SearchIcon,
|
|
645
|
+
title: 'No results',
|
|
646
|
+
description: 'Try adjusting your filters',
|
|
647
|
+
action: 'Clear filters',
|
|
648
|
+
},
|
|
649
|
+
{
|
|
650
|
+
icon: FolderIcon,
|
|
651
|
+
title: 'No items yet',
|
|
652
|
+
description: 'Get started by creating your first one',
|
|
653
|
+
action: 'Create item',
|
|
654
|
+
},
|
|
655
|
+
{
|
|
656
|
+
icon: AlertCircleIcon,
|
|
657
|
+
title: 'Something went wrong',
|
|
658
|
+
description: 'We had trouble loading this content',
|
|
659
|
+
action: 'Retry',
|
|
660
|
+
},
|
|
661
|
+
];
|
|
662
|
+
|
|
663
|
+
export function EmptyState() {
|
|
664
|
+
return (
|
|
665
|
+
<div className="grid grid-cols-1 gap-4 p-6 sm:grid-cols-3">
|
|
666
|
+
{variants.map(({ icon: Icon, title, description, action }) => (
|
|
667
|
+
<div
|
|
668
|
+
key={title}
|
|
669
|
+
className="flex flex-col items-center gap-3 rounded-lg border border-border p-8 text-center"
|
|
670
|
+
>
|
|
671
|
+
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-muted">
|
|
672
|
+
<Icon className="h-6 w-6 text-muted-foreground" />
|
|
673
|
+
</div>
|
|
674
|
+
<div>
|
|
675
|
+
<p className="text-sm font-medium">{title}</p>
|
|
676
|
+
<p className="text-sm text-muted-foreground">{description}</p>
|
|
677
|
+
</div>
|
|
678
|
+
<Button variant="outline" size="sm">
|
|
679
|
+
{action}
|
|
680
|
+
</Button>
|
|
681
|
+
</div>
|
|
682
|
+
))}
|
|
683
|
+
</div>
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
### multi step form
|
|
689
|
+
```tsx
|
|
690
|
+
import { useState } from 'react';
|
|
691
|
+
import { toast } from 'sonner';
|
|
692
|
+
import { Button } from '@coston/ui/button';
|
|
693
|
+
import { Input } from '@coston/ui/input';
|
|
694
|
+
import { Label } from '@coston/ui/label';
|
|
695
|
+
import { Progress } from '@coston/ui/progress';
|
|
696
|
+
import { Separator } from '@coston/ui/separator';
|
|
697
|
+
import { Textarea } from '@coston/ui/textarea';
|
|
698
|
+
|
|
699
|
+
type FormValues = {
|
|
700
|
+
email: string;
|
|
701
|
+
password: string;
|
|
702
|
+
name: string;
|
|
703
|
+
bio: string;
|
|
704
|
+
};
|
|
705
|
+
|
|
706
|
+
const steps = ['Account', 'Profile', 'Confirm'];
|
|
707
|
+
|
|
708
|
+
function StepIndicator({ current }: { current: number }) {
|
|
709
|
+
return (
|
|
710
|
+
<div className="flex items-center justify-center">
|
|
711
|
+
{steps.map((label, i) => (
|
|
712
|
+
<div key={label} className="flex items-center">
|
|
713
|
+
<div className="flex flex-col items-center gap-1">
|
|
714
|
+
<div
|
|
715
|
+
className={[
|
|
716
|
+
'flex h-7 w-7 items-center justify-center rounded-full text-xs font-medium',
|
|
717
|
+
i < current
|
|
718
|
+
? 'bg-primary text-primary-foreground'
|
|
719
|
+
: i === current
|
|
720
|
+
? 'text-primary ring-2 ring-primary'
|
|
721
|
+
: 'bg-muted text-muted-foreground',
|
|
722
|
+
].join(' ')}
|
|
723
|
+
>
|
|
724
|
+
{i < current ? '✓' : i + 1}
|
|
725
|
+
</div>
|
|
726
|
+
<span
|
|
727
|
+
className={`text-xs ${i === current ? 'font-medium text-foreground' : 'text-muted-foreground'}`}
|
|
728
|
+
>
|
|
729
|
+
{label}
|
|
730
|
+
</span>
|
|
731
|
+
</div>
|
|
732
|
+
{i < steps.length - 1 && (
|
|
733
|
+
<div className={`mx-2 mb-4 h-px w-12 ${i < current ? 'bg-primary' : 'bg-border'}`} />
|
|
734
|
+
)}
|
|
735
|
+
</div>
|
|
736
|
+
))}
|
|
737
|
+
</div>
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
export function MultiStepForm() {
|
|
742
|
+
const [step, setStep] = useState(0);
|
|
743
|
+
const [values, setValues] = useState<FormValues>({ email: '', password: '', name: '', bio: '' });
|
|
744
|
+
|
|
745
|
+
const set =
|
|
746
|
+
(field: keyof FormValues) => (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) =>
|
|
747
|
+
setValues(v => ({ ...v, [field]: e.target.value }));
|
|
748
|
+
|
|
749
|
+
const handleSubmit = () => {
|
|
750
|
+
toast.success('Account created!');
|
|
751
|
+
setStep(0);
|
|
752
|
+
setValues({ email: '', password: '', name: '', bio: '' });
|
|
753
|
+
};
|
|
754
|
+
|
|
755
|
+
return (
|
|
756
|
+
<div className="p-6">
|
|
757
|
+
<div className="mx-auto flex max-w-md flex-col gap-6">
|
|
758
|
+
<Progress value={((step + 1) / steps.length) * 100} className="h-1" />
|
|
759
|
+
<StepIndicator current={step} />
|
|
760
|
+
<Separator />
|
|
761
|
+
|
|
762
|
+
{step === 0 && (
|
|
763
|
+
<div className="flex flex-col gap-4">
|
|
764
|
+
<div className="grid gap-2">
|
|
765
|
+
<Label htmlFor="email">Email</Label>
|
|
766
|
+
<Input
|
|
767
|
+
id="email"
|
|
768
|
+
type="email"
|
|
769
|
+
placeholder="you@example.com"
|
|
770
|
+
value={values.email}
|
|
771
|
+
onChange={set('email')}
|
|
772
|
+
/>
|
|
773
|
+
</div>
|
|
774
|
+
<div className="grid gap-2">
|
|
775
|
+
<Label htmlFor="password">Password</Label>
|
|
776
|
+
<Input
|
|
777
|
+
id="password"
|
|
778
|
+
type="password"
|
|
779
|
+
placeholder="••••••••"
|
|
780
|
+
value={values.password}
|
|
781
|
+
onChange={set('password')}
|
|
782
|
+
/>
|
|
783
|
+
</div>
|
|
784
|
+
</div>
|
|
785
|
+
)}
|
|
786
|
+
|
|
787
|
+
{step === 1 && (
|
|
788
|
+
<div className="flex flex-col gap-4">
|
|
789
|
+
<div className="grid gap-2">
|
|
790
|
+
<Label htmlFor="name">Full name</Label>
|
|
791
|
+
<Input
|
|
792
|
+
id="name"
|
|
793
|
+
placeholder="Jane Smith"
|
|
794
|
+
value={values.name}
|
|
795
|
+
onChange={set('name')}
|
|
796
|
+
/>
|
|
797
|
+
</div>
|
|
798
|
+
<div className="grid gap-2">
|
|
799
|
+
<Label htmlFor="bio">Bio</Label>
|
|
800
|
+
<Textarea
|
|
801
|
+
id="bio"
|
|
802
|
+
placeholder="Tell us a little about yourself…"
|
|
803
|
+
value={values.bio}
|
|
804
|
+
onChange={set('bio')}
|
|
805
|
+
/>
|
|
806
|
+
</div>
|
|
807
|
+
</div>
|
|
808
|
+
)}
|
|
809
|
+
|
|
810
|
+
{step === 2 && (
|
|
811
|
+
<div className="flex flex-col gap-3 text-sm">
|
|
812
|
+
<p className="font-medium">Review your details</p>
|
|
813
|
+
{(
|
|
814
|
+
[
|
|
815
|
+
['Email', values.email || '—'],
|
|
816
|
+
['Password', values.password ? '••••••••' : '—'],
|
|
817
|
+
['Name', values.name || '—'],
|
|
818
|
+
['Bio', values.bio || '—'],
|
|
819
|
+
] as [string, string][]
|
|
820
|
+
).map(([label, value]) => (
|
|
821
|
+
<div key={label} className="flex justify-between">
|
|
822
|
+
<span className="text-muted-foreground">{label}</span>
|
|
823
|
+
<span>{value}</span>
|
|
824
|
+
</div>
|
|
825
|
+
))}
|
|
826
|
+
</div>
|
|
827
|
+
)}
|
|
828
|
+
|
|
829
|
+
<div className="flex justify-between">
|
|
830
|
+
<Button variant="outline" onClick={() => setStep(s => s - 1)} disabled={step === 0}>
|
|
831
|
+
Back
|
|
832
|
+
</Button>
|
|
833
|
+
{step < steps.length - 1 ? (
|
|
834
|
+
<Button onClick={() => setStep(s => s + 1)}>Next</Button>
|
|
835
|
+
) : (
|
|
836
|
+
<Button onClick={handleSubmit}>Create account</Button>
|
|
837
|
+
)}
|
|
838
|
+
</div>
|
|
839
|
+
</div>
|
|
840
|
+
</div>
|
|
841
|
+
);
|
|
842
|
+
}
|
|
843
|
+
```
|
|
844
|
+
|
|
845
|
+
### split pane
|
|
846
|
+
```tsx
|
|
847
|
+
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@coston/ui/resizable';
|
|
848
|
+
import { ScrollArea } from '@coston/ui/scroll-area';
|
|
849
|
+
|
|
850
|
+
const fileTree = [
|
|
851
|
+
'src/',
|
|
852
|
+
' components/',
|
|
853
|
+
' Button.tsx',
|
|
854
|
+
' Input.tsx',
|
|
855
|
+
' pages/',
|
|
856
|
+
' Home.tsx',
|
|
857
|
+
' Settings.tsx',
|
|
858
|
+
'package.json',
|
|
859
|
+
'tsconfig.json',
|
|
860
|
+
];
|
|
861
|
+
|
|
862
|
+
export function SplitPane() {
|
|
863
|
+
return (
|
|
864
|
+
<div className="flex flex-col gap-6 p-6">
|
|
865
|
+
<div>
|
|
866
|
+
<p className="mb-2 text-xs text-muted-foreground">Horizontal — file explorer + document</p>
|
|
867
|
+
<ResizablePanelGroup
|
|
868
|
+
orientation="horizontal"
|
|
869
|
+
className="h-[260px] rounded-lg border border-border"
|
|
870
|
+
>
|
|
871
|
+
<ResizablePanel defaultSize={30} minSize={20}>
|
|
872
|
+
<ScrollArea className="h-full">
|
|
873
|
+
<div className="flex flex-col gap-0.5 p-2">
|
|
874
|
+
{fileTree.map((item, i) => (
|
|
875
|
+
<div
|
|
876
|
+
key={i}
|
|
877
|
+
className="cursor-pointer rounded px-2 py-1 font-mono text-xs text-muted-foreground hover:bg-muted"
|
|
878
|
+
>
|
|
879
|
+
{item}
|
|
880
|
+
</div>
|
|
881
|
+
))}
|
|
882
|
+
</div>
|
|
883
|
+
</ScrollArea>
|
|
884
|
+
</ResizablePanel>
|
|
885
|
+
<ResizableHandle withHandle />
|
|
886
|
+
<ResizablePanel defaultSize={70}>
|
|
887
|
+
<ScrollArea className="h-full">
|
|
888
|
+
<div className="p-4">
|
|
889
|
+
<h3 className="mb-2 text-sm font-semibold">Button.tsx</h3>
|
|
890
|
+
<p className="text-sm leading-relaxed text-muted-foreground">
|
|
891
|
+
Primary interactive element. Supports multiple variants (default, outline, ghost,
|
|
892
|
+
destructive) and sizes (sm, default, lg, icon). Built on Radix Slot for
|
|
893
|
+
polymorphic rendering.
|
|
894
|
+
</p>
|
|
895
|
+
</div>
|
|
896
|
+
</ScrollArea>
|
|
897
|
+
</ResizablePanel>
|
|
898
|
+
</ResizablePanelGroup>
|
|
899
|
+
</div>
|
|
900
|
+
|
|
901
|
+
<div>
|
|
902
|
+
<p className="mb-2 text-xs text-muted-foreground">Vertical — editor + preview</p>
|
|
903
|
+
<ResizablePanelGroup
|
|
904
|
+
orientation="vertical"
|
|
905
|
+
className="h-[300px] rounded-lg border border-border"
|
|
906
|
+
>
|
|
907
|
+
<ResizablePanel defaultSize={50}>
|
|
908
|
+
<ScrollArea className="h-full">
|
|
909
|
+
<pre className="p-4 font-mono text-xs text-muted-foreground">{`export function Hello() {
|
|
910
|
+
return (
|
|
911
|
+
<div className="p-4">
|
|
912
|
+
<h1>Hello, world!</h1>
|
|
913
|
+
<p>Edit this to see changes.</p>
|
|
914
|
+
</div>
|
|
915
|
+
);
|
|
916
|
+
}`}</pre>
|
|
917
|
+
</ScrollArea>
|
|
918
|
+
</ResizablePanel>
|
|
919
|
+
<ResizableHandle withHandle />
|
|
920
|
+
<ResizablePanel defaultSize={50}>
|
|
921
|
+
<div className="flex h-full items-start p-4">
|
|
922
|
+
<div>
|
|
923
|
+
<h1 className="text-lg font-bold">Hello, world!</h1>
|
|
924
|
+
<p className="text-sm text-muted-foreground">Edit this to see changes.</p>
|
|
925
|
+
</div>
|
|
926
|
+
</div>
|
|
927
|
+
</ResizablePanel>
|
|
928
|
+
</ResizablePanelGroup>
|
|
929
|
+
</div>
|
|
930
|
+
</div>
|
|
931
|
+
);
|
|
932
|
+
}
|
|
933
|
+
```
|
|
934
|
+
|
|
935
|
+
### toast feedback
|
|
936
|
+
```tsx
|
|
937
|
+
import { toast } from 'sonner';
|
|
938
|
+
import { Button } from '@coston/ui/button';
|
|
939
|
+
|
|
940
|
+
const variants = [
|
|
941
|
+
{ label: 'Default', action: () => toast('Default notification') },
|
|
942
|
+
{ label: 'Success', action: () => toast.success('Changes saved successfully') },
|
|
943
|
+
{ label: 'Error', action: () => toast.error('Something went wrong') },
|
|
944
|
+
{ label: 'Warning', action: () => toast.warning('Low disk space') },
|
|
945
|
+
{ label: 'Info', action: () => toast.info('Update available') },
|
|
946
|
+
{
|
|
947
|
+
label: 'With description',
|
|
948
|
+
action: () =>
|
|
949
|
+
toast('File uploaded', {
|
|
950
|
+
description: 'report.pdf was uploaded to /documents',
|
|
951
|
+
}),
|
|
952
|
+
},
|
|
953
|
+
{
|
|
954
|
+
label: 'With action',
|
|
955
|
+
action: () =>
|
|
956
|
+
toast('Message sent', {
|
|
957
|
+
action: { label: 'Undo', onClick: () => toast.success('Message unsent') },
|
|
958
|
+
}),
|
|
959
|
+
},
|
|
960
|
+
{
|
|
961
|
+
label: 'Loading → done',
|
|
962
|
+
action: () => {
|
|
963
|
+
const id = toast.loading('Saving…');
|
|
964
|
+
setTimeout(() => toast.success('Saved!', { id }), 2000);
|
|
965
|
+
},
|
|
966
|
+
},
|
|
967
|
+
{
|
|
968
|
+
label: 'Promise',
|
|
969
|
+
action: () =>
|
|
970
|
+
toast.promise(new Promise(resolve => setTimeout(resolve, 2000)), {
|
|
971
|
+
loading: 'Uploading file…',
|
|
972
|
+
success: 'Upload complete',
|
|
973
|
+
error: 'Upload failed',
|
|
974
|
+
}),
|
|
975
|
+
},
|
|
976
|
+
];
|
|
977
|
+
|
|
978
|
+
export function ToastFeedback() {
|
|
979
|
+
return (
|
|
980
|
+
<div className="grid grid-cols-2 gap-2 p-6 sm:grid-cols-3">
|
|
981
|
+
{variants.map(({ label, action }) => (
|
|
982
|
+
<Button key={label} variant="outline" size="sm" onClick={action}>
|
|
983
|
+
{label}
|
|
984
|
+
</Button>
|
|
985
|
+
))}
|
|
986
|
+
</div>
|
|
987
|
+
);
|
|
988
|
+
}
|
|
989
|
+
```
|