@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.
- package/README.md +27 -0
- package/dist/index.js +2118 -0
- package/dist/index.js.map +1 -0
- package/package.json +130 -0
- package/src/animations.css +437 -0
- package/src/components/data-display/accordion.stories.tsx +57 -0
- package/src/components/data-display/accordion.tsx +65 -0
- package/src/components/data-display/filter-chip.tsx +41 -0
- package/src/components/data-display/index.ts +13 -0
- package/src/components/data-display/table.stories.tsx +75 -0
- package/src/components/data-display/table.tsx +84 -0
- package/src/components/feedback/alert.stories.tsx +51 -0
- package/src/components/feedback/alert.tsx +60 -0
- package/src/components/feedback/empty-state.tsx +66 -0
- package/src/components/feedback/index.ts +6 -0
- package/src/components/feedback/progress.stories.tsx +37 -0
- package/src/components/feedback/progress.tsx +30 -0
- package/src/components/feedback/spinner.stories.tsx +52 -0
- package/src/components/feedback/spinner.tsx +48 -0
- package/src/components/forms/calendar.stories.tsx +30 -0
- package/src/components/forms/calendar.tsx +67 -0
- package/src/components/forms/date-picker.stories.tsx +48 -0
- package/src/components/forms/date-picker.tsx +60 -0
- package/src/components/forms/field.stories.tsx +62 -0
- package/src/components/forms/field.tsx +65 -0
- package/src/components/forms/field.types.ts +7 -0
- package/src/components/forms/index.ts +24 -0
- package/src/components/forms/input.stories.tsx +47 -0
- package/src/components/forms/input.tsx +20 -0
- package/src/components/forms/radio-group.stories.tsx +48 -0
- package/src/components/forms/radio-group.tsx +38 -0
- package/src/components/forms/select.stories.tsx +88 -0
- package/src/components/forms/select.tsx +147 -0
- package/src/components/forms/slider.stories.tsx +67 -0
- package/src/components/forms/slider.tsx +25 -0
- package/src/components/forms/stepper.stories.tsx +111 -0
- package/src/components/forms/stepper.tsx +66 -0
- package/src/components/forms/switch.stories.tsx +144 -0
- package/src/components/forms/switch.tsx +36 -0
- package/src/components/forms/text-field.stories.tsx +87 -0
- package/src/components/forms/text-field.tsx +40 -0
- package/src/components/forms/text-field.types.ts +9 -0
- package/src/components/forms/textarea.stories.tsx +150 -0
- package/src/components/forms/textarea.tsx +18 -0
- package/src/components/layout/index.ts +2 -0
- package/src/components/layout/scroll-area.stories.tsx +83 -0
- package/src/components/layout/scroll-area.tsx +40 -0
- package/src/components/navigation/app-navigation.stories.tsx +81 -0
- package/src/components/navigation/app-navigation.tsx +155 -0
- package/src/components/navigation/index.ts +3 -0
- package/src/components/navigation/tabs.stories.tsx +69 -0
- package/src/components/navigation/tabs.tsx +49 -0
- package/src/components/overlays/alert-dialog.stories.tsx +45 -0
- package/src/components/overlays/alert-dialog.tsx +119 -0
- package/src/components/overlays/command.stories.tsx +65 -0
- package/src/components/overlays/command.tsx +130 -0
- package/src/components/overlays/dialog.stories.tsx +111 -0
- package/src/components/overlays/dialog.tsx +162 -0
- package/src/components/overlays/dropdown-menu.stories.tsx +79 -0
- package/src/components/overlays/dropdown-menu.tsx +214 -0
- package/src/components/overlays/index.ts +65 -0
- package/src/components/overlays/popover.stories.tsx +39 -0
- package/src/components/overlays/popover.tsx +40 -0
- package/src/components/overlays/sheet.stories.tsx +68 -0
- package/src/components/overlays/sheet.tsx +98 -0
- package/src/components/primitives/avatar.stories.tsx +71 -0
- package/src/components/primitives/avatar.tsx +132 -0
- package/src/components/primitives/badge.stories.tsx +33 -0
- package/src/components/primitives/badge.tsx +44 -0
- package/src/components/primitives/button.stories.tsx +113 -0
- package/src/components/primitives/button.tsx +95 -0
- package/src/components/primitives/button.types.ts +10 -0
- package/src/components/primitives/card.stories.tsx +59 -0
- package/src/components/primitives/card.tsx +69 -0
- package/src/components/primitives/index.ts +13 -0
- package/src/components/primitives/label.stories.tsx +47 -0
- package/src/components/primitives/label.tsx +21 -0
- package/src/constants/chart-colors.ts +39 -0
- package/src/env.d.ts +9 -0
- package/src/hooks/index.ts +6 -0
- package/src/hooks/sort.types.ts +12 -0
- package/src/hooks/use-api-client.ts +122 -0
- package/src/hooks/use-debounced-value.ts +12 -0
- package/src/hooks/use-filter-state.ts +68 -0
- package/src/hooks/use-media-query.ts +65 -0
- package/src/hooks/use-mobile.tsx +19 -0
- package/src/index.ts +25 -0
- package/src/lib/auth.ts +5 -0
- package/src/lib/clipboard.ts +8 -0
- package/src/lib/create-memory-storage.ts +24 -0
- package/src/lib/index.ts +4 -0
- package/src/lib/utils.ts +6 -0
- package/src/storybook/controls.ts +117 -0
- package/src/storybook/options.ts +52 -0
- package/src/styles.css +1052 -0
- package/src/tokens/color-systems.stories.tsx +133 -0
- package/src/tokens/colors.ts +156 -0
- package/src/tokens/foundations.stories.tsx +151 -0
- package/src/tokens/index.ts +16 -0
- package/src/tokens/layout.stories.tsx +118 -0
- package/src/tokens/motion.ts +51 -0
- package/src/tokens/radii.ts +10 -0
- package/src/tokens/shadows.ts +34 -0
- package/src/tokens/spacing.ts +30 -0
- package/src/tokens/typography.ts +42 -0
- package/src/translations/en.ts +34 -0
- package/src/translations/index.ts +32 -0
- package/src/types/upload.ts +42 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { Calculator, Calendar, CreditCard, Settings, Smile, User } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
Command,
|
|
6
|
+
CommandEmpty,
|
|
7
|
+
CommandGroup,
|
|
8
|
+
CommandInput,
|
|
9
|
+
CommandItem,
|
|
10
|
+
CommandList,
|
|
11
|
+
CommandSeparator,
|
|
12
|
+
CommandShortcut,
|
|
13
|
+
} from "./command";
|
|
14
|
+
|
|
15
|
+
const meta = {
|
|
16
|
+
title: "Overlays/Command",
|
|
17
|
+
component: Command,
|
|
18
|
+
tags: ["autodocs"],
|
|
19
|
+
} satisfies Meta<typeof Command>;
|
|
20
|
+
|
|
21
|
+
export default meta;
|
|
22
|
+
type Story = StoryObj<typeof meta>;
|
|
23
|
+
|
|
24
|
+
export const Default: Story = {
|
|
25
|
+
render: () => (
|
|
26
|
+
<Command className="w-[450px] rounded-md border">
|
|
27
|
+
<CommandInput placeholder="Type a command or search..." />
|
|
28
|
+
<CommandList>
|
|
29
|
+
<CommandEmpty>No results found.</CommandEmpty>
|
|
30
|
+
<CommandGroup heading="Suggestions">
|
|
31
|
+
<CommandItem>
|
|
32
|
+
<Calendar className="mr-2 size-4" />
|
|
33
|
+
<span>Calendar</span>
|
|
34
|
+
</CommandItem>
|
|
35
|
+
<CommandItem>
|
|
36
|
+
<Smile className="mr-2 size-4" />
|
|
37
|
+
<span>Search Emoji</span>
|
|
38
|
+
</CommandItem>
|
|
39
|
+
<CommandItem>
|
|
40
|
+
<Calculator className="mr-2 size-4" />
|
|
41
|
+
<span>Calculator</span>
|
|
42
|
+
</CommandItem>
|
|
43
|
+
</CommandGroup>
|
|
44
|
+
<CommandSeparator />
|
|
45
|
+
<CommandGroup heading="Settings">
|
|
46
|
+
<CommandItem>
|
|
47
|
+
<User className="mr-2 size-4" />
|
|
48
|
+
<span>Profile</span>
|
|
49
|
+
<CommandShortcut>⌘P</CommandShortcut>
|
|
50
|
+
</CommandItem>
|
|
51
|
+
<CommandItem>
|
|
52
|
+
<CreditCard className="mr-2 size-4" />
|
|
53
|
+
<span>Billing</span>
|
|
54
|
+
<CommandShortcut>⌘B</CommandShortcut>
|
|
55
|
+
</CommandItem>
|
|
56
|
+
<CommandItem>
|
|
57
|
+
<Settings className="mr-2 size-4" />
|
|
58
|
+
<span>Settings</span>
|
|
59
|
+
<CommandShortcut>⌘S</CommandShortcut>
|
|
60
|
+
</CommandItem>
|
|
61
|
+
</CommandGroup>
|
|
62
|
+
</CommandList>
|
|
63
|
+
</Command>
|
|
64
|
+
),
|
|
65
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { Command as CommandPrimitive } from "cmdk";
|
|
2
|
+
import { SearchIcon } from "lucide-react";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
|
|
5
|
+
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "./dialog";
|
|
6
|
+
|
|
7
|
+
function Command(props: React.ComponentProps<typeof CommandPrimitive>) {
|
|
8
|
+
return (
|
|
9
|
+
<CommandPrimitive
|
|
10
|
+
data-slot="command"
|
|
11
|
+
className="bg-raised text-primary flex h-full w-full flex-col overflow-hidden rounded-md"
|
|
12
|
+
{...props}
|
|
13
|
+
/>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function CommandDialog({
|
|
18
|
+
title = "Command Palette",
|
|
19
|
+
description = "Search for a command to run...",
|
|
20
|
+
children,
|
|
21
|
+
showCloseButton = true,
|
|
22
|
+
...props
|
|
23
|
+
}: Omit<React.ComponentProps<typeof Dialog>, "children"> & {
|
|
24
|
+
title?: string;
|
|
25
|
+
description?: string;
|
|
26
|
+
showCloseButton?: boolean;
|
|
27
|
+
children?: React.ReactNode;
|
|
28
|
+
}) {
|
|
29
|
+
return (
|
|
30
|
+
<Dialog {...props}>
|
|
31
|
+
<DialogHeader className="sr-only">
|
|
32
|
+
<DialogTitle>{title}</DialogTitle>
|
|
33
|
+
<DialogDescription>{description}</DialogDescription>
|
|
34
|
+
</DialogHeader>
|
|
35
|
+
<DialogContent className="overflow-hidden p-0" showCloseButton={showCloseButton}>
|
|
36
|
+
<Command>{children}</Command>
|
|
37
|
+
</DialogContent>
|
|
38
|
+
</Dialog>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function CommandInput(props: React.ComponentProps<typeof CommandPrimitive.Input>) {
|
|
43
|
+
return (
|
|
44
|
+
<div data-slot="command-input-wrapper" className="flex h-9 items-center gap-2 border-b px-3">
|
|
45
|
+
<SearchIcon className="size-4 shrink-0 opacity-50" />
|
|
46
|
+
<CommandPrimitive.Input
|
|
47
|
+
data-slot="command-input"
|
|
48
|
+
className="placeholder:text-secondary flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50"
|
|
49
|
+
{...props}
|
|
50
|
+
/>
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function CommandList(props: React.ComponentProps<typeof CommandPrimitive.List>) {
|
|
56
|
+
return (
|
|
57
|
+
<CommandPrimitive.List
|
|
58
|
+
data-slot="command-list"
|
|
59
|
+
className="max-h-75 scroll-py-1 overflow-x-hidden overflow-y-auto"
|
|
60
|
+
{...props}
|
|
61
|
+
/>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function CommandEmpty({ ...props }: React.ComponentProps<typeof CommandPrimitive.Empty>) {
|
|
66
|
+
return (
|
|
67
|
+
<CommandPrimitive.Empty
|
|
68
|
+
data-slot="command-empty"
|
|
69
|
+
className="py-6 text-center text-sm"
|
|
70
|
+
{...props}
|
|
71
|
+
/>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function CommandGroup(props: React.ComponentProps<typeof CommandPrimitive.Group>) {
|
|
76
|
+
return (
|
|
77
|
+
<CommandPrimitive.Group
|
|
78
|
+
data-slot="command-group"
|
|
79
|
+
className="text-primary **:[[cmdk-group-heading]]:text-secondary overflow-hidden p-1 **:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:py-1.5 **:[[cmdk-group-heading]]:text-xs **:[[cmdk-group-heading]]:font-medium"
|
|
80
|
+
{...props}
|
|
81
|
+
/>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function CommandSeparator(props: React.ComponentProps<typeof CommandPrimitive.Separator>) {
|
|
86
|
+
return (
|
|
87
|
+
<CommandPrimitive.Separator
|
|
88
|
+
data-slot="command-separator"
|
|
89
|
+
className="bg-border -mx-1 h-px"
|
|
90
|
+
{...props}
|
|
91
|
+
/>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function CommandItem(props: React.ComponentProps<typeof CommandPrimitive.Item>) {
|
|
96
|
+
return (
|
|
97
|
+
<CommandPrimitive.Item
|
|
98
|
+
data-slot="command-item"
|
|
99
|
+
className="data-[selected=true]:bg-accent data-[selected=true]:text-on-accent [&_svg:not([class*='text-'])]:text-secondary relative flex cursor-default items-center gap-2 rounded-md px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
|
|
100
|
+
{...props}
|
|
101
|
+
/>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function CommandShortcut(props: React.ComponentProps<"span">) {
|
|
106
|
+
return (
|
|
107
|
+
<span
|
|
108
|
+
data-slot="command-shortcut"
|
|
109
|
+
className="text-secondary ml-auto text-xs tracking-widest"
|
|
110
|
+
{...props}
|
|
111
|
+
/>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function CommandListLoading(props: React.ComponentProps<"div">) {
|
|
116
|
+
return <div data-slot="command-list-loading" className="py-6 text-center text-sm" {...props} />;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export {
|
|
120
|
+
Command,
|
|
121
|
+
CommandDialog,
|
|
122
|
+
CommandEmpty,
|
|
123
|
+
CommandGroup,
|
|
124
|
+
CommandInput,
|
|
125
|
+
CommandItem,
|
|
126
|
+
CommandList,
|
|
127
|
+
CommandListLoading,
|
|
128
|
+
CommandSeparator,
|
|
129
|
+
CommandShortcut,
|
|
130
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
|
|
4
|
+
import { booleanControl, hiddenControl } from "../../storybook/controls";
|
|
5
|
+
import { Button } from "../primitives/button";
|
|
6
|
+
import {
|
|
7
|
+
Dialog,
|
|
8
|
+
DialogContent,
|
|
9
|
+
DialogDescription,
|
|
10
|
+
DialogFooter,
|
|
11
|
+
DialogHeader,
|
|
12
|
+
DialogTitle,
|
|
13
|
+
DialogTrigger,
|
|
14
|
+
} from "./dialog";
|
|
15
|
+
|
|
16
|
+
const meta = {
|
|
17
|
+
title: "Overlays/Dialog",
|
|
18
|
+
component: Dialog,
|
|
19
|
+
tags: ["autodocs"],
|
|
20
|
+
argTypes: {
|
|
21
|
+
open: booleanControl("Controls whether the dialog is open", true),
|
|
22
|
+
modal: booleanControl("Whether interaction outside the dialog is disabled when open", true),
|
|
23
|
+
defaultOpen: hiddenControl,
|
|
24
|
+
onOpenChange: hiddenControl,
|
|
25
|
+
},
|
|
26
|
+
} satisfies Meta<typeof Dialog>;
|
|
27
|
+
|
|
28
|
+
export default meta;
|
|
29
|
+
type Story = StoryObj<typeof meta>;
|
|
30
|
+
|
|
31
|
+
function DialogPreview({
|
|
32
|
+
open,
|
|
33
|
+
modal,
|
|
34
|
+
children,
|
|
35
|
+
}: {
|
|
36
|
+
open: boolean;
|
|
37
|
+
modal: boolean;
|
|
38
|
+
children: React.ReactNode;
|
|
39
|
+
}) {
|
|
40
|
+
const [isOpen, setIsOpen] = useState(open);
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
setIsOpen(open);
|
|
44
|
+
}, [open]);
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<Dialog open={isOpen} onOpenChange={setIsOpen} modal={modal}>
|
|
48
|
+
{children}
|
|
49
|
+
</Dialog>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const Default: Story = {
|
|
54
|
+
args: {
|
|
55
|
+
open: true,
|
|
56
|
+
modal: true,
|
|
57
|
+
},
|
|
58
|
+
render: (args) => (
|
|
59
|
+
<DialogPreview open={Boolean(args.open ?? true)} modal={Boolean(args.modal ?? true)}>
|
|
60
|
+
<DialogTrigger asChild>
|
|
61
|
+
<Button variant="outline">Open Dialog</Button>
|
|
62
|
+
</DialogTrigger>
|
|
63
|
+
<DialogContent>
|
|
64
|
+
<DialogHeader>
|
|
65
|
+
<DialogTitle>Edit Profile</DialogTitle>
|
|
66
|
+
<DialogDescription>
|
|
67
|
+
Make changes to your profile here. Click save when you're done.
|
|
68
|
+
</DialogDescription>
|
|
69
|
+
</DialogHeader>
|
|
70
|
+
<div className="grid gap-4 py-4">
|
|
71
|
+
<div className="grid grid-cols-4 items-center gap-4">
|
|
72
|
+
<label htmlFor="name" className="text-right text-sm font-medium">
|
|
73
|
+
Name
|
|
74
|
+
</label>
|
|
75
|
+
<input
|
|
76
|
+
id="name"
|
|
77
|
+
defaultValue="Pedro Duarte"
|
|
78
|
+
className="col-span-3 h-9 rounded-md border px-3 text-sm"
|
|
79
|
+
/>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
<DialogFooter>
|
|
83
|
+
<Button type="submit">Save changes</Button>
|
|
84
|
+
</DialogFooter>
|
|
85
|
+
</DialogContent>
|
|
86
|
+
</DialogPreview>
|
|
87
|
+
),
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const WithCloseButton: Story = {
|
|
91
|
+
args: {
|
|
92
|
+
open: true,
|
|
93
|
+
modal: true,
|
|
94
|
+
},
|
|
95
|
+
render: (args) => (
|
|
96
|
+
<DialogPreview open={Boolean(args.open ?? true)} modal={Boolean(args.modal ?? true)}>
|
|
97
|
+
<DialogTrigger asChild>
|
|
98
|
+
<Button>Open</Button>
|
|
99
|
+
</DialogTrigger>
|
|
100
|
+
<DialogContent showCloseButton>
|
|
101
|
+
<DialogHeader>
|
|
102
|
+
<DialogTitle>Confirmation</DialogTitle>
|
|
103
|
+
<DialogDescription>Are you sure you want to proceed?</DialogDescription>
|
|
104
|
+
</DialogHeader>
|
|
105
|
+
<DialogFooter>
|
|
106
|
+
<Button>Confirm</Button>
|
|
107
|
+
</DialogFooter>
|
|
108
|
+
</DialogContent>
|
|
109
|
+
</DialogPreview>
|
|
110
|
+
),
|
|
111
|
+
};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { Dialog as BaseDialog } from "@base-ui/react/dialog";
|
|
2
|
+
import { XIcon } from "lucide-react";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
|
|
5
|
+
import { cn } from "../../lib/utils";
|
|
6
|
+
|
|
7
|
+
function Dialog({ ...props }: React.ComponentProps<typeof BaseDialog.Root>) {
|
|
8
|
+
return <BaseDialog.Root {...props} />;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function DialogTrigger({
|
|
12
|
+
asChild,
|
|
13
|
+
children,
|
|
14
|
+
...props
|
|
15
|
+
}: React.ComponentProps<typeof BaseDialog.Trigger> & { asChild?: boolean }) {
|
|
16
|
+
if (asChild && React.isValidElement(children)) {
|
|
17
|
+
return (
|
|
18
|
+
<BaseDialog.Trigger
|
|
19
|
+
data-slot="dialog-trigger"
|
|
20
|
+
render={(triggerProps) =>
|
|
21
|
+
React.cloneElement(children as React.ReactElement<unknown>, triggerProps)
|
|
22
|
+
}
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
return (
|
|
28
|
+
<BaseDialog.Trigger data-slot="dialog-trigger" {...props}>
|
|
29
|
+
{children}
|
|
30
|
+
</BaseDialog.Trigger>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function DialogPortal({ ...props }: React.ComponentProps<typeof BaseDialog.Portal>) {
|
|
35
|
+
return <BaseDialog.Portal data-slot="dialog-portal" {...props} />;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function DialogClose({
|
|
39
|
+
asChild,
|
|
40
|
+
children,
|
|
41
|
+
...props
|
|
42
|
+
}: React.ComponentProps<typeof BaseDialog.Close> & { asChild?: boolean }) {
|
|
43
|
+
if (asChild && React.isValidElement(children)) {
|
|
44
|
+
return (
|
|
45
|
+
<BaseDialog.Close
|
|
46
|
+
data-slot="dialog-close"
|
|
47
|
+
render={(closeProps) =>
|
|
48
|
+
React.cloneElement(children as React.ReactElement<unknown>, closeProps)
|
|
49
|
+
}
|
|
50
|
+
{...props}
|
|
51
|
+
/>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
return (
|
|
55
|
+
<BaseDialog.Close data-slot="dialog-close" {...props}>
|
|
56
|
+
{children}
|
|
57
|
+
</BaseDialog.Close>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function DialogOverlay({ className, ...props }: React.ComponentProps<typeof BaseDialog.Backdrop>) {
|
|
62
|
+
return (
|
|
63
|
+
<BaseDialog.Backdrop
|
|
64
|
+
data-slot="dialog-overlay"
|
|
65
|
+
className={cn(
|
|
66
|
+
"overlay-backdrop data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 fixed inset-0 z-50 transition-opacity",
|
|
67
|
+
className,
|
|
68
|
+
)}
|
|
69
|
+
{...props}
|
|
70
|
+
/>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function DialogContent({
|
|
75
|
+
className,
|
|
76
|
+
children,
|
|
77
|
+
showCloseButton = true,
|
|
78
|
+
...props
|
|
79
|
+
}: React.ComponentProps<typeof BaseDialog.Popup> & {
|
|
80
|
+
showCloseButton?: boolean;
|
|
81
|
+
}) {
|
|
82
|
+
return (
|
|
83
|
+
<BaseDialog.Portal data-slot="dialog-portal">
|
|
84
|
+
<DialogOverlay />
|
|
85
|
+
<BaseDialog.Popup
|
|
86
|
+
data-slot="dialog-content"
|
|
87
|
+
className={cn(
|
|
88
|
+
"bg-raised text-primary border-subtle data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid max-h-[90dvh] w-[calc(100%-2rem)] max-w-[30rem] translate-x-[-50%] translate-y-[-50%] gap-6 overflow-y-auto rounded-xl border p-4 duration-200 outline-none sm:p-6",
|
|
89
|
+
className,
|
|
90
|
+
)}
|
|
91
|
+
{...props}
|
|
92
|
+
>
|
|
93
|
+
{children}
|
|
94
|
+
{showCloseButton && (
|
|
95
|
+
<BaseDialog.Close
|
|
96
|
+
data-slot="dialog-close"
|
|
97
|
+
className="text-secondary hover:bg-inset hover:text-primary focus-visible:outline-ring absolute top-3 right-3 inline-flex size-11 items-center justify-center rounded-md border border-transparent opacity-100 transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 disabled:pointer-events-none sm:top-4 sm:right-4 [&_svg]:pointer-events-none [&_svg]:size-4.5 [&_svg]:shrink-0"
|
|
98
|
+
>
|
|
99
|
+
<XIcon />
|
|
100
|
+
<span className="sr-only">Close</span>
|
|
101
|
+
</BaseDialog.Close>
|
|
102
|
+
)}
|
|
103
|
+
</BaseDialog.Popup>
|
|
104
|
+
</BaseDialog.Portal>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
109
|
+
return (
|
|
110
|
+
<div
|
|
111
|
+
data-slot="dialog-header"
|
|
112
|
+
className={cn("flex flex-col gap-2 text-left", className)}
|
|
113
|
+
{...props}
|
|
114
|
+
/>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
119
|
+
return (
|
|
120
|
+
<div
|
|
121
|
+
data-slot="dialog-footer"
|
|
122
|
+
className={cn("flex flex-col-reverse gap-3 sm:flex-row sm:justify-end", className)}
|
|
123
|
+
{...props}
|
|
124
|
+
/>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function DialogTitle({ className, ...props }: React.ComponentProps<typeof BaseDialog.Title>) {
|
|
129
|
+
return (
|
|
130
|
+
<BaseDialog.Title
|
|
131
|
+
data-slot="dialog-title"
|
|
132
|
+
className={cn("heading-2 text-primary", className)}
|
|
133
|
+
{...props}
|
|
134
|
+
/>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function DialogDescription({
|
|
139
|
+
className,
|
|
140
|
+
...props
|
|
141
|
+
}: React.ComponentProps<typeof BaseDialog.Description>) {
|
|
142
|
+
return (
|
|
143
|
+
<BaseDialog.Description
|
|
144
|
+
data-slot="dialog-description"
|
|
145
|
+
className={cn("body-2 text-secondary max-w-prose", className)}
|
|
146
|
+
{...props}
|
|
147
|
+
/>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export {
|
|
152
|
+
Dialog,
|
|
153
|
+
DialogClose,
|
|
154
|
+
DialogContent,
|
|
155
|
+
DialogDescription,
|
|
156
|
+
DialogFooter,
|
|
157
|
+
DialogHeader,
|
|
158
|
+
DialogOverlay,
|
|
159
|
+
DialogPortal,
|
|
160
|
+
DialogTitle,
|
|
161
|
+
DialogTrigger,
|
|
162
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { CreditCard, LogOut, Settings, User } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
import { Button } from "../primitives/button";
|
|
5
|
+
import {
|
|
6
|
+
DropdownMenu,
|
|
7
|
+
DropdownMenuContent,
|
|
8
|
+
DropdownMenuGroup,
|
|
9
|
+
DropdownMenuItem,
|
|
10
|
+
DropdownMenuLabel,
|
|
11
|
+
DropdownMenuSeparator,
|
|
12
|
+
DropdownMenuShortcut,
|
|
13
|
+
DropdownMenuTrigger,
|
|
14
|
+
} from "./dropdown-menu";
|
|
15
|
+
|
|
16
|
+
const meta = {
|
|
17
|
+
title: "Overlays/DropdownMenu",
|
|
18
|
+
component: DropdownMenu,
|
|
19
|
+
tags: ["autodocs"],
|
|
20
|
+
argTypes: {
|
|
21
|
+
open: {
|
|
22
|
+
control: "boolean",
|
|
23
|
+
description: "Controls whether the dropdown menu is open",
|
|
24
|
+
},
|
|
25
|
+
defaultOpen: {
|
|
26
|
+
control: "boolean",
|
|
27
|
+
description: "Whether the dropdown menu is open by default (uncontrolled)",
|
|
28
|
+
},
|
|
29
|
+
modal: {
|
|
30
|
+
control: "boolean",
|
|
31
|
+
description: "When true, interaction outside the menu is disabled while open",
|
|
32
|
+
table: { defaultValue: { summary: "true" } },
|
|
33
|
+
},
|
|
34
|
+
dir: {
|
|
35
|
+
control: "select",
|
|
36
|
+
options: ["ltr", "rtl"],
|
|
37
|
+
description: "Reading direction of the dropdown menu",
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
} satisfies Meta<typeof DropdownMenu>;
|
|
41
|
+
|
|
42
|
+
export default meta;
|
|
43
|
+
type Story = StoryObj<typeof meta>;
|
|
44
|
+
|
|
45
|
+
export const Default: Story = {
|
|
46
|
+
render: () => (
|
|
47
|
+
<DropdownMenu>
|
|
48
|
+
<DropdownMenuTrigger asChild>
|
|
49
|
+
<Button variant="outline">Open Menu</Button>
|
|
50
|
+
</DropdownMenuTrigger>
|
|
51
|
+
<DropdownMenuContent className="w-56">
|
|
52
|
+
<DropdownMenuLabel>My Account</DropdownMenuLabel>
|
|
53
|
+
<DropdownMenuSeparator />
|
|
54
|
+
<DropdownMenuGroup>
|
|
55
|
+
<DropdownMenuItem>
|
|
56
|
+
<User className="mr-2 size-4" />
|
|
57
|
+
<span>Profile</span>
|
|
58
|
+
<DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
|
|
59
|
+
</DropdownMenuItem>
|
|
60
|
+
<DropdownMenuItem>
|
|
61
|
+
<CreditCard className="mr-2 size-4" />
|
|
62
|
+
<span>Billing</span>
|
|
63
|
+
<DropdownMenuShortcut>⌘B</DropdownMenuShortcut>
|
|
64
|
+
</DropdownMenuItem>
|
|
65
|
+
<DropdownMenuItem>
|
|
66
|
+
<Settings className="mr-2 size-4" />
|
|
67
|
+
<span>Settings</span>
|
|
68
|
+
<DropdownMenuShortcut>⌘S</DropdownMenuShortcut>
|
|
69
|
+
</DropdownMenuItem>
|
|
70
|
+
</DropdownMenuGroup>
|
|
71
|
+
<DropdownMenuSeparator />
|
|
72
|
+
<DropdownMenuItem variant="destructive">
|
|
73
|
+
<LogOut className="mr-2 size-4" />
|
|
74
|
+
<span>Log out</span>
|
|
75
|
+
</DropdownMenuItem>
|
|
76
|
+
</DropdownMenuContent>
|
|
77
|
+
</DropdownMenu>
|
|
78
|
+
),
|
|
79
|
+
};
|