@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,306 @@
|
|
|
1
|
+
// Copyright 2026 Chesapeake Computing
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import * as SelectPrimitive from "@radix-ui/react-select";
|
|
6
|
+
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
|
|
7
|
+
|
|
8
|
+
import { cn } from "../../lib/utils";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Root component for the Select dropdown.
|
|
12
|
+
* Manages the open/closed state and selected value.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```tsx
|
|
16
|
+
* <Select>
|
|
17
|
+
* <SelectTrigger className="w-[180px]">
|
|
18
|
+
* <SelectValue placeholder="Select a fruit" />
|
|
19
|
+
* </SelectTrigger>
|
|
20
|
+
* <SelectContent>
|
|
21
|
+
* <SelectItem value="apple">Apple</SelectItem>
|
|
22
|
+
* <SelectItem value="banana">Banana</SelectItem>
|
|
23
|
+
* <SelectItem value="orange">Orange</SelectItem>
|
|
24
|
+
* </SelectContent>
|
|
25
|
+
* </Select>
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
function Select({
|
|
29
|
+
...props
|
|
30
|
+
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
|
|
31
|
+
return <SelectPrimitive.Root data-slot="select" {...props} />;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Group component for organizing select items.
|
|
36
|
+
* Used with SelectLabel to create labeled groups.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```tsx
|
|
40
|
+
* <SelectContent>
|
|
41
|
+
* <SelectGroup>
|
|
42
|
+
* <SelectLabel>Fruits</SelectLabel>
|
|
43
|
+
* <SelectItem value="apple">Apple</SelectItem>
|
|
44
|
+
* <SelectItem value="banana">Banana</SelectItem>
|
|
45
|
+
* </SelectGroup>
|
|
46
|
+
* </SelectContent>
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
function SelectGroup({
|
|
50
|
+
...props
|
|
51
|
+
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
|
|
52
|
+
return <SelectPrimitive.Group data-slot="select-group" {...props} />;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Displays the currently selected value or placeholder.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```tsx
|
|
60
|
+
* <SelectValue placeholder="Select an option" />
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
function SelectValue({
|
|
64
|
+
...props
|
|
65
|
+
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
|
|
66
|
+
return <SelectPrimitive.Value data-slot="select-value" {...props} />;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Trigger button that opens the select dropdown.
|
|
71
|
+
* Contains the SelectValue and dropdown arrow.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```tsx
|
|
75
|
+
* <SelectTrigger className="w-[200px]">
|
|
76
|
+
* <SelectValue placeholder="Choose..." />
|
|
77
|
+
* </SelectTrigger>
|
|
78
|
+
*
|
|
79
|
+
* // Small size
|
|
80
|
+
* <SelectTrigger size="sm">
|
|
81
|
+
* <SelectValue placeholder="Small" />
|
|
82
|
+
* </SelectTrigger>
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
function SelectTrigger({
|
|
86
|
+
className,
|
|
87
|
+
size = "default",
|
|
88
|
+
children,
|
|
89
|
+
...props
|
|
90
|
+
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
|
|
91
|
+
size?: "sm" | "default";
|
|
92
|
+
className?: string;
|
|
93
|
+
}) {
|
|
94
|
+
return (
|
|
95
|
+
<SelectPrimitive.Trigger
|
|
96
|
+
data-slot="select-trigger"
|
|
97
|
+
data-size={size}
|
|
98
|
+
className={cn(
|
|
99
|
+
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
100
|
+
className
|
|
101
|
+
)}
|
|
102
|
+
{...props}
|
|
103
|
+
>
|
|
104
|
+
{children}
|
|
105
|
+
<SelectPrimitive.Icon asChild>
|
|
106
|
+
<ChevronDownIcon className="size-4 opacity-50" />
|
|
107
|
+
</SelectPrimitive.Icon>
|
|
108
|
+
</SelectPrimitive.Trigger>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Content container for the select dropdown.
|
|
114
|
+
* Displays when the select is open.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```tsx
|
|
118
|
+
* <SelectContent>
|
|
119
|
+
* <SelectItem value="1">Option 1</SelectItem>
|
|
120
|
+
* <SelectItem value="2">Option 2</SelectItem>
|
|
121
|
+
* </SelectContent>
|
|
122
|
+
*
|
|
123
|
+
* // Position as popper
|
|
124
|
+
* <SelectContent position="popper">
|
|
125
|
+
* <SelectItem value="1">Option 1</SelectItem>
|
|
126
|
+
* </SelectContent>
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
function SelectContent({
|
|
130
|
+
className,
|
|
131
|
+
children,
|
|
132
|
+
position = "item-aligned",
|
|
133
|
+
align = "center",
|
|
134
|
+
...props
|
|
135
|
+
}: React.ComponentProps<typeof SelectPrimitive.Content> & {
|
|
136
|
+
className?: string;
|
|
137
|
+
}) {
|
|
138
|
+
return (
|
|
139
|
+
<SelectPrimitive.Portal>
|
|
140
|
+
<SelectPrimitive.Content
|
|
141
|
+
data-slot="select-content"
|
|
142
|
+
className={cn(
|
|
143
|
+
"bg-popover text-popover-foreground 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-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
|
|
144
|
+
position === "popper" &&
|
|
145
|
+
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
|
146
|
+
className
|
|
147
|
+
)}
|
|
148
|
+
position={position}
|
|
149
|
+
align={align}
|
|
150
|
+
{...props}
|
|
151
|
+
>
|
|
152
|
+
<SelectScrollUpButton />
|
|
153
|
+
<SelectPrimitive.Viewport
|
|
154
|
+
className={cn(
|
|
155
|
+
"p-1",
|
|
156
|
+
position === "popper" &&
|
|
157
|
+
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
|
|
158
|
+
)}
|
|
159
|
+
>
|
|
160
|
+
{children}
|
|
161
|
+
</SelectPrimitive.Viewport>
|
|
162
|
+
<SelectScrollDownButton />
|
|
163
|
+
</SelectPrimitive.Content>
|
|
164
|
+
</SelectPrimitive.Portal>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Label for a group of select items.
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* ```tsx
|
|
173
|
+
* <SelectGroup>
|
|
174
|
+
* <SelectLabel>Fruits</SelectLabel>
|
|
175
|
+
* <SelectItem value="apple">Apple</SelectItem>
|
|
176
|
+
* </SelectGroup>
|
|
177
|
+
* ```
|
|
178
|
+
*/
|
|
179
|
+
function SelectLabel({
|
|
180
|
+
className,
|
|
181
|
+
...props
|
|
182
|
+
}: React.ComponentProps<typeof SelectPrimitive.Label> & {
|
|
183
|
+
className?: string;
|
|
184
|
+
}) {
|
|
185
|
+
return (
|
|
186
|
+
<SelectPrimitive.Label
|
|
187
|
+
data-slot="select-label"
|
|
188
|
+
className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
|
|
189
|
+
{...props}
|
|
190
|
+
/>
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Individual selectable item within the dropdown.
|
|
196
|
+
*
|
|
197
|
+
* @example
|
|
198
|
+
* ```tsx
|
|
199
|
+
* <SelectItem value="apple">Apple</SelectItem>
|
|
200
|
+
* ```
|
|
201
|
+
*/
|
|
202
|
+
function SelectItem({
|
|
203
|
+
className,
|
|
204
|
+
children,
|
|
205
|
+
...props
|
|
206
|
+
}: React.ComponentProps<typeof SelectPrimitive.Item> & {
|
|
207
|
+
className?: string;
|
|
208
|
+
}) {
|
|
209
|
+
return (
|
|
210
|
+
<SelectPrimitive.Item
|
|
211
|
+
data-slot="select-item"
|
|
212
|
+
className={cn(
|
|
213
|
+
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
|
214
|
+
className
|
|
215
|
+
)}
|
|
216
|
+
{...props}
|
|
217
|
+
>
|
|
218
|
+
<span
|
|
219
|
+
data-slot="select-item-indicator"
|
|
220
|
+
className="absolute right-2 flex size-3.5 items-center justify-center"
|
|
221
|
+
>
|
|
222
|
+
<SelectPrimitive.ItemIndicator>
|
|
223
|
+
<CheckIcon className="size-4" />
|
|
224
|
+
</SelectPrimitive.ItemIndicator>
|
|
225
|
+
</span>
|
|
226
|
+
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
|
227
|
+
</SelectPrimitive.Item>
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Visual separator between groups of items.
|
|
233
|
+
*/
|
|
234
|
+
function SelectSeparator({
|
|
235
|
+
className,
|
|
236
|
+
...props
|
|
237
|
+
}: React.ComponentProps<typeof SelectPrimitive.Separator> & {
|
|
238
|
+
className?: string;
|
|
239
|
+
}) {
|
|
240
|
+
return (
|
|
241
|
+
<SelectPrimitive.Separator
|
|
242
|
+
data-slot="select-separator"
|
|
243
|
+
className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
|
|
244
|
+
{...props}
|
|
245
|
+
/>
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Scroll up button displayed when content overflows.
|
|
251
|
+
*/
|
|
252
|
+
function SelectScrollUpButton({
|
|
253
|
+
className,
|
|
254
|
+
...props
|
|
255
|
+
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton> & {
|
|
256
|
+
className?: string;
|
|
257
|
+
}) {
|
|
258
|
+
return (
|
|
259
|
+
<SelectPrimitive.ScrollUpButton
|
|
260
|
+
data-slot="select-scroll-up-button"
|
|
261
|
+
className={cn(
|
|
262
|
+
"flex cursor-default items-center justify-center py-1",
|
|
263
|
+
className
|
|
264
|
+
)}
|
|
265
|
+
{...props}
|
|
266
|
+
>
|
|
267
|
+
<ChevronUpIcon className="size-4" />
|
|
268
|
+
</SelectPrimitive.ScrollUpButton>
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Scroll down button displayed when content overflows.
|
|
274
|
+
*/
|
|
275
|
+
function SelectScrollDownButton({
|
|
276
|
+
className,
|
|
277
|
+
...props
|
|
278
|
+
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton> & {
|
|
279
|
+
className?: string;
|
|
280
|
+
}) {
|
|
281
|
+
return (
|
|
282
|
+
<SelectPrimitive.ScrollDownButton
|
|
283
|
+
data-slot="select-scroll-down-button"
|
|
284
|
+
className={cn(
|
|
285
|
+
"flex cursor-default items-center justify-center py-1",
|
|
286
|
+
className
|
|
287
|
+
)}
|
|
288
|
+
{...props}
|
|
289
|
+
>
|
|
290
|
+
<ChevronDownIcon className="size-4" />
|
|
291
|
+
</SelectPrimitive.ScrollDownButton>
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export {
|
|
296
|
+
Select,
|
|
297
|
+
SelectContent,
|
|
298
|
+
SelectGroup,
|
|
299
|
+
SelectItem,
|
|
300
|
+
SelectLabel,
|
|
301
|
+
SelectScrollDownButton,
|
|
302
|
+
SelectScrollUpButton,
|
|
303
|
+
SelectSeparator,
|
|
304
|
+
SelectTrigger,
|
|
305
|
+
SelectValue,
|
|
306
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Copyright 2026 Chesapeake Computing
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import * as SeparatorPrimitive from "@radix-ui/react-separator";
|
|
6
|
+
|
|
7
|
+
import { cn } from "../../lib/utils";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A visual separator component for dividing content.
|
|
11
|
+
*
|
|
12
|
+
* Can be rendered horizontally or vertically and is decorative by default
|
|
13
|
+
* (hidden from screen readers). Built on Radix UI's Separator primitive.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```tsx
|
|
17
|
+
* // Horizontal separator (default)
|
|
18
|
+
* <Separator />
|
|
19
|
+
*
|
|
20
|
+
* // Vertical separator
|
|
21
|
+
* <Separator orientation="vertical" />
|
|
22
|
+
*
|
|
23
|
+
* // With custom styling
|
|
24
|
+
* <Separator className="my-4" />
|
|
25
|
+
*
|
|
26
|
+
* // Non-decorative (accessible to screen readers)
|
|
27
|
+
* <Separator decorative={false} />
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
function Separator({
|
|
31
|
+
className,
|
|
32
|
+
orientation,
|
|
33
|
+
decorative,
|
|
34
|
+
...props
|
|
35
|
+
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
|
|
36
|
+
return (
|
|
37
|
+
<SeparatorPrimitive.Root
|
|
38
|
+
data-slot="separator"
|
|
39
|
+
decorative={decorative}
|
|
40
|
+
orientation={orientation}
|
|
41
|
+
className={cn(
|
|
42
|
+
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
|
|
43
|
+
className
|
|
44
|
+
)}
|
|
45
|
+
{...props}
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export { Separator };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Copyright 2026 Chesapeake Computing
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/** @jsxImportSource react */
|
|
5
|
+
|
|
6
|
+
import { Toaster as Sonner } from "sonner";
|
|
7
|
+
|
|
8
|
+
import type { ToasterProps } from "./sonner-api.ts";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Toast notification component for displaying temporary messages.
|
|
12
|
+
*
|
|
13
|
+
* Built on top of Sonner for a polished toast experience.
|
|
14
|
+
* Positioned in the top-right corner by default.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```tsx
|
|
18
|
+
* // Add Toaster to your app root
|
|
19
|
+
* function App() {
|
|
20
|
+
* return (
|
|
21
|
+
* <>
|
|
22
|
+
* <YourApp />
|
|
23
|
+
* <Toaster />
|
|
24
|
+
* </>
|
|
25
|
+
* );
|
|
26
|
+
* }
|
|
27
|
+
*
|
|
28
|
+
* // Show toast anywhere in your app
|
|
29
|
+
* import { toast } from "sonner";
|
|
30
|
+
*
|
|
31
|
+
* toast.success("Operation successful!");
|
|
32
|
+
* toast.error("Something went wrong");
|
|
33
|
+
* toast.info("New update available");
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```tsx
|
|
38
|
+
* // Custom toast with action
|
|
39
|
+
* toast("Event has been created", {
|
|
40
|
+
* description: "Sunday, December 03, 2023 at 9:00 AM",
|
|
41
|
+
* action: {
|
|
42
|
+
* label: "Undo",
|
|
43
|
+
* onClick: () => console.log("Undo"),
|
|
44
|
+
* },
|
|
45
|
+
* });
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
function Toaster(props: ToasterProps) {
|
|
49
|
+
return (
|
|
50
|
+
<Sonner
|
|
51
|
+
position="top-right"
|
|
52
|
+
className="toaster group"
|
|
53
|
+
toastOptions={{
|
|
54
|
+
classNames: {
|
|
55
|
+
toast:
|
|
56
|
+
"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
|
|
57
|
+
description: "group-[.toast]:text-muted-foreground",
|
|
58
|
+
actionButton:
|
|
59
|
+
"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
|
|
60
|
+
cancelButton:
|
|
61
|
+
"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
|
|
62
|
+
},
|
|
63
|
+
}}
|
|
64
|
+
{...props}
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export { Toaster };
|
|
@@ -0,0 +1,53 @@
|
|
|
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
|
+
* Textarea component for multi-line text input.
|
|
10
|
+
*
|
|
11
|
+
* Provides consistent styling with focus states, disabled states, and error states.
|
|
12
|
+
* Automatically adjusts height based on content when used with appropriate CSS.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```tsx
|
|
16
|
+
* // Basic textarea
|
|
17
|
+
* <Textarea placeholder="Enter your message..." />
|
|
18
|
+
*
|
|
19
|
+
* // With rows
|
|
20
|
+
* <Textarea rows={5} placeholder="Longer message..." />
|
|
21
|
+
*
|
|
22
|
+
* // With label
|
|
23
|
+
* <div>
|
|
24
|
+
* <Label htmlFor="description">Description</Label>
|
|
25
|
+
* <Textarea id="description" />
|
|
26
|
+
* </div>
|
|
27
|
+
*
|
|
28
|
+
* // Disabled
|
|
29
|
+
* <Textarea disabled placeholder="Disabled textarea" />
|
|
30
|
+
*
|
|
31
|
+
* // With error state
|
|
32
|
+
* <Textarea aria-invalid="true" />
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
const Textarea = React.forwardRef<
|
|
36
|
+
HTMLTextAreaElement,
|
|
37
|
+
React.ComponentPropsWithoutRef<"textarea">
|
|
38
|
+
>(({ className, ...props }, ref) => {
|
|
39
|
+
return (
|
|
40
|
+
<textarea
|
|
41
|
+
ref={ref}
|
|
42
|
+
data-slot="textarea"
|
|
43
|
+
className={cn(
|
|
44
|
+
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
45
|
+
className
|
|
46
|
+
)}
|
|
47
|
+
{...props}
|
|
48
|
+
/>
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
Textarea.displayName = "Textarea";
|
|
52
|
+
|
|
53
|
+
export { Textarea };
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// Copyright 2026 Chesapeake Computing
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
6
|
+
|
|
7
|
+
import { cn } from "../../lib/utils";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* TooltipProvider component. Must wrap all Tooltip components.
|
|
11
|
+
* Provides the context for tooltip components.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* <TooltipProvider>
|
|
16
|
+
* <App />
|
|
17
|
+
* </TooltipProvider>
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
interface TooltipProviderProps {
|
|
21
|
+
delayDuration?: number;
|
|
22
|
+
children: React.ReactNode;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function TooltipProvider({
|
|
26
|
+
delayDuration = 300,
|
|
27
|
+
...props
|
|
28
|
+
}: TooltipProviderProps) {
|
|
29
|
+
return <TooltipPrimitive.Provider delayDuration={delayDuration} {...props} />;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Tooltip component root. Provides the context for tooltip components.
|
|
34
|
+
* Built on top of Radix UI Tooltip primitives for accessibility.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```tsx
|
|
38
|
+
* import { Tooltip, TooltipTrigger, TooltipContent } from "@shared/components/ui/tooltip"
|
|
39
|
+
* import { Button } from "@shared/components/ui/button"
|
|
40
|
+
*
|
|
41
|
+
* function MyTooltip() {
|
|
42
|
+
* return (
|
|
43
|
+
* <Tooltip>
|
|
44
|
+
* <TooltipTrigger asChild>
|
|
45
|
+
* <Button>Hover me</Button>
|
|
46
|
+
* </TooltipTrigger>
|
|
47
|
+
* <TooltipContent>
|
|
48
|
+
* <p>This is a tooltip</p>
|
|
49
|
+
* </TooltipContent>
|
|
50
|
+
* </Tooltip>
|
|
51
|
+
* )
|
|
52
|
+
* }
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
function Tooltip({
|
|
56
|
+
...props
|
|
57
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
|
|
58
|
+
return <TooltipPrimitive.Root {...props} />;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* TooltipTrigger component. Wraps the element that shows the tooltip on hover.
|
|
63
|
+
*/
|
|
64
|
+
function TooltipTrigger({
|
|
65
|
+
...props
|
|
66
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
|
67
|
+
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* TooltipPortal component. Portals the tooltip content outside the DOM hierarchy.
|
|
72
|
+
*/
|
|
73
|
+
function TooltipPortal({
|
|
74
|
+
...props
|
|
75
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Portal>) {
|
|
76
|
+
return <TooltipPrimitive.Portal data-slot="tooltip-portal" {...props} />;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* TooltipContent component. The main content container for the tooltip.
|
|
81
|
+
* Includes the portal by default.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```tsx
|
|
85
|
+
* <Tooltip>
|
|
86
|
+
* <TooltipTrigger>Hover me</TooltipTrigger>
|
|
87
|
+
* <TooltipContent>
|
|
88
|
+
* <p>Tooltip text</p>
|
|
89
|
+
* </TooltipContent>
|
|
90
|
+
* </Tooltip>
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
interface TooltipContentProps {
|
|
94
|
+
className?: string;
|
|
95
|
+
sideOffset?: number;
|
|
96
|
+
children?: React.ReactNode;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function TooltipContent({
|
|
100
|
+
className,
|
|
101
|
+
sideOffset = 4,
|
|
102
|
+
...props
|
|
103
|
+
}: TooltipContentProps) {
|
|
104
|
+
return (
|
|
105
|
+
<TooltipPortal>
|
|
106
|
+
<TooltipPrimitive.Content
|
|
107
|
+
data-slot="tooltip-content"
|
|
108
|
+
sideOffset={sideOffset}
|
|
109
|
+
className={cn(
|
|
110
|
+
"z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
111
|
+
className ?? ""
|
|
112
|
+
)}
|
|
113
|
+
{...props}
|
|
114
|
+
/>
|
|
115
|
+
</TooltipPortal>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export {
|
|
120
|
+
Tooltip,
|
|
121
|
+
TooltipProvider,
|
|
122
|
+
TooltipTrigger,
|
|
123
|
+
TooltipContent,
|
|
124
|
+
TooltipPortal,
|
|
125
|
+
};
|