(null);
+
+ return (
+
+
+
+ Open Dialog
+
+
+
+
+ Dialog Custom Close
+ Dialog with custom close button
+
+ Dialog content goes here.
+
+
+ Close
+
+
+
+
+
+ );
+}
diff --git a/apps/docs/src/examples/dialog/example-2.tsx b/apps/docs/src/examples/dialog/example-2.tsx
new file mode 100644
index 0000000..e34a985
--- /dev/null
+++ b/apps/docs/src/examples/dialog/example-2.tsx
@@ -0,0 +1,30 @@
+import { useRef, useState } from "react";
+import { Button, Dialog } from "@lemonsqueezy/wedges";
+
+export default function Example() {
+ // You most likely don't need to use `containerRef` in your implementation. This is just for preview.
+ const containerRef = useRef(null);
+ const [open, setOpen] = useState(false);
+
+ return (
+
+ setOpen(true)}>Open Dialog
+
+
+ Dialog content goes here.
+
+ setOpen(false)}>
+ Close
+
+
+
+
+ );
+}
diff --git a/apps/docs/src/examples/dialog/preview.tsx b/apps/docs/src/examples/dialog/preview.tsx
new file mode 100644
index 0000000..36348fd
--- /dev/null
+++ b/apps/docs/src/examples/dialog/preview.tsx
@@ -0,0 +1,25 @@
+import { useRef, useState } from "react";
+import { Button, Dialog } from "@lemonsqueezy/wedges";
+
+export default function Example() {
+ // You most likely don't need to use `containerRef` in your implementation. This is just for preview.
+ const containerRef = useRef(null);
+ const [open, setOpen] = useState(false);
+
+ return (
+
+ setOpen(true)}>Open Dialog
+
+
+ Dialog content goes here.
+
+
+ );
+}
diff --git a/apps/docs/src/examples/index.ts b/apps/docs/src/examples/index.ts
index af92f5c..f9f4bf0 100644
--- a/apps/docs/src/examples/index.ts
+++ b/apps/docs/src/examples/index.ts
@@ -1377,6 +1377,103 @@ export function Example() {
);
}
+`,
+ },
+ "dialog/example-1": {
+ component: lazy(() => import("@/examples/dialog/example-1.tsx")),
+ code: `import { useRef } from "react";
+import { Button, Dialog } from "@lemonsqueezy/wedges";
+
+export function Example() {
+ // You most likely don't need to use \`containerRef\` in your implementation. This is just for preview.
+ const containerRef = useRef(null);
+
+ return (
+
+
+
+ Open Dialog
+
+
+
+
+ Dialog Custom Close
+ Dialog with custom close button
+
+ Dialog content goes here.
+
+
+ Close
+
+
+
+
+
+ );
+}
+`,
+ },
+ "dialog/example-2": {
+ component: lazy(() => import("@/examples/dialog/example-2.tsx")),
+ code: `import { useRef, useState } from "react";
+import { Button, Dialog } from "@lemonsqueezy/wedges";
+
+export function Example() {
+ // You most likely don't need to use \`containerRef\` in your implementation. This is just for preview.
+ const containerRef = useRef(null);
+ const [open, setOpen] = useState(false);
+
+ return (
+
+ setOpen(true)}>Open Dialog
+
+
+ Dialog content goes here.
+
+ setOpen(false)}>
+ Close
+
+
+
+
+ );
+}
+`,
+ },
+ "dialog/preview": {
+ component: lazy(() => import("@/examples/dialog/preview.tsx")),
+ code: `import { useRef, useState } from "react";
+import { Button, Dialog } from "@lemonsqueezy/wedges";
+
+export function Example() {
+ // You most likely don't need to use \`containerRef\` in your implementation. This is just for preview.
+ const containerRef = useRef(null);
+ const [open, setOpen] = useState(false);
+
+ return (
+
+ setOpen(true)}>Open Dialog
+
+
+ Dialog content goes here.
+
+
+ );
+}
`,
},
"dropdown-menu/example-1": {
diff --git a/packages/wedges/package.json b/packages/wedges/package.json
index 8e24529..5fa57cb 100644
--- a/packages/wedges/package.json
+++ b/packages/wedges/package.json
@@ -70,6 +70,7 @@
"@iconicicons/react": "latest",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-checkbox": "^1.0.4",
+ "@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-popover": "^1.0.7",
diff --git a/packages/wedges/src/components/Dialog/Dialog.tsx b/packages/wedges/src/components/Dialog/Dialog.tsx
new file mode 100644
index 0000000..9439f39
--- /dev/null
+++ b/packages/wedges/src/components/Dialog/Dialog.tsx
@@ -0,0 +1,148 @@
+import * as React from "react";
+import { CloseIcon } from "@iconicicons/react";
+import * as DialogPrimitive from "@radix-ui/react-dialog";
+
+import { cn } from "../../helpers/utils";
+import { Button } from "../Button";
+import { dialogVariants } from "./variants";
+
+/* ---------------------------------- Types --------------------------------- */
+export type DialogElement = React.ElementRef;
+export type DialogElementProps = React.ComponentPropsWithoutRef &
+ DialogHeaderElementProps &
+ DialogContentElementProps;
+
+export type DialogHeaderElement = React.ElementRef<"div">;
+export type DialogHeaderElementProps = React.ComponentPropsWithoutRef<"div"> & {
+ title?: React.ReactNode;
+ description?: React.ReactNode;
+};
+
+export type DialogContentElement = React.ElementRef;
+export type DialogContentElementProps = React.ComponentPropsWithoutRef<
+ typeof DialogPrimitive.Content
+> & {
+ size?: keyof typeof dialogVariants;
+ container?: DialogPrimitive.DialogPortalProps["container"];
+};
+
+/* ------------------------------- Components ------------------------------- */
+
+const DialogRoot = React.forwardRef(
+ ({ title, description, size, container, className, ...props }, ref) => (
+
+
+
+ {props.children}
+
+
+ )
+);
+
+const DialogOverlay = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+
+const DialogTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+
+const DialogDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+
+const DialogHeader = React.forwardRef(
+ ({ title, description, children, className, ...props }, ref) => (
+
+ {title && {title} }
+ {description && {description} }
+ {children}
+
+ )
+);
+
+const DialogContent = React.forwardRef(
+ ({ className, size, container, children, ...props }, ref) => (
+
+
+
+ {children}
+
+ } variant="link" size="xs-icon" />
+
+
+
+ )
+);
+
+const DialogFooter = ({ className, ...props }: React.HTMLAttributes) => (
+
+);
+
+DialogRoot.displayName = "Dialog";
+DialogHeader.displayName = "DialogHeader";
+DialogTitle.displayName = "DialogTitle";
+DialogDescription.displayName = "DialogDescription";
+DialogContent.displayName = "DialogContent";
+DialogOverlay.displayName = "DialogOverlay";
+DialogFooter.displayName = "DialogFooter";
+
+const Dialog = Object.assign(DialogRoot, {
+ Root: DialogPrimitive.Root,
+ Title: DialogTitle,
+ Description: DialogDescription,
+ Header: DialogHeader,
+ Trigger: DialogPrimitive.Trigger,
+ Content: DialogContent,
+ Portal: DialogPrimitive.Portal,
+ Close: DialogPrimitive.Close,
+ Overlay: DialogOverlay,
+ Footer: DialogFooter,
+});
+
+export default Dialog;
diff --git a/packages/wedges/src/components/Dialog/index.ts b/packages/wedges/src/components/Dialog/index.ts
new file mode 100644
index 0000000..d5ea3a8
--- /dev/null
+++ b/packages/wedges/src/components/Dialog/index.ts
@@ -0,0 +1,2 @@
+export { default as Dialog } from "./Dialog";
+export type { DialogElement, DialogElementProps } from "./Dialog";
diff --git a/packages/wedges/src/components/Dialog/variants.ts b/packages/wedges/src/components/Dialog/variants.ts
new file mode 100644
index 0000000..9506bf4
--- /dev/null
+++ b/packages/wedges/src/components/Dialog/variants.ts
@@ -0,0 +1,16 @@
+import { cva } from "cva";
+
+export const dialogVariants = cva({
+ base: "w-full max-w-lg",
+ variants: {
+ size: {
+ sm: "sm:max-w-[448px]",
+ md: "sm:max-w-[512px]",
+ lg: "sm:max-w-[672px]",
+ xl: "sm:max-w-[896px]",
+ },
+ },
+ defaultVariants: {
+ size: "md",
+ },
+});
diff --git a/packages/wedges/src/components/index.ts b/packages/wedges/src/components/index.ts
index 57d6a47..44f6051 100644
--- a/packages/wedges/src/components/index.ts
+++ b/packages/wedges/src/components/index.ts
@@ -7,6 +7,7 @@ export * from "./Button";
export * from "./ButtonGroup";
export * from "./Checkbox";
export * from "./CheckboxGroup";
+export * from "./Dialog";
export * from "./DropdownMenu";
export * from "./Input";
export * from "./Kbd";
diff --git a/packages/wedges/src/tw-plugin/plugin.ts b/packages/wedges/src/tw-plugin/plugin.ts
index 79885d4..49420f4 100644
--- a/packages/wedges/src/tw-plugin/plugin.ts
+++ b/packages/wedges/src/tw-plugin/plugin.ts
@@ -138,6 +138,21 @@ const corePlugin = (
const prefixedBaseColors = addPrefix(wedgesPalette, "wg");
const prefixedBoxShadows = addPrefix(boxShadows, "wg");
const animationEasing = "cubic-bezier(.2,1,.4,1)";
+ const transformDefaults = {
+ translateX: "var(--tw-translate-x)",
+ translateY: "var(--tw-translate-y)",
+ rotate: "var(--tw-rotate)",
+ skewX: "var(--tw-skew-x)",
+ skewY: "var(--tw-skew-y)",
+ scaleX: "var(--tw-scale-x)",
+ scaleY: "var(--tw-scale-y)",
+ };
+
+ const animateTransform = (transforms: Partial = {}) => {
+ return Object.entries({ ...transformDefaults, ...transforms })
+ .map(([key, value]) => `${key}(${value})`)
+ .join(" ");
+ };
return plugin(
({ addBase, addUtilities, addVariant, matchUtilities, theme }) => {
@@ -249,6 +264,7 @@ const corePlugin = (
3: "3px",
},
animation: {
+ "wg-fade-in": `fadeIn 0.3s ${animationEasing}`,
"wg-fade-in-up": `fadeInUp 0.3s ${animationEasing}`,
"wg-fade-in-down": `fadeInDown 0.3s ${animationEasing}`,
"wg-fade-in-left": `fadeInLeft 0.3s ${animationEasing}`,
@@ -257,70 +273,80 @@ const corePlugin = (
"wg-line-spinner": "lineSpinner 1.5s ease-in-out infinite both",
},
keyframes: {
+ fadeIn: {
+ "0%": {
+ opacity: "0",
+ transform: animateTransform({ scaleX: ".97", scaleY: ".97" }),
+ },
+ "100%": {
+ opacity: "1",
+ transform: animateTransform({ scaleX: "1", scaleY: "1" }),
+ },
+ },
fadeInUp: {
"0%": {
opacity: "0",
- transform: "translateY(5px) scale(.97)",
+ transform: animateTransform({ translateY: "5px", scaleX: ".97", scaleY: ".97" }),
},
"100%": {
opacity: "1",
- transform: "translateY(0px) scale(1)",
+ transform: animateTransform({ translateY: "0", scaleX: "1", scaleY: "1" }),
},
},
fadeInDown: {
"0%": {
opacity: "0",
- transform: "translateY(-5px) scale(.97)",
+ transform: animateTransform({ translateY: "-5px", scaleX: ".97", scaleY: ".97" }),
},
"100%": {
opacity: "1",
- transform: "translateY(0px) scale(1)",
+ transform: animateTransform({ translateY: "0", scaleX: "1", scaleY: "1" }),
},
},
fadeInLeft: {
"0%": {
opacity: "0",
- transform: "translateX(5px) scale(.97)",
+ transform: animateTransform({ translateX: "5px", scaleX: ".97", scaleY: ".97" }),
},
"100%": {
opacity: "1",
- transform: "translateX(0px) scale(1)",
+ transform: animateTransform({ translateX: "0", scaleX: "1", scaleY: "1" }),
},
},
fadeInRight: {
"0%": {
opacity: "0",
- transform: "translateX(-5px) scale(.97)",
+ transform: animateTransform({ translateX: "-5px", scaleX: ".97", scaleY: ".97" }),
},
"100%": {
opacity: "1",
- transform: "translateX(0px) scale(1)",
+ transform: animateTransform({ translateX: "0", scaleX: "1", scaleY: "1" }),
},
},
fadeOut: {
"0%": {
opacity: "1",
- transform: "scale(1)",
+ transform: animateTransform(),
},
"100%": {
opacity: "0",
- transform: "scale(.97)",
+ transform: animateTransform({ scaleX: ".97", scaleY: ".97" }),
},
},
lineSpinner: {
"0%, 25%": {
strokeDashoffset: "var(--wg-dashoffset-97)",
- transform: "rotate(0)",
+ transform: animateTransform({ rotate: "0" }),
},
"50%, 75%": {
strokeDashoffset: "var(--wg-dashoffset-25)",
- transform: "rotate(45deg)",
+ transform: animateTransform({ rotate: "45deg" }),
},
"100%": {
strokeDashoffset: "var(--wg-dashoffset-97)",
- transform: "rotate(360deg)",
+ transform: animateTransform({ rotate: "360deg" }),
},
},
},
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b504427..c894779 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -214,6 +214,9 @@ importers:
'@radix-ui/react-checkbox':
specifier: ^1.0.4
version: 1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-dialog':
+ specifier: ^1.0.5
+ version: 1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-dropdown-menu':
specifier: ^2.0.6
version: 2.0.6(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -1409,6 +1412,19 @@ packages:
'@types/react':
optional: true
+ '@radix-ui/react-dialog@1.0.5':
+ resolution: {integrity: sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
'@radix-ui/react-direction@1.0.1':
resolution: {integrity: sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==}
peerDependencies:
@@ -7332,6 +7348,29 @@ snapshots:
optionalDependencies:
'@types/react': 18.3.3
+ '@radix-ui/react-dialog@1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.24.7
+ '@radix-ui/primitive': 1.0.1
+ '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.3)(react@18.3.1)
+ '@radix-ui/react-context': 1.0.1(@types/react@18.3.3)(react@18.3.1)
+ '@radix-ui/react-dismissable-layer': 1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-focus-guards': 1.0.1(@types/react@18.3.3)(react@18.3.1)
+ '@radix-ui/react-focus-scope': 1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-id': 1.0.1(@types/react@18.3.3)(react@18.3.1)
+ '@radix-ui/react-portal': 1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-slot': 1.0.2(@types/react@18.3.3)(react@18.3.1)
+ '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.3.3)(react@18.3.1)
+ aria-hidden: 1.2.4
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ react-remove-scroll: 2.5.5(@types/react@18.3.3)(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.3
+ '@types/react-dom': 18.3.0
+
'@radix-ui/react-direction@1.0.1(@types/react@18.3.3)(react@18.3.1)':
dependencies:
'@babel/runtime': 7.24.7