@chesapeake/stack 0.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/CHANGELOG.md +26 -0
- package/LICENSE +190 -0
- package/README.md +440 -0
- package/components/ui/avatar.tsx +87 -0
- package/components/ui/badge.tsx +49 -0
- package/components/ui/badgeVariants.ts +26 -0
- package/components/ui/button-group.tsx +67 -0
- package/components/ui/button.tsx +70 -0
- package/components/ui/buttonGroupVariants.ts +23 -0
- package/components/ui/buttonVariants.ts +38 -0
- package/components/ui/calendar.tsx +220 -0
- package/components/ui/card.tsx +162 -0
- package/components/ui/dialog.tsx +290 -0
- package/components/ui/drawer.tsx +227 -0
- package/components/ui/dropdown-menu.tsx +260 -0
- package/components/ui/index.ts +86 -0
- package/components/ui/input.tsx +59 -0
- package/components/ui/label.tsx +47 -0
- package/components/ui/location-input.md +194 -0
- package/components/ui/location-input.tsx +384 -0
- package/components/ui/popover.tsx +110 -0
- package/components/ui/select.tsx +306 -0
- package/components/ui/separator.tsx +50 -0
- package/components/ui/sonner-api.ts +8 -0
- package/components/ui/sonner.tsx +69 -0
- package/components/ui/textarea.tsx +53 -0
- package/components/ui/tooltip.tsx +125 -0
- package/components/ux/ActivityAnimation.tsx +135 -0
- package/components/ux/EmptyState.tsx +22 -0
- package/components/ux/FeedbackDrawer.tsx +141 -0
- package/components/ux/FireAnimation.tsx +189 -0
- package/components/ux/FriendList.tsx +111 -0
- package/components/ux/FriendRequestList.tsx +104 -0
- package/components/ux/GifSearch.tsx +264 -0
- package/components/ux/ImageUpload.tsx +829 -0
- package/components/ux/SelectedUsersList.tsx +91 -0
- package/components/ux/StandardLayout/PageLayout.tsx +107 -0
- package/components/ux/StandardLayout/ProfilePage.tsx +452 -0
- package/components/ux/StandardLayout/StandardLayout.tsx +345 -0
- package/components/ux/StandardLayout/index.ts +22 -0
- package/components/ux/TutorialCarousel.tsx +128 -0
- package/components/ux/UserSearch.tsx +324 -0
- package/components/ux/UserSelector.tsx +116 -0
- package/components/ux/auth/AuthSelector.tsx +154 -0
- package/components/ux/auth/MultiAuthProvider.tsx +196 -0
- package/components/ux/auth/README.md +317 -0
- package/components/ux/auth/context.ts +46 -0
- package/components/ux/auth/github/AuthCallback.tsx +177 -0
- package/components/ux/auth/github/AuthProvider.tsx +111 -0
- package/components/ux/auth/github/README.md +208 -0
- package/components/ux/auth/github/client.ts +127 -0
- package/components/ux/auth/github/config.ts +68 -0
- package/components/ux/auth/github/context.ts +34 -0
- package/components/ux/auth/github/default-client.ts +74 -0
- package/components/ux/auth/github/index.ts +43 -0
- package/components/ux/auth/github/types.ts +44 -0
- package/components/ux/auth/github/useGitHubAuth.ts +16 -0
- package/components/ux/auth/google/AuthCallback.tsx +170 -0
- package/components/ux/auth/google/AuthProvider.tsx +108 -0
- package/components/ux/auth/google/README.md +450 -0
- package/components/ux/auth/google/client.ts +109 -0
- package/components/ux/auth/google/config.ts +65 -0
- package/components/ux/auth/google/context.ts +34 -0
- package/components/ux/auth/google/default-client.ts +69 -0
- package/components/ux/auth/google/index.ts +43 -0
- package/components/ux/auth/google/types.ts +42 -0
- package/components/ux/auth/google/useGoogleAuth.ts +16 -0
- package/components/ux/auth/index.ts +72 -0
- package/components/ux/auth/useAuth.ts +16 -0
- package/components/ux/index.ts +46 -0
- package/components/ux/theme-provider-context.ts +20 -0
- package/components/ux/theme-provider.tsx +114 -0
- package/components/ux/useTheme.ts +42 -0
- package/index.ts +29 -0
- package/lib/utils.ts +57 -0
- package/package.json +170 -0
- package/styles/base.css +38 -0
- package/styles/components.css +54 -0
- package/styles/index.css +7 -0
- package/styles/tokens.css +189 -0
- package/tailwind.preset.ts +169 -0
- package/types/user.ts +69 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// Copyright 2026 Chesapeake Computing
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import React from "react";
|
|
5
|
+
|
|
6
|
+
import { cn } from "../../lib/utils";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The main Card container component.
|
|
10
|
+
* Provides consistent card styling with background, border, and shadow.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* <Card>
|
|
15
|
+
* <CardHeader>
|
|
16
|
+
* <CardTitle>Card Title</CardTitle>
|
|
17
|
+
* <CardDescription>Card description goes here</CardDescription>
|
|
18
|
+
* </CardHeader>
|
|
19
|
+
* <CardContent>
|
|
20
|
+
* <p>Main content of the card</p>
|
|
21
|
+
* </CardContent>
|
|
22
|
+
* <CardFooter>
|
|
23
|
+
* <Button>Action</Button>
|
|
24
|
+
* </CardFooter>
|
|
25
|
+
* </Card>
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
function Card({ className, ...props }: React.ComponentPropsWithoutRef<"div">) {
|
|
29
|
+
return (
|
|
30
|
+
<div
|
|
31
|
+
data-slot="card"
|
|
32
|
+
className={cn(
|
|
33
|
+
"bg-card text-card-foreground flex flex-col rounded-xl border shadow-sm",
|
|
34
|
+
className
|
|
35
|
+
)}
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Header section of the Card.
|
|
43
|
+
* Contains the title and description with proper spacing.
|
|
44
|
+
*/
|
|
45
|
+
function CardHeader({
|
|
46
|
+
className,
|
|
47
|
+
...props
|
|
48
|
+
}: React.ComponentPropsWithoutRef<"div">) {
|
|
49
|
+
return (
|
|
50
|
+
<div
|
|
51
|
+
data-slot="card-header"
|
|
52
|
+
className={cn(
|
|
53
|
+
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-4 sm:px-6 pt-4 sm:pt-6 pb-4 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
|
|
54
|
+
className
|
|
55
|
+
)}
|
|
56
|
+
{...props}
|
|
57
|
+
/>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Title element for the Card header.
|
|
63
|
+
*/
|
|
64
|
+
function CardTitle({
|
|
65
|
+
className,
|
|
66
|
+
...props
|
|
67
|
+
}: React.ComponentPropsWithoutRef<"div">) {
|
|
68
|
+
return (
|
|
69
|
+
<div
|
|
70
|
+
data-slot="card-title"
|
|
71
|
+
className={cn(
|
|
72
|
+
"leading-tight font-semibold text-base sm:text-lg",
|
|
73
|
+
className
|
|
74
|
+
)}
|
|
75
|
+
{...props}
|
|
76
|
+
/>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Description text for the Card header.
|
|
82
|
+
* Typically rendered below the title with muted styling.
|
|
83
|
+
*/
|
|
84
|
+
function CardDescription({
|
|
85
|
+
className,
|
|
86
|
+
...props
|
|
87
|
+
}: React.ComponentPropsWithoutRef<"div">) {
|
|
88
|
+
return (
|
|
89
|
+
<div
|
|
90
|
+
data-slot="card-description"
|
|
91
|
+
className={cn("text-muted-foreground text-sm", className)}
|
|
92
|
+
{...props}
|
|
93
|
+
/>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Action element positioned in the top-right of the Card header.
|
|
99
|
+
* Useful for dropdown menus, buttons, or other actions.
|
|
100
|
+
*/
|
|
101
|
+
function CardAction({
|
|
102
|
+
className,
|
|
103
|
+
...props
|
|
104
|
+
}: React.ComponentPropsWithoutRef<"div">) {
|
|
105
|
+
return (
|
|
106
|
+
<div
|
|
107
|
+
data-slot="card-action"
|
|
108
|
+
className={cn(
|
|
109
|
+
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
|
110
|
+
className
|
|
111
|
+
)}
|
|
112
|
+
{...props}
|
|
113
|
+
/>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Main content area of the Card.
|
|
119
|
+
* Contains the primary content with consistent padding.
|
|
120
|
+
*/
|
|
121
|
+
function CardContent({
|
|
122
|
+
className,
|
|
123
|
+
...props
|
|
124
|
+
}: React.ComponentPropsWithoutRef<"div">) {
|
|
125
|
+
return (
|
|
126
|
+
<div
|
|
127
|
+
data-slot="card-content"
|
|
128
|
+
className={cn("px-4 sm:px-6 pb-4 sm:pb-6", className)}
|
|
129
|
+
{...props}
|
|
130
|
+
/>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Footer section of the Card.
|
|
136
|
+
* Typically contains actions or additional information.
|
|
137
|
+
*/
|
|
138
|
+
function CardFooter({
|
|
139
|
+
className,
|
|
140
|
+
...props
|
|
141
|
+
}: React.ComponentPropsWithoutRef<"div">) {
|
|
142
|
+
return (
|
|
143
|
+
<div
|
|
144
|
+
data-slot="card-footer"
|
|
145
|
+
className={cn(
|
|
146
|
+
"flex items-center px-4 sm:px-6 py-4 sm:py-6 [.border-t]:pt-6",
|
|
147
|
+
className
|
|
148
|
+
)}
|
|
149
|
+
{...props}
|
|
150
|
+
/>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export {
|
|
155
|
+
Card,
|
|
156
|
+
CardAction,
|
|
157
|
+
CardContent,
|
|
158
|
+
CardDescription,
|
|
159
|
+
CardFooter,
|
|
160
|
+
CardHeader,
|
|
161
|
+
CardTitle,
|
|
162
|
+
};
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
// Copyright 2026 Chesapeake Computing
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import React from "react";
|
|
5
|
+
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
6
|
+
|
|
7
|
+
import { cn } from "../../lib/utils";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Dialog component root. Provides the context for dialog components.
|
|
11
|
+
* Built on top of Radix UI Dialog primitives for accessibility.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle } from "@shared/components/ui/dialog"
|
|
16
|
+
* import { Button } from "@shared/components/ui/button"
|
|
17
|
+
*
|
|
18
|
+
* function MyDialog() {
|
|
19
|
+
* return (
|
|
20
|
+
* <Dialog>
|
|
21
|
+
* <DialogTrigger asChild>
|
|
22
|
+
* <Button>Open Dialog</Button>
|
|
23
|
+
* </DialogTrigger>
|
|
24
|
+
* <DialogContent>
|
|
25
|
+
* <DialogHeader>
|
|
26
|
+
* <DialogTitle>Dialog Title</DialogTitle>
|
|
27
|
+
* </DialogHeader>
|
|
28
|
+
* <p>Dialog content here</p>
|
|
29
|
+
* </DialogContent>
|
|
30
|
+
* </Dialog>
|
|
31
|
+
* )
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
function Dialog({
|
|
36
|
+
...props
|
|
37
|
+
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
|
|
38
|
+
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* DialogTrigger component. Wraps the element that opens the dialog.
|
|
43
|
+
* When clicked, the dialog will open.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```tsx
|
|
47
|
+
* <Dialog>
|
|
48
|
+
* <DialogTrigger asChild>
|
|
49
|
+
* <Button>Open</Button>
|
|
50
|
+
* </DialogTrigger>
|
|
51
|
+
* <DialogContent>...</DialogContent>
|
|
52
|
+
* </Dialog>
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
function DialogTrigger({
|
|
56
|
+
...props
|
|
57
|
+
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
|
|
58
|
+
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* DialogPortal component. Portals the dialog content outside the DOM hierarchy.
|
|
63
|
+
* Usually used internally by DialogContent.
|
|
64
|
+
*/
|
|
65
|
+
function DialogPortal({
|
|
66
|
+
...props
|
|
67
|
+
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
|
|
68
|
+
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* DialogClose component. Closes the dialog when clicked.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```tsx
|
|
76
|
+
* <DialogContent>
|
|
77
|
+
* <DialogHeader>
|
|
78
|
+
* <DialogTitle>Title</DialogTitle>
|
|
79
|
+
* </DialogHeader>
|
|
80
|
+
* <p>Content</p>
|
|
81
|
+
* <DialogFooter>
|
|
82
|
+
* <DialogClose asChild>
|
|
83
|
+
* <Button variant="outline">Close</Button>
|
|
84
|
+
* </DialogClose>
|
|
85
|
+
* </DialogFooter>
|
|
86
|
+
* </DialogContent>
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
function DialogClose({
|
|
90
|
+
...props
|
|
91
|
+
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
|
|
92
|
+
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* DialogOverlay component. The backdrop behind the dialog content.
|
|
97
|
+
* Usually used internally by DialogContent.
|
|
98
|
+
*/
|
|
99
|
+
function DialogOverlay({
|
|
100
|
+
className,
|
|
101
|
+
...props
|
|
102
|
+
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
|
|
103
|
+
return (
|
|
104
|
+
<DialogPrimitive.Overlay
|
|
105
|
+
data-slot="dialog-overlay"
|
|
106
|
+
className={cn(
|
|
107
|
+
"fixed inset-0 z-50 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
108
|
+
className
|
|
109
|
+
)}
|
|
110
|
+
{...props}
|
|
111
|
+
/>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* DialogContent component. The main content container for the dialog.
|
|
117
|
+
* Includes the overlay and portal by default.
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```tsx
|
|
121
|
+
* <Dialog>
|
|
122
|
+
* <DialogTrigger>Open</DialogTrigger>
|
|
123
|
+
* <DialogContent className="max-w-md">
|
|
124
|
+
* <DialogHeader>
|
|
125
|
+
* <DialogTitle>Confirm Action</DialogTitle>
|
|
126
|
+
* <DialogDescription>
|
|
127
|
+
* Are you sure you want to proceed?
|
|
128
|
+
* </DialogDescription>
|
|
129
|
+
* </DialogHeader>
|
|
130
|
+
* <DialogFooter>
|
|
131
|
+
* <Button>Confirm</Button>
|
|
132
|
+
* <DialogClose asChild>
|
|
133
|
+
* <Button variant="outline">Cancel</Button>
|
|
134
|
+
* </DialogClose>
|
|
135
|
+
* </DialogFooter>
|
|
136
|
+
* </DialogContent>
|
|
137
|
+
* </Dialog>
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
function DialogContent({
|
|
141
|
+
className,
|
|
142
|
+
children,
|
|
143
|
+
...props
|
|
144
|
+
}: React.ComponentProps<typeof DialogPrimitive.Content>) {
|
|
145
|
+
return (
|
|
146
|
+
<DialogPortal>
|
|
147
|
+
<DialogOverlay />
|
|
148
|
+
<DialogPrimitive.Content
|
|
149
|
+
data-slot="dialog-content"
|
|
150
|
+
className={cn(
|
|
151
|
+
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
|
152
|
+
className
|
|
153
|
+
)}
|
|
154
|
+
{...props}
|
|
155
|
+
>
|
|
156
|
+
{children}
|
|
157
|
+
</DialogPrimitive.Content>
|
|
158
|
+
</DialogPortal>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* DialogHeader component. Container for the dialog title and description.
|
|
164
|
+
* Provides consistent spacing and layout.
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* ```tsx
|
|
168
|
+
* <DialogContent>
|
|
169
|
+
* <DialogHeader>
|
|
170
|
+
* <DialogTitle>Dialog Title</DialogTitle>
|
|
171
|
+
* <DialogDescription>Optional description text</DialogDescription>
|
|
172
|
+
* </DialogHeader>
|
|
173
|
+
* </DialogContent>
|
|
174
|
+
* ```
|
|
175
|
+
*/
|
|
176
|
+
function DialogHeader({
|
|
177
|
+
className,
|
|
178
|
+
...props
|
|
179
|
+
}: React.ComponentPropsWithoutRef<"div">) {
|
|
180
|
+
return (
|
|
181
|
+
<div
|
|
182
|
+
data-slot="dialog-header"
|
|
183
|
+
className={cn(
|
|
184
|
+
"flex flex-col space-y-1.5 text-center sm:text-left",
|
|
185
|
+
className
|
|
186
|
+
)}
|
|
187
|
+
{...props}
|
|
188
|
+
/>
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* DialogFooter component. Container for dialog action buttons.
|
|
194
|
+
* Provides consistent spacing and responsive layout.
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* ```tsx
|
|
198
|
+
* <DialogContent>
|
|
199
|
+
* <DialogHeader>...</DialogHeader>
|
|
200
|
+
* <DialogFooter>
|
|
201
|
+
* <Button>Save</Button>
|
|
202
|
+
* <DialogClose asChild>
|
|
203
|
+
* <Button variant="outline">Cancel</Button>
|
|
204
|
+
* </DialogClose>
|
|
205
|
+
* </DialogFooter>
|
|
206
|
+
* </DialogContent>
|
|
207
|
+
* ```
|
|
208
|
+
*/
|
|
209
|
+
function DialogFooter({
|
|
210
|
+
className,
|
|
211
|
+
...props
|
|
212
|
+
}: React.ComponentPropsWithoutRef<"div">) {
|
|
213
|
+
return (
|
|
214
|
+
<div
|
|
215
|
+
data-slot="dialog-footer"
|
|
216
|
+
className={cn(
|
|
217
|
+
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
|
218
|
+
className
|
|
219
|
+
)}
|
|
220
|
+
{...props}
|
|
221
|
+
/>
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* DialogTitle component. The accessible title for the dialog.
|
|
227
|
+
* Required for screen readers and accessibility.
|
|
228
|
+
*
|
|
229
|
+
* @example
|
|
230
|
+
* ```tsx
|
|
231
|
+
* <DialogHeader>
|
|
232
|
+
* <DialogTitle>Delete Item</DialogTitle>
|
|
233
|
+
* </DialogHeader>
|
|
234
|
+
* ```
|
|
235
|
+
*/
|
|
236
|
+
function DialogTitle({
|
|
237
|
+
className,
|
|
238
|
+
...props
|
|
239
|
+
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
|
|
240
|
+
return (
|
|
241
|
+
<DialogPrimitive.Title
|
|
242
|
+
data-slot="dialog-title"
|
|
243
|
+
className={cn(
|
|
244
|
+
"text-lg font-semibold leading-none tracking-tight",
|
|
245
|
+
className
|
|
246
|
+
)}
|
|
247
|
+
{...props}
|
|
248
|
+
/>
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* DialogDescription component. Optional descriptive text for the dialog.
|
|
254
|
+
* Helps provide context to users.
|
|
255
|
+
*
|
|
256
|
+
* @example
|
|
257
|
+
* ```tsx
|
|
258
|
+
* <DialogHeader>
|
|
259
|
+
* <DialogTitle>Confirm Deletion</DialogTitle>
|
|
260
|
+
* <DialogDescription>
|
|
261
|
+
* This action cannot be undone. This will permanently delete the item.
|
|
262
|
+
* </DialogDescription>
|
|
263
|
+
* </DialogHeader>
|
|
264
|
+
* ```
|
|
265
|
+
*/
|
|
266
|
+
function DialogDescription({
|
|
267
|
+
className,
|
|
268
|
+
...props
|
|
269
|
+
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
|
|
270
|
+
return (
|
|
271
|
+
<DialogPrimitive.Description
|
|
272
|
+
data-slot="dialog-description"
|
|
273
|
+
className={cn("text-sm text-muted-foreground", className)}
|
|
274
|
+
{...props}
|
|
275
|
+
/>
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export {
|
|
280
|
+
Dialog,
|
|
281
|
+
DialogClose,
|
|
282
|
+
DialogContent,
|
|
283
|
+
DialogDescription,
|
|
284
|
+
DialogFooter,
|
|
285
|
+
DialogHeader,
|
|
286
|
+
DialogOverlay,
|
|
287
|
+
DialogPortal,
|
|
288
|
+
DialogTitle,
|
|
289
|
+
DialogTrigger,
|
|
290
|
+
};
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
// Copyright 2026 Chesapeake Computing
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"use client";
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { Drawer as DrawerPrimitive } from "vaul";
|
|
8
|
+
|
|
9
|
+
import { cn } from "../../lib/utils";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Drawer component root. Manages the open/closed state of the drawer.
|
|
13
|
+
* Built on top of Vaul for smooth interactions and animations.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```tsx
|
|
17
|
+
* <Drawer>
|
|
18
|
+
* <DrawerTrigger asChild>
|
|
19
|
+
* <Button>Open Drawer</Button>
|
|
20
|
+
* </DrawerTrigger>
|
|
21
|
+
* <DrawerContent>
|
|
22
|
+
* <DrawerHeader>
|
|
23
|
+
* <DrawerTitle>Drawer Title</DrawerTitle>
|
|
24
|
+
* <DrawerDescription>Description text</DrawerDescription>
|
|
25
|
+
* </DrawerHeader>
|
|
26
|
+
* <DrawerFooter>
|
|
27
|
+
* <DrawerClose asChild>
|
|
28
|
+
* <Button>Close</Button>
|
|
29
|
+
* </DrawerClose>
|
|
30
|
+
* </DrawerFooter>
|
|
31
|
+
* </DrawerContent>
|
|
32
|
+
* </Drawer>
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
function Drawer({
|
|
36
|
+
...props
|
|
37
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Root>) {
|
|
38
|
+
return <DrawerPrimitive.Root data-slot="drawer" {...props} />;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Trigger element that opens/closes the drawer.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```tsx
|
|
46
|
+
* <DrawerTrigger asChild>
|
|
47
|
+
* <Button>Open</Button>
|
|
48
|
+
* </DrawerTrigger>
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
function DrawerTrigger({
|
|
52
|
+
...props
|
|
53
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {
|
|
54
|
+
return <DrawerPrimitive.Trigger data-slot="drawer-trigger" {...props} />;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Portal for rendering drawer content outside the DOM hierarchy.
|
|
59
|
+
*/
|
|
60
|
+
function DrawerPortal({
|
|
61
|
+
...props
|
|
62
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Portal>) {
|
|
63
|
+
return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props} />;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Closes the drawer when clicked.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```tsx
|
|
71
|
+
* <DrawerClose asChild>
|
|
72
|
+
* <Button variant="outline">Cancel</Button>
|
|
73
|
+
* </DrawerClose>
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
function DrawerClose({
|
|
77
|
+
...props
|
|
78
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Close>) {
|
|
79
|
+
return <DrawerPrimitive.Close data-slot="drawer-close" {...props} />;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Backdrop overlay behind the drawer content.
|
|
84
|
+
*/
|
|
85
|
+
function DrawerOverlay({
|
|
86
|
+
className,
|
|
87
|
+
...props
|
|
88
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Overlay>) {
|
|
89
|
+
return (
|
|
90
|
+
<DrawerPrimitive.Overlay
|
|
91
|
+
data-slot="drawer-overlay"
|
|
92
|
+
className={cn(
|
|
93
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
|
94
|
+
className
|
|
95
|
+
)}
|
|
96
|
+
{...props}
|
|
97
|
+
/>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Main content container for the drawer.
|
|
103
|
+
* Supports multiple directions: top, bottom, left, right (default: bottom).
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```tsx
|
|
107
|
+
* // Bottom drawer (default)
|
|
108
|
+
* <DrawerContent>
|
|
109
|
+
* <p>Content here</p>
|
|
110
|
+
* </DrawerContent>
|
|
111
|
+
*
|
|
112
|
+
* // Side drawer
|
|
113
|
+
* <Drawer direction="right">
|
|
114
|
+
* <DrawerContent>
|
|
115
|
+
* <p>Side content</p>
|
|
116
|
+
* </DrawerContent>
|
|
117
|
+
* </Drawer>
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
function DrawerContent({
|
|
121
|
+
className,
|
|
122
|
+
children,
|
|
123
|
+
...props
|
|
124
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Content>) {
|
|
125
|
+
return (
|
|
126
|
+
<DrawerPortal data-slot="drawer-portal">
|
|
127
|
+
<DrawerOverlay />
|
|
128
|
+
<DrawerPrimitive.Content
|
|
129
|
+
data-slot="drawer-content"
|
|
130
|
+
className={cn(
|
|
131
|
+
"group/drawer-content bg-background fixed z-50 flex h-auto flex-col",
|
|
132
|
+
"data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg data-[vaul-drawer-direction=top]:border-b",
|
|
133
|
+
"data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t",
|
|
134
|
+
"data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=right]:sm:max-w-sm",
|
|
135
|
+
"data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=left]:sm:max-w-sm",
|
|
136
|
+
className
|
|
137
|
+
)}
|
|
138
|
+
{...props}
|
|
139
|
+
>
|
|
140
|
+
<div className="bg-muted mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block" />
|
|
141
|
+
{children}
|
|
142
|
+
</DrawerPrimitive.Content>
|
|
143
|
+
</DrawerPortal>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Header section of the drawer.
|
|
149
|
+
* Contains title and description with proper spacing.
|
|
150
|
+
*/
|
|
151
|
+
function DrawerHeader({
|
|
152
|
+
className,
|
|
153
|
+
...props
|
|
154
|
+
}: React.ComponentPropsWithoutRef<"div">) {
|
|
155
|
+
return (
|
|
156
|
+
<div
|
|
157
|
+
data-slot="drawer-header"
|
|
158
|
+
className={cn(
|
|
159
|
+
"flex flex-col gap-0.5 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:gap-1.5 md:text-left",
|
|
160
|
+
className
|
|
161
|
+
)}
|
|
162
|
+
{...props}
|
|
163
|
+
/>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Footer section of the drawer.
|
|
169
|
+
* Typically contains action buttons.
|
|
170
|
+
*/
|
|
171
|
+
function DrawerFooter({
|
|
172
|
+
className,
|
|
173
|
+
...props
|
|
174
|
+
}: React.ComponentPropsWithoutRef<"div">) {
|
|
175
|
+
return (
|
|
176
|
+
<div
|
|
177
|
+
data-slot="drawer-footer"
|
|
178
|
+
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
|
|
179
|
+
{...props}
|
|
180
|
+
/>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Title element for the drawer header.
|
|
186
|
+
*/
|
|
187
|
+
function DrawerTitle({
|
|
188
|
+
className,
|
|
189
|
+
...props
|
|
190
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Title>) {
|
|
191
|
+
return (
|
|
192
|
+
<DrawerPrimitive.Title
|
|
193
|
+
data-slot="drawer-title"
|
|
194
|
+
className={cn("text-foreground font-semibold", className)}
|
|
195
|
+
{...props}
|
|
196
|
+
/>
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Description text for the drawer header.
|
|
202
|
+
*/
|
|
203
|
+
function DrawerDescription({
|
|
204
|
+
className,
|
|
205
|
+
...props
|
|
206
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Description>) {
|
|
207
|
+
return (
|
|
208
|
+
<DrawerPrimitive.Description
|
|
209
|
+
data-slot="drawer-description"
|
|
210
|
+
className={cn("text-muted-foreground text-sm", className)}
|
|
211
|
+
{...props}
|
|
212
|
+
/>
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export {
|
|
217
|
+
Drawer,
|
|
218
|
+
DrawerClose,
|
|
219
|
+
DrawerContent,
|
|
220
|
+
DrawerDescription,
|
|
221
|
+
DrawerFooter,
|
|
222
|
+
DrawerHeader,
|
|
223
|
+
DrawerOverlay,
|
|
224
|
+
DrawerPortal,
|
|
225
|
+
DrawerTitle,
|
|
226
|
+
DrawerTrigger,
|
|
227
|
+
};
|