From a9dba793ba4029f23d7af36fbb69815f12888398 Mon Sep 17 00:00:00 2001 From: Sapayth Hossain Date: Mon, 9 Feb 2026 20:31:19 +0600 Subject: [PATCH 1/5] enhance: storybook modal --- src/components/ui/Modal.stories.tsx | 91 +++++++++++++++++++++++++++-- src/components/ui/modal.tsx | 6 +- src/styles.css | 16 +++++ 3 files changed, 107 insertions(+), 6 deletions(-) diff --git a/src/components/ui/Modal.stories.tsx b/src/components/ui/Modal.stories.tsx index a01a1a7..a907ae3 100644 --- a/src/components/ui/Modal.stories.tsx +++ b/src/components/ui/Modal.stories.tsx @@ -1,13 +1,34 @@ import type { Meta, StoryObj } from "@storybook/react"; -import { useState } from "react"; +import React, { useState } from "react"; import { Modal, ModalHeader, ModalTitle, - ModalDescription, ModalFooter, Button, + Input, + LabeledSwitch, + Field, + FieldLabel, + FieldDescription, + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, } from "./index"; +import { ChevronDownIcon } from "lucide-react"; + +const ZONE_NAME_HELPER = "Give a meaningful name for your reference"; + +const COUNTRIES = [ + "Bangladesh", + "United States", + "United Kingdom", + "Canada", + "Australia", + "Germany", + "France", +] as const; function ModalDemo() { const [open, setOpen] = useState(false); @@ -17,11 +38,12 @@ function ModalDemo() { setOpen(false)}> Modal Title - Description text here.
Modal content.
- +
@@ -29,11 +51,68 @@ function ModalDemo() { ); } +function CreateShippingZoneDemo() { + const [open, setOpen] = useState(false); + const [restOfWorld, setRestOfWorld] = useState(false); + + return ( + <> + + setOpen(false)} size="default"> + + Create Shipping Zone + +
+ + Zone Name + + {ZONE_NAME_HELPER} + + + setRestOfWorld(checked)} + /> + + + Countries + + + + + + {COUNTRIES.map((country) => ( + {country} + ))} + + + {ZONE_NAME_HELPER} + +
+ + + + +
+ + ); +} + const meta = { title: "UI/Modal", component: Modal, parameters: { layout: "centered" }, tags: ["autodocs"], + args: { open: false, onClose: () => {} }, } satisfies Meta; export default meta; @@ -41,3 +120,7 @@ export default meta; type Story = StoryObj; export const Default: Story = { render: () => }; + +export const CreateShippingZone: Story = { + render: () => , +}; diff --git a/src/components/ui/modal.tsx b/src/components/ui/modal.tsx index d55ddd9..be9c95e 100644 --- a/src/components/ui/modal.tsx +++ b/src/components/ui/modal.tsx @@ -80,8 +80,9 @@ const ModalHeader = forwardRef( ({ className, ...props }, ref) => (
( ({ className, ...props }, ref) => (
Date: Mon, 9 Feb 2026 21:31:50 +0600 Subject: [PATCH 2/5] enhance: alert component for storybook --- src/components/ui/Alert.stories.tsx | 136 ++++++++++++++++++++++++---- 1 file changed, 120 insertions(+), 16 deletions(-) diff --git a/src/components/ui/Alert.stories.tsx b/src/components/ui/Alert.stories.tsx index c8df6b2..5574269 100644 --- a/src/components/ui/Alert.stories.tsx +++ b/src/components/ui/Alert.stories.tsx @@ -1,5 +1,29 @@ import type { Meta, StoryObj } from "@storybook/react"; -import { Alert, AlertTitle, AlertDescription } from "./alert"; +import React from "react"; +import { + AlertTriangle, + CheckCircle, + Info as InfoIcon, + X, + XCircle, +} from "lucide-react"; +import { Alert, AlertAction, AlertDescription, AlertTitle } from "./alert"; +import { Button } from "./button"; + +const VARIANT_ICONS = { + default: InfoIcon, + destructive: XCircle, + success: CheckCircle, + warning: AlertTriangle, + info: InfoIcon, +} as const; + +type AlertVariant = keyof typeof VARIANT_ICONS; + +function AlertIcon({ variant }: { variant: AlertVariant }) { + const Icon = VARIANT_ICONS[variant] ?? InfoIcon; + return ; +} const meta = { title: "UI/Alert", @@ -19,8 +43,9 @@ export default meta; type Story = StoryObj; export const Default: Story = { - render: () => ( - + args: { variant: "default" }, + render: (args) => ( + Title Description text goes here. @@ -28,8 +53,9 @@ export const Default: Story = { }; export const Destructive: Story = { - render: () => ( - + args: { variant: "destructive" }, + render: (args) => ( + Error Something went wrong. @@ -37,21 +63,19 @@ export const Destructive: Story = { }; export const Success: Story = { - args: { - variant: "default" - }, - - render: () => ( - + args: { variant: "success" }, + render: (args) => ( + Success Your changes have been saved. - ) + ), }; export const Warning: Story = { - render: () => ( - + args: { variant: "warning" }, + render: (args) => ( + Warning Please review before continuing. @@ -59,10 +83,90 @@ export const Warning: Story = { }; export const Info: Story = { - render: () => ( - + args: { variant: "info" }, + render: (args) => ( + Info New update available. ), }; + +const defaultVariant: AlertVariant = "default"; + +export const WithDescription: Story = { + args: { variant: defaultVariant }, + render: (args) => ( + + + Hold on I need at least a few minutes! + + Lorem Ipsum is simply dummy text of the printing and typesetting industry. + + + + + + ), +}; + +export const WithoutDescription: Story = { + args: { variant: defaultVariant }, + render: (args) => ( + + + Hold on I need at least a few minutes! + + + + + ), +}; + +export const WithButtons: Story = { + args: { variant: defaultVariant }, + render: (args) => ( + + +
+ Hold on I need at least a few minutes! +
+ + +
+
+ + + +
+ ), +}; + +export const WithDescriptionAndButtons: Story = { + args: { variant: defaultVariant }, + render: (args) => ( + + + Hold on I need at least a few minutes! + + This process may take a few minutes to complete. See the{" "} + documentation for more details. + +
+ + +
+ + + +
+ ), +}; From 8bc5777341d517292b9f369f93d19960b1104c8d Mon Sep 17 00:00:00 2001 From: Sapayth Hossain Date: Mon, 9 Feb 2026 21:41:45 +0600 Subject: [PATCH 3/5] css fixes --- src/components/ui/Alert.stories.tsx | 2 +- src/components/ui/alert.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ui/Alert.stories.tsx b/src/components/ui/Alert.stories.tsx index 5574269..438524e 100644 --- a/src/components/ui/Alert.stories.tsx +++ b/src/components/ui/Alert.stories.tsx @@ -131,8 +131,8 @@ export const WithButtons: Story = { args: { variant: defaultVariant }, render: (args) => ( -
+ Hold on I need at least a few minutes!
diff --git a/src/components/ui/alert.tsx b/src/components/ui/alert.tsx index 3d4a72a..2e17ca1 100644 --- a/src/components/ui/alert.tsx +++ b/src/components/ui/alert.tsx @@ -4,7 +4,7 @@ import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "@/lib/utils"; const alertVariants = cva( - "grid gap-2.5 rounded-lg border px-5 py-3 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2.5 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4 w-full relative group/alert", + "grid gap-2.5 rounded-lg border p-5 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2.5 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4 w-full relative group/alert", { variants: { variant: { From 00f9a980bb919d24f8916cbb41cee2139d4fd6a7 Mon Sep 17 00:00:00 2001 From: Sapayth Hossain Date: Mon, 9 Feb 2026 22:26:23 +0600 Subject: [PATCH 4/5] enhance: storybook confirmation --- src/components/ui/Confirmation.stories.tsx | 171 +++++++++++++++++++++ src/components/ui/modal.tsx | 2 +- src/components/ui/notice.tsx | 2 +- 3 files changed, 173 insertions(+), 2 deletions(-) create mode 100644 src/components/ui/Confirmation.stories.tsx diff --git a/src/components/ui/Confirmation.stories.tsx b/src/components/ui/Confirmation.stories.tsx new file mode 100644 index 0000000..63ee47e --- /dev/null +++ b/src/components/ui/Confirmation.stories.tsx @@ -0,0 +1,171 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import React, { useState } from "react"; +import { Modal, ModalTitle, Button, Notice } from "./index"; +import { Minus, Info } from "lucide-react"; +import { cn } from "@/lib/utils"; + +function LeaveWithUnsavedChangesDemo() { + const [open, setOpen] = useState(false); + return ( + <> + + setOpen(false)} className="border-0"> +
+
+ + + + + +
+ + Are you sure you want to leave? + +
+

+ You have unsaved changes. +

+
+ + +
+
+ + ); +} + +function CancelSubscriptionDemo() { + const [open, setOpen] = useState(false); + return ( + <> + + setOpen(false)} className="border-0"> +
+
+ + + + + + + + + + + + +
+ + Are you sure you want to cancel the subscription plan? + +
+ + +

+ Next billing date is 25th March 2024 and reassign + is not possible for recurring subscription +

+
+
+ + +
+
+ + ); +} + +function DiscardDraftDemo() { + const [open, setOpen] = useState(false); + return ( + <> + + setOpen(false)} className="border-0"> +
+
+ + + + + + + + + + + + +
+ + Are you sure you want to cancel the subscription plan? + +
+
+ + +
+
+ + ); +} + +const meta = { + title: "UI/Confirmation", + component: Modal, + parameters: { layout: "centered" }, + tags: ["autodocs"], + args: { open: false, onClose: () => {} }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const LeaveWithUnsavedChanges: Story = { + render: () => , +}; + +export const CancelSubscription: Story = { + render: () => , +}; + +export const DiscardDraft: Story = { + render: () => , +}; diff --git a/src/components/ui/modal.tsx b/src/components/ui/modal.tsx index be9c95e..cb25962 100644 --- a/src/components/ui/modal.tsx +++ b/src/components/ui/modal.tsx @@ -174,7 +174,7 @@ const ModalClose = forwardRef( ref={ref} type="button" className={cn( - "absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity", + "absolute right-4 top-4 cursor-pointer rounded-sm opacity-70 ring-offset-background transition-opacity", "hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", "disabled:pointer-events-none", className, diff --git a/src/components/ui/notice.tsx b/src/components/ui/notice.tsx index 5ae6d5c..ed2960b 100644 --- a/src/components/ui/notice.tsx +++ b/src/components/ui/notice.tsx @@ -4,7 +4,7 @@ import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "@/lib/utils"; const noticeVariants = cva( - "relative grid gap-2.5 rounded-[3px] border-l-[3px] border-y-0 border-r-0 px-4 py-3 text-left text-sm has-data-[slot=notice-action]:pr-10 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2.5 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4 w-full group/notice", + "relative grid gap-2.5 rounded-[3px] border-l-[3px] border-y-0 border-r-0 px-4 py-3 text-left text-sm has-data-[slot=notice-action]:pr-10 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2.5 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4 group/notice", { variants: { variant: { From c785cbd93aaf2165fdb578e295322c4ba0dfb063 Mon Sep 17 00:00:00 2001 From: Sapayth Hossain Date: Tue, 10 Feb 2026 12:45:58 +0600 Subject: [PATCH 5/5] enhance component for storybook --- src/components/ui/Confirmation.stories.tsx | 193 ++++++++------------- src/components/ui/Modal.stories.tsx | 3 +- src/components/ui/confirmation.tsx | 103 +++++++++++ src/components/ui/index.ts | 5 + src/components/ui/modal.tsx | 171 ++++++++++++++---- src/providers/index.ts | 1 + src/providers/theme-provider.tsx | 8 + 7 files changed, 334 insertions(+), 150 deletions(-) create mode 100644 src/components/ui/confirmation.tsx diff --git a/src/components/ui/Confirmation.stories.tsx b/src/components/ui/Confirmation.stories.tsx index 63ee47e..169356a 100644 --- a/src/components/ui/Confirmation.stories.tsx +++ b/src/components/ui/Confirmation.stories.tsx @@ -1,43 +1,22 @@ import type { Meta, StoryObj } from "@storybook/react"; import React, { useState } from "react"; -import { Modal, ModalTitle, Button, Notice } from "./index"; -import { Minus, Info } from "lucide-react"; -import { cn } from "@/lib/utils"; +import { Confirmation, Button, Notice } from "./index"; +import { CircleMinus } from "lucide-react"; function LeaveWithUnsavedChangesDemo() { const [open, setOpen] = useState(false); return ( <> - setOpen(false)} className="border-0"> -
-
- - - - - -
- - Are you sure you want to leave? - -
-

- You have unsaved changes. -

-
- - -
-
+ setOpen(false)} + title="Are you sure you want to leave?" + body="You have unsaved changes." + confirmLabel="Leave" + cancelLabel="Stay in page" + onConfirm={() => setOpen(false)} + /> ); } @@ -49,53 +28,23 @@ function CancelSubscriptionDemo() { - setOpen(false)} className="border-0"> -
-
- - - - - - - - - - - - -
- - Are you sure you want to cancel the subscription plan? - -
- - -

- Next billing date is 25th March 2024 and reassign - is not possible for recurring subscription -

-
-
- - -
-
+ setOpen(false)} + title="Are you sure you want to cancel the subscription plan?" + body={ + +

+ Next billing date is 25th March 2024 and reassign + is not possible for recurring subscription +

+
+ } + icon={} + confirmLabel="Yes, Cancel" + cancelLabel="No" + onConfirm={() => setOpen(false)} + /> ); } @@ -105,54 +54,54 @@ function DiscardDraftDemo() { return ( <> + setOpen(false)} + title="Are you sure you want to discard this draft?" + confirmLabel="Yes, Cancel" + cancelLabel="No" + onConfirm={() => setOpen(false)} + /> + + ); +} + +function DestructiveDemo() { + const [open, setOpen] = useState(false); + return ( + <> + - setOpen(false)} className="border-0"> -
-
- - - - - - - - - - - - -
- - Are you sure you want to cancel the subscription plan? - -
-
- - -
-
+ setOpen(false)} + title="Delete this item?" + body="This action cannot be undone." + variant="destructive" + confirmLabel="Delete" + cancelLabel="Cancel" + onConfirm={() => setOpen(false)} + /> ); } const meta = { title: "UI/Confirmation", - component: Modal, + component: Confirmation, parameters: { layout: "centered" }, tags: ["autodocs"], - args: { open: false, onClose: () => {} }, -} satisfies Meta; + args: { + open: false, + onClose: () => {}, + title: "Confirm action", + confirmLabel: "Confirm", + cancelLabel: "Cancel", + }, +} satisfies Meta; export default meta; @@ -160,12 +109,20 @@ type Story = StoryObj; export const LeaveWithUnsavedChanges: Story = { render: () => , + args: { title: "Are you sure you want to leave?", body: "You have unsaved changes." }, }; export const CancelSubscription: Story = { render: () => , + args: { title: "Cancel subscription?" }, }; export const DiscardDraft: Story = { render: () => , + args: { title: "Discard this draft?" }, +}; + +export const Destructive: Story = { + render: () => , + args: { title: "Delete this item?", variant: "destructive" }, }; diff --git a/src/components/ui/Modal.stories.tsx b/src/components/ui/Modal.stories.tsx index a907ae3..3d0ef94 100644 --- a/src/components/ui/Modal.stories.tsx +++ b/src/components/ui/Modal.stories.tsx @@ -15,6 +15,7 @@ import { DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, + ModalDescription, } from "./index"; import { ChevronDownIcon } from "lucide-react"; @@ -39,7 +40,7 @@ function ModalDemo() { Modal Title -
Modal content.
+ Modal content. + + + + ); +} diff --git a/src/components/ui/index.ts b/src/components/ui/index.ts index 68be726..ad84888 100644 --- a/src/components/ui/index.ts +++ b/src/components/ui/index.ts @@ -51,6 +51,11 @@ export { Modal, ModalClose, ModalContent, ModalDescription, ModalFooter, ModalHeader, ModalOverlay, ModalTitle, type ModalProps } from "./modal"; +export { + Confirmation, + type ConfirmationProps, + type ConfirmationVariant, +} from "./confirmation"; export { ComponentPreview, DesignSystemSection, diff --git a/src/components/ui/modal.tsx b/src/components/ui/modal.tsx index cb25962..5a22d0b 100644 --- a/src/components/ui/modal.tsx +++ b/src/components/ui/modal.tsx @@ -1,6 +1,10 @@ import { + createContext, forwardRef, + useContext, useEffect, + useId, + useLayoutEffect, useRef, useState, type HTMLAttributes, @@ -8,6 +12,7 @@ import { } from "react"; import { createPortal } from "react-dom"; import { cn } from "@/lib/utils"; +import { getThemeStyles, useThemeOptional } from "@/providers"; /* ============================================ Modal Overlay @@ -15,12 +20,15 @@ import { cn } from "@/lib/utils"; interface ModalOverlayProps extends HTMLAttributes { onClose?: () => void; + "data-state"?: "open" | "closed"; } const ModalOverlay = forwardRef( - ({ className, onClose, ...props }, ref) => ( + ({ className, onClose, "data-state": dataState, ...props }, ref) => (