Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 120 additions & 16 deletions src/components/ui/Alert.stories.tsx
Original file line number Diff line number Diff line change
@@ -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 <Icon />;
}

const meta = {
title: "UI/Alert",
Expand All @@ -19,50 +43,130 @@ export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
render: () => (
<Alert variant="default">
args: { variant: "default" },
render: (args) => (
<Alert variant={args.variant}>
<AlertTitle>Title</AlertTitle>
<AlertDescription>Description text goes here.</AlertDescription>
</Alert>
),
};

export const Destructive: Story = {
render: () => (
<Alert variant="destructive">
args: { variant: "destructive" },
render: (args) => (
<Alert variant={args.variant}>
<AlertTitle>Error</AlertTitle>
<AlertDescription>Something went wrong.</AlertDescription>
</Alert>
),
};

export const Success: Story = {
args: {
variant: "default"
},

render: () => (
<Alert variant="success">
args: { variant: "success" },
render: (args) => (
<Alert variant={args.variant}>
<AlertTitle>Success</AlertTitle>
<AlertDescription>Your changes have been saved.</AlertDescription>
</Alert>
)
),
};

export const Warning: Story = {
render: () => (
<Alert variant="warning">
args: { variant: "warning" },
render: (args) => (
<Alert variant={args.variant}>
<AlertTitle>Warning</AlertTitle>
<AlertDescription>Please review before continuing.</AlertDescription>
</Alert>
),
};

export const Info: Story = {
render: () => (
<Alert variant="info">
args: { variant: "info" },
render: (args) => (
<Alert variant={args.variant}>
<AlertTitle>Info</AlertTitle>
<AlertDescription>New update available.</AlertDescription>
</Alert>
),
};

const defaultVariant: AlertVariant = "default";

export const WithDescription: Story = {
args: { variant: defaultVariant },
render: (args) => (
<Alert variant={args.variant}>
<AlertIcon variant={args.variant ?? defaultVariant} />
<AlertTitle>Hold on I need at least a few minutes!</AlertTitle>
<AlertDescription>
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
</AlertDescription>
<AlertAction>
<Button variant="ghost" size="icon-sm" aria-label="Dismiss">
<X />
</Button>
</AlertAction>
</Alert>
),
};

export const WithoutDescription: Story = {
args: { variant: defaultVariant },
render: (args) => (
<Alert variant={args.variant}>
<AlertIcon variant={args.variant ?? defaultVariant} />
<AlertTitle>Hold on I need at least a few minutes!</AlertTitle>
<AlertAction>
<Button variant="ghost" size="icon-sm" aria-label="Dismiss">
<X />
</Button>
</AlertAction>
</Alert>
),
};

export const WithButtons: Story = {
args: { variant: defaultVariant },
render: (args) => (
<Alert variant={args.variant}>
<div className="flex items-center justify-between gap-4 col-start-2">
<AlertIcon variant={args.variant ?? defaultVariant} />
<AlertTitle>Hold on I need at least a few minutes!</AlertTitle>
<div className="flex gap-2">
<Button variant="default">Primary</Button>
<Button variant="outline">Button</Button>
</div>
</div>
<AlertAction>
<Button variant="ghost" size="icon-sm" aria-label="Dismiss">
<X />
</Button>
</AlertAction>
</Alert>
),
};
Comment on lines +130 to +149
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

col-start-2 has no effect in the WithButtons story.

The Alert grid only switches to a 2-column layout (grid-cols-[auto_1fr]) when it has-[>svg] as a direct child. In this story, the SVG (from AlertIcon) is nested inside the <div className="flex ..."> wrapper, so the grid stays single-column and col-start-2 on line 134 is dead CSS.

If the intent is a single-row horizontal layout, the class can simply be removed. If you want the 2-column grid to activate, move <AlertIcon> out of the flex wrapper to be a direct child of <Alert>.

🤖 Prompt for AI Agents
In `@src/components/ui/Alert.stories.tsx` around lines 130 - 149, In the
WithButtons Story (render function), the col-start-2 utility on the inner div is
ineffective because AlertIcon is nested inside that flex wrapper instead of
being a direct child of Alert; either remove the dead class "col-start-2" if you
intend a single-row horizontal layout, or move the <AlertIcon> component out of
the inner <div className="flex..."> so AlertIcon becomes a direct child of Alert
(which will trigger the grid-cols-[auto_1fr] two-column layout); update the
WithButtons story accordingly.


export const WithDescriptionAndButtons: Story = {
args: { variant: defaultVariant },
render: (args) => (
<Alert variant={args.variant}>
<AlertIcon variant={args.variant ?? defaultVariant} />
<AlertTitle>Hold on I need at least a few minutes!</AlertTitle>
<AlertDescription>
This process may take a few minutes to complete. See the{" "}
<a href="#">documentation</a> for more details.
</AlertDescription>
<div className="flex gap-2 pt-1 col-start-2">
<Button variant="default">Primary</Button>
<Button variant="outline">Button</Button>
</div>
<AlertAction>
<Button variant="ghost" size="icon-sm" aria-label="Dismiss">
<X />
</Button>
</AlertAction>
</Alert>
),
};
91 changes: 87 additions & 4 deletions src/components/ui/Modal.stories.tsx
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -17,27 +38,89 @@ function ModalDemo() {
<Modal open={open} onClose={() => setOpen(false)}>
<ModalHeader>
<ModalTitle>Modal Title</ModalTitle>
<ModalDescription>Description text here.</ModalDescription>
</ModalHeader>
<div className="p-6">Modal content.</div>
<ModalFooter>
<Button variant="outline" onClick={() => setOpen(false)}>Cancel</Button>
<Button variant="outline" onClick={() => setOpen(false)}>
Cancel
</Button>
<Button onClick={() => setOpen(false)}>Save</Button>
</ModalFooter>
</Modal>
</>
);
}

function CreateShippingZoneDemo() {
const [open, setOpen] = useState(false);
const [restOfWorld, setRestOfWorld] = useState(false);

return (
<>
<Button onClick={() => setOpen(true)}>Create Shipping Zone</Button>
<Modal open={open} onClose={() => setOpen(false)} size="default">
<ModalHeader>
<ModalTitle>Create Shipping Zone</ModalTitle>
</ModalHeader>
<div className="flex flex-col gap-6 px-8 py-6">
<Field>
<FieldLabel>Zone Name</FieldLabel>
<Input placeholder="Type" />
<FieldDescription>{ZONE_NAME_HELPER}</FieldDescription>
</Field>

<LabeledSwitch
label="Rest of the world"
checked={restOfWorld}
onCheckedChange={(checked) => setRestOfWorld(checked)}
/>

<Field>
<FieldLabel>Countries</FieldLabel>
<DropdownMenu>
<DropdownMenuTrigger>
<Button
variant="outline"
className="h-9 w-full justify-between font-normal"
>
Bangladesh, United States
<ChevronDownIcon className="size-4 opacity-50" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-[var(--anchor-width)]">
{COUNTRIES.map((country) => (
<DropdownMenuItem key={country}>{country}</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
<FieldDescription>{ZONE_NAME_HELPER}</FieldDescription>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Copy-paste issue: wrong helper text for the Countries field.

ZONE_NAME_HELPER ("Give a meaningful name for your reference") is reused as the description for the Countries field. This is clearly meant for the Zone Name field only.

Proposed fix
-            <FieldDescription>{ZONE_NAME_HELPER}</FieldDescription>
+            <FieldDescription>Select the countries for this shipping zone</FieldDescription>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<FieldDescription>{ZONE_NAME_HELPER}</FieldDescription>
<FieldDescription>Select the countries for this shipping zone</FieldDescription>
🤖 Prompt for AI Agents
In `@src/components/ui/Modal.stories.tsx` at line 96, The FieldDescription for the
Countries field is using the wrong constant (ZONE_NAME_HELPER); change it to the
appropriate countries helper (e.g., COUNTRIES_HELPER or a new constant like
COUNTRIES_HELPER = "Select one or more countries for this zone") and update the
usage in the Modal story where FieldDescription currently renders
ZONE_NAME_HELPER; also ensure the helper constant is imported or defined
alongside other helper constants so the Countries field shows the correct
descriptive text.

</Field>
</div>
<ModalFooter>
<Button variant="outline" onClick={() => setOpen(false)}>
Cancel
</Button>
<Button onClick={() => setOpen(false)}>Create</Button>
</ModalFooter>
</Modal>
</>
);
}

const meta = {
title: "UI/Modal",
component: Modal,
parameters: { layout: "centered" },
tags: ["autodocs"],
args: { open: false, onClose: () => {} },
} satisfies Meta<typeof Modal>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Default: Story = { render: () => <ModalDemo /> };

export const CreateShippingZone: Story = {
render: () => <CreateShippingZoneDemo />,
};
2 changes: 1 addition & 1 deletion src/components/ui/alert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
6 changes: 4 additions & 2 deletions src/components/ui/modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,9 @@ const ModalHeader = forwardRef<HTMLDivElement, ModalHeaderProps>(
({ className, ...props }, ref) => (
<div
ref={ref}
data-slot="modal-header"
className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left border-b border-border py-4 px-8",
"flex flex-col space-y-1.5 text-center sm:text-left border-b py-4 px-8",
className,
)}
Comment on lines +83 to 87
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Removing border-border breaks theme-driven border colors.

By dropping the border-border Tailwind utility, the header border color is no longer tied to the --border design token. Instead it relies on a hardcoded #E9E9E9 override in styles.css. This means dark mode (.pui-root.dark) and any per-plugin theme overrides via ThemeProvider will not affect these borders.

Consider keeping the Tailwind utility and fixing the root cause (likely a CSS reset stripping border-color). For example, the scoped override in styles.css could set border-color: var(--border) instead of a hardcoded hex value, preserving the design-token pipeline.

Proposed fix (modal.tsx)
-        "flex flex-col space-y-1.5 text-center sm:text-left border-b py-4 px-8",
+        "flex flex-col space-y-1.5 text-center sm:text-left border-b border-border py-4 px-8",

The same applies to ModalFooter at Line 153.

🤖 Prompt for AI Agents
In `@src/components/ui/modal.tsx` around lines 83 - 87, The modal header/footer
lost the Tailwind border utility so borders no longer follow the theme token;
restore the theme-driven border by re-adding the Tailwind class (e.g.,
"border-border") to the className used on the element with
data-slot="modal-header" and the ModalFooter element, and fix the scoped CSS
override (in styles.css) that currently hardcodes `#E9E9E9` by changing it to use
border-color: var(--border) so the design token and ThemeProvider/dark mode
continue to control the border color.

{...props}
Expand Down Expand Up @@ -147,8 +148,9 @@ const ModalFooter = forwardRef<HTMLDivElement, ModalFooterProps>(
({ className, ...props }, ref) => (
<div
ref={ref}
data-slot="modal-footer"
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 border-t border-border py-5 px-8",
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 border-t py-5 px-8",
className,
)}
{...props}
Expand Down
16 changes: 16 additions & 0 deletions src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,22 @@
-moz-osx-font-smoothing: grayscale;
}

/* ============================================
Modal Header (overrides reset border-color)
============================================ */

.pui-root [data-slot="modal-header"] {
border-bottom: 1px solid #E9E9E9;
}

/* ============================================
Modal Footer (overrides reset border-color)
============================================ */

.pui-root [data-slot="modal-footer"] {
border-top: 1px solid #E9E9E9;
}
Comment on lines +249 to +259
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Hardcoded color #E9E9E9 bypasses the design-token system and breaks dark mode.

These overrides should use the existing --border token so that dark mode and per-plugin theming continue to work:

Proposed fix
 .pui-root [data-slot="modal-header"] {
-    border-bottom: 1px solid `#E9E9E9`;
+    border-bottom-color: var(--border);
 }

 .pui-root [data-slot="modal-footer"] {
-    border-top: 1px solid `#E9E9E9`;
+    border-top-color: var(--border);
 }

If border-border is restored in the Tailwind classes (as suggested in modal.tsx), these CSS overrides may not be needed at all.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.pui-root [data-slot="modal-header"] {
border-bottom: 1px solid #E9E9E9;
}
/* ============================================
Modal Footer (overrides reset border-color)
============================================ */
.pui-root [data-slot="modal-footer"] {
border-top: 1px solid #E9E9E9;
}
.pui-root [data-slot="modal-header"] {
border-bottom-color: var(--border);
}
/* ============================================
Modal Footer (overrides reset border-color)
============================================ */
.pui-root [data-slot="modal-footer"] {
border-top-color: var(--border);
}
🤖 Prompt for AI Agents
In `@src/styles.css` around lines 249 - 259, The modal header/footer CSS uses a
hardcoded color `#E9E9E9` which bypasses design tokens and breaks dark mode;
update the selectors .pui-root [data-slot="modal-header"] and .pui-root
[data-slot="modal-footer"] to use the design token (e.g. border: 1px solid
var(--border)) instead of the hex value, or remove these overrides entirely if
the Tailwind class (border-border) is restored in modal.tsx so the tokenized
border is applied consistently.


/* ============================================
Component Base Styles
============================================ */
Expand Down