@ponti-studios/ui 0.1.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.
Files changed (108) hide show
  1. package/README.md +27 -0
  2. package/dist/index.js +2118 -0
  3. package/dist/index.js.map +1 -0
  4. package/package.json +130 -0
  5. package/src/animations.css +437 -0
  6. package/src/components/data-display/accordion.stories.tsx +57 -0
  7. package/src/components/data-display/accordion.tsx +65 -0
  8. package/src/components/data-display/filter-chip.tsx +41 -0
  9. package/src/components/data-display/index.ts +13 -0
  10. package/src/components/data-display/table.stories.tsx +75 -0
  11. package/src/components/data-display/table.tsx +84 -0
  12. package/src/components/feedback/alert.stories.tsx +51 -0
  13. package/src/components/feedback/alert.tsx +60 -0
  14. package/src/components/feedback/empty-state.tsx +66 -0
  15. package/src/components/feedback/index.ts +6 -0
  16. package/src/components/feedback/progress.stories.tsx +37 -0
  17. package/src/components/feedback/progress.tsx +30 -0
  18. package/src/components/feedback/spinner.stories.tsx +52 -0
  19. package/src/components/feedback/spinner.tsx +48 -0
  20. package/src/components/forms/calendar.stories.tsx +30 -0
  21. package/src/components/forms/calendar.tsx +67 -0
  22. package/src/components/forms/date-picker.stories.tsx +48 -0
  23. package/src/components/forms/date-picker.tsx +60 -0
  24. package/src/components/forms/field.stories.tsx +62 -0
  25. package/src/components/forms/field.tsx +65 -0
  26. package/src/components/forms/field.types.ts +7 -0
  27. package/src/components/forms/index.ts +24 -0
  28. package/src/components/forms/input.stories.tsx +47 -0
  29. package/src/components/forms/input.tsx +20 -0
  30. package/src/components/forms/radio-group.stories.tsx +48 -0
  31. package/src/components/forms/radio-group.tsx +38 -0
  32. package/src/components/forms/select.stories.tsx +88 -0
  33. package/src/components/forms/select.tsx +147 -0
  34. package/src/components/forms/slider.stories.tsx +67 -0
  35. package/src/components/forms/slider.tsx +25 -0
  36. package/src/components/forms/stepper.stories.tsx +111 -0
  37. package/src/components/forms/stepper.tsx +66 -0
  38. package/src/components/forms/switch.stories.tsx +144 -0
  39. package/src/components/forms/switch.tsx +36 -0
  40. package/src/components/forms/text-field.stories.tsx +87 -0
  41. package/src/components/forms/text-field.tsx +40 -0
  42. package/src/components/forms/text-field.types.ts +9 -0
  43. package/src/components/forms/textarea.stories.tsx +150 -0
  44. package/src/components/forms/textarea.tsx +18 -0
  45. package/src/components/layout/index.ts +2 -0
  46. package/src/components/layout/scroll-area.stories.tsx +83 -0
  47. package/src/components/layout/scroll-area.tsx +40 -0
  48. package/src/components/navigation/app-navigation.stories.tsx +81 -0
  49. package/src/components/navigation/app-navigation.tsx +155 -0
  50. package/src/components/navigation/index.ts +3 -0
  51. package/src/components/navigation/tabs.stories.tsx +69 -0
  52. package/src/components/navigation/tabs.tsx +49 -0
  53. package/src/components/overlays/alert-dialog.stories.tsx +45 -0
  54. package/src/components/overlays/alert-dialog.tsx +119 -0
  55. package/src/components/overlays/command.stories.tsx +65 -0
  56. package/src/components/overlays/command.tsx +130 -0
  57. package/src/components/overlays/dialog.stories.tsx +111 -0
  58. package/src/components/overlays/dialog.tsx +162 -0
  59. package/src/components/overlays/dropdown-menu.stories.tsx +79 -0
  60. package/src/components/overlays/dropdown-menu.tsx +214 -0
  61. package/src/components/overlays/index.ts +65 -0
  62. package/src/components/overlays/popover.stories.tsx +39 -0
  63. package/src/components/overlays/popover.tsx +40 -0
  64. package/src/components/overlays/sheet.stories.tsx +68 -0
  65. package/src/components/overlays/sheet.tsx +98 -0
  66. package/src/components/primitives/avatar.stories.tsx +71 -0
  67. package/src/components/primitives/avatar.tsx +132 -0
  68. package/src/components/primitives/badge.stories.tsx +33 -0
  69. package/src/components/primitives/badge.tsx +44 -0
  70. package/src/components/primitives/button.stories.tsx +113 -0
  71. package/src/components/primitives/button.tsx +95 -0
  72. package/src/components/primitives/button.types.ts +10 -0
  73. package/src/components/primitives/card.stories.tsx +59 -0
  74. package/src/components/primitives/card.tsx +69 -0
  75. package/src/components/primitives/index.ts +13 -0
  76. package/src/components/primitives/label.stories.tsx +47 -0
  77. package/src/components/primitives/label.tsx +21 -0
  78. package/src/constants/chart-colors.ts +39 -0
  79. package/src/env.d.ts +9 -0
  80. package/src/hooks/index.ts +6 -0
  81. package/src/hooks/sort.types.ts +12 -0
  82. package/src/hooks/use-api-client.ts +122 -0
  83. package/src/hooks/use-debounced-value.ts +12 -0
  84. package/src/hooks/use-filter-state.ts +68 -0
  85. package/src/hooks/use-media-query.ts +65 -0
  86. package/src/hooks/use-mobile.tsx +19 -0
  87. package/src/index.ts +25 -0
  88. package/src/lib/auth.ts +5 -0
  89. package/src/lib/clipboard.ts +8 -0
  90. package/src/lib/create-memory-storage.ts +24 -0
  91. package/src/lib/index.ts +4 -0
  92. package/src/lib/utils.ts +6 -0
  93. package/src/storybook/controls.ts +117 -0
  94. package/src/storybook/options.ts +52 -0
  95. package/src/styles.css +1052 -0
  96. package/src/tokens/color-systems.stories.tsx +133 -0
  97. package/src/tokens/colors.ts +156 -0
  98. package/src/tokens/foundations.stories.tsx +151 -0
  99. package/src/tokens/index.ts +16 -0
  100. package/src/tokens/layout.stories.tsx +118 -0
  101. package/src/tokens/motion.ts +51 -0
  102. package/src/tokens/radii.ts +10 -0
  103. package/src/tokens/shadows.ts +34 -0
  104. package/src/tokens/spacing.ts +30 -0
  105. package/src/tokens/typography.ts +42 -0
  106. package/src/translations/en.ts +34 -0
  107. package/src/translations/index.ts +32 -0
  108. package/src/types/upload.ts +42 -0
@@ -0,0 +1,83 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { ScrollArea } from "./scroll-area";
3
+
4
+ const meta = {
5
+ title: "Layout/ScrollArea",
6
+ component: ScrollArea,
7
+ tags: ["autodocs"],
8
+ } satisfies Meta<typeof ScrollArea>;
9
+
10
+ export default meta;
11
+ type Story = StoryObj<typeof meta>;
12
+
13
+ const placeholderItems = Array.from({ length: 8 }, (_, i) => ({
14
+ id: i + 1,
15
+ label: `Item ${i + 1}`,
16
+ bg: `hsl(${i * 45}, 70%, 80%)`,
17
+ }));
18
+
19
+ export const Default: Story = {
20
+ args: {
21
+ className: "gap-4 p-4 max-w-lg",
22
+ children: placeholderItems.map((item) => (
23
+ <div
24
+ key={item.id}
25
+ className="flex size-40 shrink-0 items-center justify-center rounded-lg"
26
+ style={{ backgroundColor: item.bg }}
27
+ >
28
+ <span className="text-primary font-medium">{item.label}</span>
29
+ </div>
30
+ )),
31
+ },
32
+ };
33
+
34
+ export const WithSnap: Story = {
35
+ args: {
36
+ className: "gap-4 p-4 max-w-lg",
37
+ snap: "start",
38
+ children: placeholderItems.map((item) => (
39
+ <div
40
+ key={item.id}
41
+ className="flex size-48 shrink-0 items-center justify-center rounded-lg"
42
+ style={{ backgroundColor: item.bg }}
43
+ >
44
+ <span className="text-primary font-medium">{item.label}</span>
45
+ </div>
46
+ )),
47
+ },
48
+ };
49
+
50
+ export const Vertical: Story = {
51
+ args: {
52
+ orientation: "vertical",
53
+ className: "gap-3 p-4 max-h-64",
54
+ children: placeholderItems.map((item) => (
55
+ <div
56
+ key={item.id}
57
+ className="flex h-24 shrink-0 items-center justify-center rounded-lg"
58
+ style={{ backgroundColor: item.bg }}
59
+ >
60
+ <span className="text-primary font-medium">{item.label}</span>
61
+ </div>
62
+ )),
63
+ },
64
+ };
65
+
66
+ export const ScreenshotsGallery: Story = {
67
+ render: () => (
68
+ <div className="flex max-w-2xl flex-col gap-3">
69
+ <h2 className="text-lg font-semibold">Screenshots</h2>
70
+ <ScrollArea className="gap-4" snap="start">
71
+ {placeholderItems.map((item) => (
72
+ <div
73
+ key={item.id}
74
+ className="flex aspect-video w-72 shrink-0 items-center justify-center overflow-hidden rounded-xl border"
75
+ style={{ backgroundColor: item.bg }}
76
+ >
77
+ <span className="text-primary/70 text-sm">{item.label}</span>
78
+ </div>
79
+ ))}
80
+ </ScrollArea>
81
+ </div>
82
+ ),
83
+ };
@@ -0,0 +1,40 @@
1
+ import * as React from "react";
2
+ import { cn } from "../../lib/utils";
3
+
4
+ interface ScrollAreaProps extends React.HTMLAttributes<HTMLDivElement> {
5
+ orientation?: "horizontal" | "vertical";
6
+ snap?: "start" | "center" | "none";
7
+ }
8
+
9
+ const ScrollArea = React.forwardRef<HTMLDivElement, ScrollAreaProps>(
10
+ ({ className, children, orientation = "horizontal", snap = "none", ...props }, ref) => {
11
+ return (
12
+ <div
13
+ ref={ref}
14
+ className={cn(
15
+ "scrollbar-hide flex overflow-auto",
16
+ orientation === "horizontal" && "flex-row",
17
+ orientation === "vertical" && "flex-col",
18
+ snap !== "none" && orientation === "horizontal" && "snap-x snap-mandatory",
19
+ snap !== "none" && orientation === "vertical" && "snap-y snap-mandatory",
20
+ className,
21
+ )}
22
+ {...props}
23
+ >
24
+ {React.Children.map(children, (child) => {
25
+ if (!React.isValidElement(child)) return child;
26
+ if (snap !== "none") {
27
+ return React.cloneElement(child as React.ReactElement<{ className?: string }>, {
28
+ className: cn(`snap-${snap}`, (child.props as { className?: string }).className),
29
+ });
30
+ }
31
+ return child;
32
+ })}
33
+ </div>
34
+ );
35
+ },
36
+ );
37
+ ScrollArea.displayName = "ScrollArea";
38
+
39
+ export { ScrollArea };
40
+ export type { ScrollAreaProps };
@@ -0,0 +1,81 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import * as React from "react";
3
+
4
+ import { AppNavigation } from "./app-navigation";
5
+
6
+ const meta: Meta<typeof AppNavigation> = {
7
+ title: "Navigation/AppNavigation",
8
+ component: AppNavigation,
9
+ tags: ["autodocs"],
10
+ parameters: {
11
+ layout: "fullscreen",
12
+ },
13
+ };
14
+
15
+ export default meta;
16
+ type Story = StoryObj<typeof meta>;
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // Shared helpers
20
+ // ---------------------------------------------------------------------------
21
+
22
+ const defaultLinks: Array<{ href: string; label: string }> = [
23
+ { href: "/features", label: "Features" },
24
+ { href: "/pricing", label: "Pricing" },
25
+ { href: "/blog", label: "Blog" },
26
+ { href: "/about", label: "About" },
27
+ ];
28
+
29
+ /** Adds a little page content so the navigation can be viewed in context. */
30
+ function PageContent({
31
+ message = "Page content appears below the sticky nav.",
32
+ }: {
33
+ message?: string;
34
+ }) {
35
+ return (
36
+ <div style={{ padding: "2rem" }}>
37
+ <p style={{ color: "var(--muted-foreground, #888)", fontSize: 14 }}>{message}</p>
38
+ </div>
39
+ );
40
+ }
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // Stories
44
+ // ---------------------------------------------------------------------------
45
+
46
+ export const Default: Story = {
47
+ render: () => (
48
+ <>
49
+ <AppNavigation
50
+ brand="Acme"
51
+ brandHref="/"
52
+ links={defaultLinks}
53
+ cta={{ href: "/get-started", label: "Get started", variant: "default" }}
54
+ />
55
+ <PageContent />
56
+ </>
57
+ ),
58
+ };
59
+
60
+ export const NoCta: Story = {
61
+ render: () => (
62
+ <>
63
+ <AppNavigation brand="Acme" brandHref="/" links={defaultLinks} />
64
+ <PageContent message="Same nav, no CTA button." />
65
+ </>
66
+ ),
67
+ };
68
+
69
+ export const OutlineCta: Story = {
70
+ render: () => (
71
+ <>
72
+ <AppNavigation
73
+ brand="Acme"
74
+ brandHref="/"
75
+ links={defaultLinks}
76
+ cta={{ href: "/get-started", label: "Get started", variant: "outline" }}
77
+ />
78
+ <PageContent message="The outline CTA keeps the same navigation geometry with a quieter emphasis." />
79
+ </>
80
+ ),
81
+ };
@@ -0,0 +1,155 @@
1
+ import { Menu } from "lucide-react";
2
+ import * as React from "react";
3
+
4
+ import { Button } from "../primitives/button";
5
+ import { Sheet, SheetClose, SheetContent, SheetTitle, SheetTrigger } from "../overlays/sheet";
6
+
7
+ export interface AppNavigationLink {
8
+ href: string;
9
+ label: string;
10
+ }
11
+
12
+ export interface AppNavigationCta {
13
+ href: string;
14
+ label: string;
15
+ variant?: "default" | "outline";
16
+ }
17
+
18
+ export interface AppNavigationProps {
19
+ brand?: React.ReactNode;
20
+ brandHref?: string;
21
+ endContent?: React.ReactNode;
22
+ links?: AppNavigationLink[];
23
+ cta?: AppNavigationCta;
24
+ ariaLabel?: string;
25
+ /** Current pathname used to highlight the active link. */
26
+ activeHref?: string;
27
+ /** A router link component. Native anchors are used when omitted. */
28
+ linkComponent?: React.ElementType;
29
+ /** The prop used by the link component for its destination. */
30
+ linkProp?: "href" | "to";
31
+ /** Props shared by every link, such as a router's prefetch hint. */
32
+ linkProps?: Record<string, unknown>;
33
+ }
34
+
35
+ export function AppNavigation({
36
+ brand,
37
+ brandHref = "/",
38
+ endContent,
39
+ links,
40
+ cta,
41
+ ariaLabel = "Primary navigation",
42
+ activeHref,
43
+ linkComponent: LinkComponent = "a",
44
+ linkProp = "href",
45
+ linkProps,
46
+ }: AppNavigationProps) {
47
+ const [mobileOpen, setMobileOpen] = React.useState(false);
48
+
49
+ const isActive = (href: string) => {
50
+ if (!activeHref) return false;
51
+ if (href === activeHref) return true;
52
+ // Nested routes (e.g. /work/123) keep the parent link active.
53
+ return href !== "/" && activeHref.startsWith(`${href}/`);
54
+ };
55
+
56
+ const createLink = ({
57
+ href,
58
+ children,
59
+ active = false,
60
+ variant,
61
+ brand = false,
62
+ }: {
63
+ href: string;
64
+ children: React.ReactNode;
65
+ active?: boolean;
66
+ variant?: AppNavigationCta["variant"];
67
+ brand?: boolean;
68
+ }) =>
69
+ React.createElement(
70
+ LinkComponent,
71
+ {
72
+ ...linkProps,
73
+ ...(linkProp === "to" ? { to: href } : { href }),
74
+ className: brand
75
+ ? "navigation-link navigation-brand shrink-0"
76
+ : variant
77
+ ? "navigation-cta"
78
+ : "navigation-link",
79
+ "aria-current": active ? "page" : undefined,
80
+ "data-variant": variant,
81
+ },
82
+ children,
83
+ );
84
+
85
+ return (
86
+ <header className="navigation-shell">
87
+ <nav className="navigation" aria-label={ariaLabel}>
88
+ <div className="navigation-inner">
89
+ <div className="navigation-start">
90
+ {brand && createLink({ href: brandHref, children: brand, brand: true })}
91
+ </div>
92
+
93
+ <div className="navigation-end">
94
+ {links?.map((link) => (
95
+ <React.Fragment key={link.href}>
96
+ {createLink({ href: link.href, children: link.label, active: isActive(link.href) })}
97
+ </React.Fragment>
98
+ ))}
99
+ {cta &&
100
+ createLink({
101
+ href: cta.href,
102
+ children: cta.label,
103
+ variant: cta.variant ?? "default",
104
+ })}
105
+ {endContent}
106
+ </div>
107
+
108
+ <div className="navigation-mobile">
109
+ <Sheet open={mobileOpen} onOpenChange={setMobileOpen}>
110
+ <SheetTrigger asChild>
111
+ <Button
112
+ variant="outline"
113
+ size="icon"
114
+ aria-label={mobileOpen ? "Close menu" : "Open menu"}
115
+ >
116
+ <Menu className="size-4" aria-hidden="true" />
117
+ </Button>
118
+ </SheetTrigger>
119
+ <SheetContent className="navigation-panel">
120
+ <SheetTitle className="sr-only">Navigation menu</SheetTitle>
121
+
122
+ <div className="navigation-panel-links">
123
+ {links?.map((link) => (
124
+ <SheetClose asChild key={link.href}>
125
+ {createLink({
126
+ href: link.href,
127
+ children: link.label,
128
+ active: isActive(link.href),
129
+ })}
130
+ </SheetClose>
131
+ ))}
132
+ </div>
133
+
134
+ {(cta || endContent) && (
135
+ <div className="navigation-panel-end">
136
+ {cta && (
137
+ <SheetClose asChild>
138
+ {createLink({
139
+ href: cta.href,
140
+ children: cta.label,
141
+ variant: cta.variant ?? "default",
142
+ })}
143
+ </SheetClose>
144
+ )}
145
+ {endContent && <div className="navigation-panel-content">{endContent}</div>}
146
+ </div>
147
+ )}
148
+ </SheetContent>
149
+ </Sheet>
150
+ </div>
151
+ </div>
152
+ </nav>
153
+ </header>
154
+ );
155
+ }
@@ -0,0 +1,3 @@
1
+ export { AppNavigation } from "./app-navigation";
2
+ export type { AppNavigationCta, AppNavigationLink, AppNavigationProps } from "./app-navigation";
3
+ export { Tabs, TabsContent, TabsList, TabsTrigger } from "./tabs";
@@ -0,0 +1,69 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+
3
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "./tabs";
4
+
5
+ const meta = {
6
+ title: "Navigation/Tabs",
7
+ component: Tabs,
8
+ tags: ["autodocs"],
9
+ } satisfies Meta<typeof Tabs>;
10
+
11
+ export default meta;
12
+ type Story = StoryObj<typeof meta>;
13
+
14
+ export const Default: Story = {
15
+ render: () => (
16
+ <Tabs defaultValue="account" className="w-[400px]">
17
+ <TabsList>
18
+ <TabsTrigger value="account">Account</TabsTrigger>
19
+ <TabsTrigger value="password">Password</TabsTrigger>
20
+ </TabsList>
21
+ <TabsContent value="account">
22
+ <p className="text-secondary p-4 text-sm">
23
+ Make changes to your account here. Click save when you're done.
24
+ </p>
25
+ </TabsContent>
26
+ <TabsContent value="password">
27
+ <p className="text-secondary p-4 text-sm">
28
+ Change your password here. After saving, you'll be logged out.
29
+ </p>
30
+ </TabsContent>
31
+ </Tabs>
32
+ ),
33
+ };
34
+
35
+ export const LineVariant: Story = {
36
+ render: () => (
37
+ <Tabs defaultValue="tab1" className="w-[400px]">
38
+ <TabsList className="border-x-0 border-t-0 bg-transparent">
39
+ <TabsTrigger value="tab1">Overview</TabsTrigger>
40
+ <TabsTrigger value="tab2">Analytics</TabsTrigger>
41
+ <TabsTrigger value="tab3">Reports</TabsTrigger>
42
+ </TabsList>
43
+ <TabsContent value="tab1">
44
+ <p className="text-secondary p-4 text-sm">Overview content</p>
45
+ </TabsContent>
46
+ <TabsContent value="tab2">
47
+ <p className="text-secondary p-4 text-sm">Analytics content</p>
48
+ </TabsContent>
49
+ <TabsContent value="tab3">
50
+ <p className="text-secondary p-4 text-sm">Reports content</p>
51
+ </TabsContent>
52
+ </Tabs>
53
+ ),
54
+ };
55
+
56
+ export const Vertical: Story = {
57
+ render: () => (
58
+ <Tabs defaultValue="tab1" orientation="vertical" className="w-[400px]">
59
+ <TabsList>
60
+ <TabsTrigger value="tab1">Account</TabsTrigger>
61
+ <TabsTrigger value="tab2">Billing</TabsTrigger>
62
+ <TabsTrigger value="tab3">Security</TabsTrigger>
63
+ </TabsList>
64
+ <TabsContent value="tab1">Account settings</TabsContent>
65
+ <TabsContent value="tab2">Billing information</TabsContent>
66
+ <TabsContent value="tab3">Security settings</TabsContent>
67
+ </Tabs>
68
+ ),
69
+ };
@@ -0,0 +1,49 @@
1
+ import * as TabsPrimitive from "@radix-ui/react-tabs";
2
+ import * as React from "react";
3
+
4
+ import { cn } from "../../lib/utils";
5
+
6
+ function Tabs({ ...props }: React.ComponentProps<typeof TabsPrimitive.Root>) {
7
+ return <TabsPrimitive.Root data-slot="tabs" {...props} />;
8
+ }
9
+
10
+ function TabsList({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.List>) {
11
+ return (
12
+ <TabsPrimitive.List
13
+ data-slot="tabs-list"
14
+ className={cn(
15
+ "bg-panel flex h-auto w-full items-center gap-1 rounded-full border p-1",
16
+ className,
17
+ )}
18
+ {...props}
19
+ />
20
+ );
21
+ }
22
+
23
+ function TabsTrigger({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
24
+ return (
25
+ <TabsPrimitive.Trigger
26
+ data-slot="tabs-trigger"
27
+ className={cn(
28
+ "text-primary focus-visible:ring-focus data-[state=active]:bg-panel data-[state=active]:text-primary data-[state=active]:border-subtle flex flex-1 items-center justify-center gap-2 rounded-full px-3 py-2 text-sm font-medium whitespace-nowrap transition-all focus-visible:ring-2 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 data-[state=active]:border",
29
+ className,
30
+ )}
31
+ {...props}
32
+ />
33
+ );
34
+ }
35
+
36
+ function TabsContent({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Content>) {
37
+ return (
38
+ <TabsPrimitive.Content
39
+ data-slot="tabs-content"
40
+ className={cn(
41
+ "focus-visible:bg-accent/10 mt-2 rounded-md focus-visible:border focus-visible:outline-none",
42
+ className,
43
+ )}
44
+ {...props}
45
+ />
46
+ );
47
+ }
48
+
49
+ export { Tabs, TabsContent, TabsList, TabsTrigger };
@@ -0,0 +1,45 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+
3
+ import {
4
+ AlertDialog,
5
+ AlertDialogAction,
6
+ AlertDialogCancel,
7
+ AlertDialogContent,
8
+ AlertDialogDescription,
9
+ AlertDialogFooter,
10
+ AlertDialogHeader,
11
+ AlertDialogTitle,
12
+ AlertDialogTrigger,
13
+ } from "./alert-dialog";
14
+
15
+ const meta = {
16
+ title: "Overlays/AlertDialog",
17
+ component: AlertDialog,
18
+ tags: ["autodocs"],
19
+ } satisfies Meta<typeof AlertDialog>;
20
+
21
+ export default meta;
22
+ type Story = StoryObj<typeof meta>;
23
+
24
+ export const Default: Story = {
25
+ render: () => (
26
+ <AlertDialog>
27
+ <AlertDialogTrigger className="rounded border px-4 py-2 text-sm">
28
+ Open Dialog
29
+ </AlertDialogTrigger>
30
+ <AlertDialogContent>
31
+ <AlertDialogHeader>
32
+ <AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
33
+ <AlertDialogDescription>
34
+ This action cannot be undone. This will permanently delete your account and remove your
35
+ data from our servers.
36
+ </AlertDialogDescription>
37
+ </AlertDialogHeader>
38
+ <AlertDialogFooter>
39
+ <AlertDialogCancel>Cancel</AlertDialogCancel>
40
+ <AlertDialogAction>Continue</AlertDialogAction>
41
+ </AlertDialogFooter>
42
+ </AlertDialogContent>
43
+ </AlertDialog>
44
+ ),
45
+ };
@@ -0,0 +1,119 @@
1
+ import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
2
+ import * as React from "react";
3
+
4
+ import { cn } from "../../lib/utils";
5
+ import { buttonVariants } from "../primitives/button";
6
+
7
+ const AlertDialog = AlertDialogPrimitive.Root;
8
+ const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
9
+
10
+ const AlertDialogPortal = ({ children, ...props }: AlertDialogPrimitive.AlertDialogPortalProps) => (
11
+ <AlertDialogPrimitive.Portal {...props}>
12
+ <div className="fixed inset-0 z-50 flex items-end justify-center sm:items-center">
13
+ {children}
14
+ </div>
15
+ </AlertDialogPrimitive.Portal>
16
+ );
17
+ AlertDialogPortal.displayName = AlertDialogPrimitive.Portal.displayName;
18
+
19
+ const AlertDialogOverlay = React.forwardRef<
20
+ React.ComponentRef<typeof AlertDialogPrimitive.Overlay>,
21
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
22
+ >(({ className, ...props }, ref) => (
23
+ <AlertDialogPrimitive.Overlay
24
+ ref={ref}
25
+ className={cn(
26
+ "overlay-backdrop 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 transition-opacity",
27
+ className,
28
+ )}
29
+ {...props}
30
+ />
31
+ ));
32
+ AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
33
+
34
+ const AlertDialogContent = React.forwardRef<
35
+ React.ComponentRef<typeof AlertDialogPrimitive.Content>,
36
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
37
+ >(({ className, ...props }, ref) => (
38
+ <AlertDialogPortal>
39
+ <AlertDialogOverlay />
40
+ <AlertDialogPrimitive.Content
41
+ ref={ref}
42
+ className={cn(
43
+ "bg-raised text-primary data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=open]:slide-in-from-bottom-10 data-[state=closed]:slide-out-to-bottom-10 border-subtle sm:data-[state=open]:slide-in-from-bottom-0 fixed bottom-0 z-50 grid w-full max-w-lg gap-6 rounded-t-xl border p-4 outline-none sm:top-[50%] sm:right-auto sm:bottom-auto sm:left-[50%] sm:-translate-x-1/2 sm:-translate-y-1/2 sm:rounded-xl sm:p-6",
44
+ className,
45
+ )}
46
+ {...props}
47
+ />
48
+ </AlertDialogPortal>
49
+ ));
50
+ AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
51
+
52
+ function AlertDialogHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
53
+ return <div className={cn("flex flex-col gap-2 text-left", className)} {...props} />;
54
+ }
55
+
56
+ function AlertDialogFooter({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
57
+ return (
58
+ <div
59
+ className={cn("flex flex-col-reverse gap-3 sm:flex-row sm:justify-end", className)}
60
+ {...props}
61
+ />
62
+ );
63
+ }
64
+
65
+ const AlertDialogTitle = React.forwardRef<
66
+ React.ComponentRef<typeof AlertDialogPrimitive.Title>,
67
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
68
+ >(({ className, ...props }, ref) => (
69
+ <AlertDialogPrimitive.Title
70
+ ref={ref}
71
+ className={cn("heading-2 text-primary", className)}
72
+ {...props}
73
+ />
74
+ ));
75
+ AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
76
+
77
+ const AlertDialogDescription = React.forwardRef<
78
+ React.ComponentRef<typeof AlertDialogPrimitive.Description>,
79
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
80
+ >(({ className, ...props }, ref) => (
81
+ <AlertDialogPrimitive.Description
82
+ ref={ref}
83
+ className={cn("body-2 text-secondary max-w-prose", className)}
84
+ {...props}
85
+ />
86
+ ));
87
+ AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName;
88
+
89
+ const AlertDialogAction = React.forwardRef<
90
+ React.ComponentRef<typeof AlertDialogPrimitive.Action>,
91
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
92
+ >(({ className, ...props }, ref) => (
93
+ <AlertDialogPrimitive.Action ref={ref} className={cn(buttonVariants(), className)} {...props} />
94
+ ));
95
+ AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;
96
+
97
+ const AlertDialogCancel = React.forwardRef<
98
+ React.ComponentRef<typeof AlertDialogPrimitive.Cancel>,
99
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
100
+ >(({ className, ...props }, ref) => (
101
+ <AlertDialogPrimitive.Cancel
102
+ ref={ref}
103
+ className={cn(buttonVariants({ variant: "outline" }), "mt-2 sm:mt-0", className)}
104
+ {...props}
105
+ />
106
+ ));
107
+ AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
108
+
109
+ export {
110
+ AlertDialog,
111
+ AlertDialogAction,
112
+ AlertDialogCancel,
113
+ AlertDialogContent,
114
+ AlertDialogDescription,
115
+ AlertDialogFooter,
116
+ AlertDialogHeader,
117
+ AlertDialogTitle,
118
+ AlertDialogTrigger,
119
+ };