-
Notifications
You must be signed in to change notification settings - Fork 441
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ce229b9
commit d022757
Showing
2 changed files
with
297 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
"use client"; | ||
|
||
import { X } from "@phosphor-icons/react/dist/ssr"; | ||
import * as DialogPrimitive from "@radix-ui/react-dialog"; | ||
import { FocusScope, type FocusScopeProps } from "@radix-ui/react-focus-scope"; | ||
import { type VariantProps, cva } from "class-variance-authority"; | ||
import { cn } from "lib/cn"; | ||
import { | ||
type ComponentPropsWithoutRef, | ||
type ElementRef, | ||
type HTMLAttributes, | ||
forwardRef, | ||
} from "react"; | ||
|
||
const Dialog = DialogPrimitive.Root; | ||
|
||
const DialogTrigger = DialogPrimitive.Trigger; | ||
|
||
const DialogPortal = DialogPrimitive.Portal; | ||
|
||
const DialogOverlay = forwardRef< | ||
ElementRef<typeof DialogPrimitive.Overlay>, | ||
ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay> | ||
>(({ className, ...props }, ref) => ( | ||
<DialogPrimitive.Overlay | ||
ref={ref} | ||
className={cn( | ||
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-modal grid items-end justify-center overflow-y-auto bg-black/50 backdrop-blur-sm duration-200 data-[state=closed]:animate-out data-[state=open]:animate-in sm:items-center", | ||
className, | ||
)} | ||
{...props} | ||
/> | ||
)); | ||
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; | ||
|
||
export const dialogContentVariants = cva( | ||
"flex flex-col mt-4 md:my-16 relative rounded-xl max-sm:rounded-b-none bg-card shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 focus-visible:outline-none ring-ring focus-visible:ring-4 ring-offset-0", | ||
{ | ||
variants: { | ||
size: { | ||
sm: "w-[min(theme(maxWidth.sm),_100vw)]", | ||
md: "w-[min(theme(maxWidth.md),_100vw)]", | ||
lg: "w-[min(theme(maxWidth.lg),_100vw)]", | ||
xl: "w-[min(theme(maxWidth.xl),_100vw)]", | ||
"2xl": "w-[min(theme(maxWidth.2xl),_100vw)]", | ||
"3xl": "w-[min(theme(maxWidth.3xl),_100vw)]", | ||
"4xl": "w-[min(theme(maxWidth.4xl),_100vw)]", | ||
}, | ||
}, | ||
defaultVariants: { | ||
size: "md", | ||
}, | ||
}, | ||
); | ||
|
||
export interface DialogContentProps | ||
extends ComponentPropsWithoutRef<typeof DialogPrimitive.Content>, | ||
VariantProps<typeof dialogContentVariants> { | ||
scrollBody?: boolean; | ||
trapFocus?: FocusScopeProps["trapped"]; | ||
} | ||
|
||
const DialogContent = forwardRef< | ||
ElementRef<typeof DialogPrimitive.Content>, | ||
DialogContentProps | ||
>( | ||
( | ||
{ size, trapFocus = true, className, scrollBody, children, ...props }, | ||
ref, | ||
) => ( | ||
<DialogPortal> | ||
<DialogOverlay> | ||
<FocusScope trapped={trapFocus} loop> | ||
<DialogPrimitive.Content | ||
ref={ref} | ||
className={cn(dialogContentVariants({ size, className }), { | ||
"max-h-[calc(100vh-2*theme(space.16))]": scrollBody, | ||
})} | ||
{...props} | ||
> | ||
{children} | ||
</DialogPrimitive.Content> | ||
</FocusScope> | ||
</DialogOverlay> | ||
</DialogPortal> | ||
), | ||
); | ||
DialogContent.displayName = DialogPrimitive.Content.displayName; | ||
|
||
const DialogCloseButton = forwardRef< | ||
ElementRef<typeof DialogPrimitive.Close>, | ||
DialogPrimitive.DialogCloseProps | ||
>(({ className, ...props }, ref) => ( | ||
<DialogPrimitive.Close | ||
ref={ref} | ||
className={cn( | ||
"absolute top-8 right-8 rounded-full opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-ring focus-visible:ring-4 disabled:pointer-events-none data-[state=open]:text-foreground/50 md:right-10", | ||
className, | ||
)} | ||
{...props} | ||
> | ||
<X weight="bold" className="h-5 w-5" /> | ||
<span className="sr-only">Close</span> | ||
</DialogPrimitive.Close> | ||
)); | ||
DialogCloseButton.displayName = DialogPrimitive.Close.displayName; | ||
|
||
const DialogHeader = ({ | ||
className, | ||
...props | ||
}: HTMLAttributes<HTMLDivElement>) => ( | ||
<div | ||
className={cn("flex flex-col space-y-1.5 px-6 py-8 sm:px-10", className)} | ||
{...props} | ||
/> | ||
); | ||
DialogHeader.displayName = "DialogHeader"; | ||
|
||
interface DialogBodyProps extends HTMLAttributes<HTMLDivElement> { | ||
scroll?: boolean; | ||
} | ||
const DialogBody = ({ className, scroll, ...props }: DialogBodyProps) => ( | ||
<div | ||
className={cn( | ||
"flex flex-col overflow-visible px-6 pb-10 has-[~div]:pb-0 sm:px-10", | ||
{ | ||
"custom-scrollbar flex-shrink-1 flex-grow-1 overflow-y-auto": scroll, | ||
}, | ||
className, | ||
)} | ||
{...props} | ||
/> | ||
); | ||
DialogBody.displayName = "DialogBody"; | ||
|
||
const DialogFooter = ({ | ||
className, | ||
...props | ||
}: HTMLAttributes<HTMLDivElement>) => ( | ||
<div | ||
className={cn( | ||
"flex flex-col-reverse px-6 pt-8 pb-10 sm:flex-row sm:justify-end sm:space-x-2 sm:px-10", | ||
className, | ||
)} | ||
{...props} | ||
/> | ||
); | ||
DialogFooter.displayName = "DialogFooter"; | ||
|
||
const DialogTitle = forwardRef< | ||
ElementRef<typeof DialogPrimitive.Title>, | ||
ComponentPropsWithoutRef<typeof DialogPrimitive.Title> | ||
>(({ className, ...props }, ref) => ( | ||
<DialogPrimitive.Title | ||
ref={ref} | ||
className={cn( | ||
"font-display font-extrabold text-xl tracking-wide", | ||
className, | ||
)} | ||
{...props} | ||
/> | ||
)); | ||
DialogTitle.displayName = DialogPrimitive.Title.displayName; | ||
|
||
export { | ||
Dialog, | ||
DialogBody, | ||
DialogCloseButton, | ||
DialogContent, | ||
DialogFooter, | ||
DialogHeader, | ||
DialogOverlay, | ||
DialogPortal, | ||
DialogTitle, | ||
DialogTrigger, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
import type { Meta, StoryObj } from "@storybook/react"; | ||
import { | ||
Dialog, | ||
DialogBody, | ||
DialogCloseButton, | ||
DialogContent, | ||
type DialogContentProps, | ||
DialogFooter, | ||
DialogHeader, | ||
DialogTitle, | ||
DialogTrigger, | ||
} from "app/components/ui/Dialog"; | ||
|
||
import type { ComponentProps } from "react"; | ||
|
||
const DialogExample = ({ | ||
size, | ||
longContent, | ||
scrollBody, | ||
showHeader = true, | ||
showFooter, | ||
}: { | ||
size?: DialogContentProps["size"]; | ||
longContent?: ComponentProps<typeof DynamicDialogContent>["longContent"]; | ||
scrollBody?: boolean; | ||
showHeader?: boolean; | ||
showFooter?: boolean; | ||
}) => ( | ||
<Dialog defaultOpen> | ||
<DialogTrigger>Open dialog</DialogTrigger> | ||
<DialogContent size={size} scrollBody={scrollBody}> | ||
{showHeader && ( | ||
<DialogHeader> | ||
<DialogTitle>Awesome dialog</DialogTitle> | ||
</DialogHeader> | ||
)} | ||
|
||
<DialogBody scroll={scrollBody}> | ||
<DynamicDialogContent longContent={longContent} /> | ||
</DialogBody> | ||
|
||
{showFooter && <DialogFooter>Sneaky dialog footer</DialogFooter>} | ||
|
||
<DialogCloseButton /> | ||
</DialogContent> | ||
</Dialog> | ||
); | ||
|
||
const meta: Meta<typeof DialogExample> = { | ||
title: "Design system/Dialog", | ||
component: DialogExample, | ||
}; | ||
|
||
export default meta; | ||
|
||
type Story = StoryObj<typeof DialogExample>; | ||
|
||
export const Default: Story = { | ||
args: { | ||
longContent: false, | ||
size: "md", | ||
scrollBody: false, | ||
showHeader: true, | ||
showFooter: false, | ||
}, | ||
argTypes: { | ||
size: { | ||
control: { | ||
type: "select", | ||
}, | ||
options: ["sm", "md", "lg", "xl", "2xl", "3xl", "4xl"], | ||
}, | ||
}, | ||
}; | ||
|
||
const DynamicDialogContent = ({ longContent }: { longContent?: boolean }) => { | ||
if (!longContent) return <p>This is a simple dialog.</p>; | ||
|
||
return ( | ||
<> | ||
<p> | ||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin a luctus | ||
lorem. Etiam aliquam vel est vel lacinia. Nulla vehicula tortor quis | ||
erat pellentesque, et interdum nisl aliquet. Aenean ac malesuada mauris. | ||
Aliquam scelerisque lorem ut metus interdum, ut venenatis orci egestas. | ||
In semper mollis eros lobortis mollis. Praesent vestibulum convallis | ||
ipsum at fermentum. Donec porta facilisis lacus eu dignissim. Proin | ||
hendrerit orci gravida risus commodo fermentum. Donec finibus sapien eu | ||
nibh mattis dictum. Praesent ac tempor odio, et lobortis ex. Donec nec | ||
mauris et lorem facilisis auctor. Maecenas vitae convallis leo. | ||
</p> | ||
<p> | ||
Maecenas maximus felis scelerisque turpis euismod rutrum. Pellentesque a | ||
dolor scelerisque, elementum sapien eu, porta libero. Donec volutpat | ||
egestas tincidunt. Vivamus blandit eros mollis viverra aliquam. Mauris | ||
eu turpis id est gravida finibus. In viverra, elit eget eleifend | ||
sagittis, massa quam faucibus erat, sed mattis odio nunc vitae enim. | ||
Donec lacus diam, lobortis at facilisis in, placerat ut diam. Maecenas | ||
sed dui sit amet massa tristique vulputate non ac erat. Nullam orci | ||
urna, finibus eu blandit et, pharetra ac enim. Donec magna augue, | ||
interdum at sollicitudin id, fringilla nec sapien. In nisl quam, rhoncus | ||
blandit ipsum in, volutpat aliquam nisi. Nam accumsan lobortis ante, at | ||
tristique ante vehicula eget. Sed ornare varius velit, ut ultrices ante | ||
auctor non. Cras bibendum, libero sed varius fringilla, quam lorem | ||
fermentum nunc, vitae varius mauris libero sed tortor. | ||
</p> | ||
<p> | ||
Donec ut aliquam massa. Etiam congue turpis at purus tempor maximus. | ||
Etiam semper libero non varius pellentesque. Ut egestas faucibus purus | ||
non faucibus. Duis sed nisi consequat, laoreet nisi in, laoreet purus. | ||
Integer aliquam mi ac metus interdum rhoncus. In ac iaculis quam. Sed eu | ||
nibh lectus. Donec imperdiet vestibulum nisl in facilisis. Sed molestie | ||
ipsum eu orci imperdiet cursus. Morbi sit amet quam mi. Etiam maximus | ||
scelerisque orci, id gravida ligula sagittis in. Maecenas volutpat quam | ||
elit, vel vehicula ex fringilla ac. Aenean risus lectus, pellentesque id | ||
est eget, feugiat hendrerit ligula. Nulla tempor pulvinar lacus, ut | ||
euismod tortor. | ||
</p> | ||
</> | ||
); | ||
}; |