@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.
Files changed (82) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/LICENSE +190 -0
  3. package/README.md +440 -0
  4. package/components/ui/avatar.tsx +87 -0
  5. package/components/ui/badge.tsx +49 -0
  6. package/components/ui/badgeVariants.ts +26 -0
  7. package/components/ui/button-group.tsx +67 -0
  8. package/components/ui/button.tsx +70 -0
  9. package/components/ui/buttonGroupVariants.ts +23 -0
  10. package/components/ui/buttonVariants.ts +38 -0
  11. package/components/ui/calendar.tsx +220 -0
  12. package/components/ui/card.tsx +162 -0
  13. package/components/ui/dialog.tsx +290 -0
  14. package/components/ui/drawer.tsx +227 -0
  15. package/components/ui/dropdown-menu.tsx +260 -0
  16. package/components/ui/index.ts +86 -0
  17. package/components/ui/input.tsx +59 -0
  18. package/components/ui/label.tsx +47 -0
  19. package/components/ui/location-input.md +194 -0
  20. package/components/ui/location-input.tsx +384 -0
  21. package/components/ui/popover.tsx +110 -0
  22. package/components/ui/select.tsx +306 -0
  23. package/components/ui/separator.tsx +50 -0
  24. package/components/ui/sonner-api.ts +8 -0
  25. package/components/ui/sonner.tsx +69 -0
  26. package/components/ui/textarea.tsx +53 -0
  27. package/components/ui/tooltip.tsx +125 -0
  28. package/components/ux/ActivityAnimation.tsx +135 -0
  29. package/components/ux/EmptyState.tsx +22 -0
  30. package/components/ux/FeedbackDrawer.tsx +141 -0
  31. package/components/ux/FireAnimation.tsx +189 -0
  32. package/components/ux/FriendList.tsx +111 -0
  33. package/components/ux/FriendRequestList.tsx +104 -0
  34. package/components/ux/GifSearch.tsx +264 -0
  35. package/components/ux/ImageUpload.tsx +829 -0
  36. package/components/ux/SelectedUsersList.tsx +91 -0
  37. package/components/ux/StandardLayout/PageLayout.tsx +107 -0
  38. package/components/ux/StandardLayout/ProfilePage.tsx +452 -0
  39. package/components/ux/StandardLayout/StandardLayout.tsx +345 -0
  40. package/components/ux/StandardLayout/index.ts +22 -0
  41. package/components/ux/TutorialCarousel.tsx +128 -0
  42. package/components/ux/UserSearch.tsx +324 -0
  43. package/components/ux/UserSelector.tsx +116 -0
  44. package/components/ux/auth/AuthSelector.tsx +154 -0
  45. package/components/ux/auth/MultiAuthProvider.tsx +196 -0
  46. package/components/ux/auth/README.md +317 -0
  47. package/components/ux/auth/context.ts +46 -0
  48. package/components/ux/auth/github/AuthCallback.tsx +177 -0
  49. package/components/ux/auth/github/AuthProvider.tsx +111 -0
  50. package/components/ux/auth/github/README.md +208 -0
  51. package/components/ux/auth/github/client.ts +127 -0
  52. package/components/ux/auth/github/config.ts +68 -0
  53. package/components/ux/auth/github/context.ts +34 -0
  54. package/components/ux/auth/github/default-client.ts +74 -0
  55. package/components/ux/auth/github/index.ts +43 -0
  56. package/components/ux/auth/github/types.ts +44 -0
  57. package/components/ux/auth/github/useGitHubAuth.ts +16 -0
  58. package/components/ux/auth/google/AuthCallback.tsx +170 -0
  59. package/components/ux/auth/google/AuthProvider.tsx +108 -0
  60. package/components/ux/auth/google/README.md +450 -0
  61. package/components/ux/auth/google/client.ts +109 -0
  62. package/components/ux/auth/google/config.ts +65 -0
  63. package/components/ux/auth/google/context.ts +34 -0
  64. package/components/ux/auth/google/default-client.ts +69 -0
  65. package/components/ux/auth/google/index.ts +43 -0
  66. package/components/ux/auth/google/types.ts +42 -0
  67. package/components/ux/auth/google/useGoogleAuth.ts +16 -0
  68. package/components/ux/auth/index.ts +72 -0
  69. package/components/ux/auth/useAuth.ts +16 -0
  70. package/components/ux/index.ts +46 -0
  71. package/components/ux/theme-provider-context.ts +20 -0
  72. package/components/ux/theme-provider.tsx +114 -0
  73. package/components/ux/useTheme.ts +42 -0
  74. package/index.ts +29 -0
  75. package/lib/utils.ts +57 -0
  76. package/package.json +170 -0
  77. package/styles/base.css +38 -0
  78. package/styles/components.css +54 -0
  79. package/styles/index.css +7 -0
  80. package/styles/tokens.css +189 -0
  81. package/tailwind.preset.ts +169 -0
  82. 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,8 @@
1
+ // Copyright 2026 Chesapeake Computing
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import * as React from "react";
5
+ import { Toaster as Sonner } from "sonner";
6
+
7
+ export { toast } from "sonner";
8
+ export type ToasterProps = React.ComponentProps<typeof Sonner>;
@@ -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
+ };