Skip to content
Open
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
12 changes: 7 additions & 5 deletions src/components/ElevationAndShape.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import type { Meta, StoryObj } from "@storybook/react-vite"

const ELEVATION_LEVELS = [
{ level: 0, cssClass: "shadow-elevation-0", cssVar: "--elevation-0", description: "Flat / no shadow" },
{ level: 1, cssClass: "shadow-elevation-1", cssVar: "--elevation-1", description: "Cards, buttons at rest" },
{ level: 2, cssClass: "shadow-elevation-2", cssVar: "--elevation-2", description: "Raised cards, menus" },
{ level: 3, cssClass: "shadow-elevation-3", cssVar: "--elevation-3", description: "Navigation drawers, FABs" },
{ level: 4, cssClass: "shadow-elevation-4", cssVar: "--elevation-4", description: "App bars, elevated navigation" },
{ level: 5, cssClass: "shadow-elevation-5", cssVar: "--elevation-5", description: "Dialogs, modals" },
{ level: 1, cssClass: "shadow-elevation-1", cssVar: "--elevation-1", description: "Hairline raise (≙ shadow-2xs)" },
{ level: 2, cssClass: "shadow-elevation-2", cssVar: "--elevation-2", description: "Subtle cards, chips (≙ shadow-xs)" },
{ level: 3, cssClass: "shadow-elevation-3", cssVar: "--elevation-3", description: "Cards, tab pills, sidebar (≙ shadow-sm)" },
{ level: 4, cssClass: "shadow-elevation-4", cssVar: "--elevation-4", description: "Menus, popovers, selects (≙ shadow-md)" },
{ level: 5, cssClass: "shadow-elevation-5", cssVar: "--elevation-5", description: "Dialogs, sheets, submenus (≙ shadow-lg)" },
{ level: 6, cssClass: "shadow-elevation-6", cssVar: "--elevation-6", description: "Reserved headroom (≙ shadow-xl)" },
{ level: 7, cssClass: "shadow-elevation-7", cssVar: "--elevation-7", description: "Maximum lift (≙ shadow-2xl)" },
]

// ---------------------------------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion src/components/ai/confirmation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export const Confirmation = ({
<motion.div
{...passthroughProps}
className={cn(
"flex flex-col gap-4 rounded-xl border bg-card p-5 shadow-sm",
"flex flex-col gap-4 rounded-xl border bg-card p-5 shadow-elevation-3",
className
)}
exit={{ opacity: 0, scale: 0.96 }}
Expand Down
2 changes: 1 addition & 1 deletion src/components/ai/queue.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ export const Queue = ({ className, isStreaming = false, children, style, id, ...
<motion.div
{...passthroughProps}
className={cn(
"flex flex-col gap-2 rounded-xl border border-border bg-background px-2 pb-2 pt-2 shadow-xs",
"flex flex-col gap-2 rounded-xl border border-border bg-background px-2 pb-2 pt-2 shadow-elevation-2",
className
)}
exit={{ opacity: 0, height: 0, marginTop: 0, marginBottom: 0, paddingTop: 0, paddingBottom: 0, overflow: "hidden" }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -582,7 +582,7 @@ export function PlatePaintGrid<T extends WellRecord = WellRecord>({
<div
className={cn(
"relative inline-block",
framed && "rounded-xl border bg-card p-3 shadow-sm",
framed && "rounded-xl border bg-card p-3 shadow-elevation-3",
)}
data-slot="plate-paint-grid-frame"
>
Expand Down
2 changes: 1 addition & 1 deletion src/components/ui/combobox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ function ComboboxContent({
<ComboboxPrimitive.Popup
data-slot="combobox-content"
data-chips={!!anchor}
className={cn("group/combobox-content relative max-h-(--available-height) w-(--anchor-width) max-w-(--available-width) min-w-[calc(var(--anchor-width)+--spacing(7))] origin-(--transform-origin) overflow-hidden rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[chips=true]:min-w-(--anchor-width) data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 *:data-[slot=input-group]:m-1 *:data-[slot=input-group]:mb-0 *:data-[slot=input-group]:h-8 *:data-[slot=input-group]:border-input/30 *:data-[slot=input-group]:bg-input/30 *:data-[slot=input-group]:shadow-none data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
className={cn("group/combobox-content relative max-h-(--available-height) w-(--anchor-width) max-w-(--available-width) min-w-[calc(var(--anchor-width)+--spacing(7))] origin-(--transform-origin) overflow-hidden rounded-lg bg-popover text-popover-foreground shadow-elevation-4 ring-1 ring-foreground/10 duration-100 data-[chips=true]:min-w-(--anchor-width) data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 *:data-[slot=input-group]:m-1 *:data-[slot=input-group]:mb-0 *:data-[slot=input-group]:h-8 *:data-[slot=input-group]:border-input/30 *:data-[slot=input-group]:bg-input/30 *:data-[slot=input-group]:shadow-none data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
{...props}
/>
</ComboboxPrimitive.Positioner>
Expand Down
239 changes: 238 additions & 1 deletion src/components/ui/context-menu.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
import { CopyIcon, FolderIcon, PencilIcon, Trash2Icon } from "lucide-react"
import { expect, within } from "storybook/test"
import React from "react"
import { expect, userEvent, waitFor, within } from "storybook/test"

import {
ContextMenu,
ContextMenuCheckboxItem,
ContextMenuContent,
ContextMenuGroup,
ContextMenuItem,
ContextMenuLabel,
ContextMenuRadioGroup,
ContextMenuRadioItem,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuTrigger,
} from "./context-menu"

Expand Down Expand Up @@ -122,4 +131,232 @@ export const Destructive: Story = {
expect(await body.findByText("Delete")).toBeInTheDocument()
})
},
}

export const WithGroup: Story = {
render: () => (
<ContextMenu>
<ContextMenuTrigger className="flex h-40 w-[320px] items-center justify-center rounded-xl border border-dashed text-sm text-muted-foreground">
Right click this area
</ContextMenuTrigger>
<ContextMenuContent className="w-52">
<ContextMenuGroup>
<ContextMenuLabel>File</ContextMenuLabel>
<ContextMenuItem>
<PencilIcon />
Rename
<ContextMenuShortcut>⌘R</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem>
<CopyIcon />
Duplicate
<ContextMenuShortcut>⌘D</ContextMenuShortcut>
</ContextMenuItem>
</ContextMenuGroup>
<ContextMenuSeparator />
<ContextMenuGroup>
<ContextMenuLabel>Actions</ContextMenuLabel>
<ContextMenuItem>
<FolderIcon />
Move to folder
</ContextMenuItem>
</ContextMenuGroup>
</ContextMenuContent>
</ContextMenu>
),
parameters: {
// Auto-generated by sync-storybook-zephyr - do not add manually
zephyr: { testCaseId: "" },
},
play: async ({ canvasElement, step }) => {
const canvas = within(canvasElement)

await step("Trigger renders", async () => {
expect(canvas.getByText("Right click this area")).toBeInTheDocument()
})

await step("Open menu and verify grouped items", async () => {
const trigger = canvas.getByText("Right click this area")
trigger.dispatchEvent(
new MouseEvent("contextmenu", { bubbles: true, cancelable: true, clientX: 8, clientY: 8 }),
)
const body = within(canvasElement.ownerDocument.body)
expect(await body.findByText("File")).toBeInTheDocument()
expect(body.getByText("Actions")).toBeInTheDocument()
expect(body.getByText("Rename")).toBeInTheDocument()
expect(body.getByText("Move to folder")).toBeInTheDocument()
})
},
}

export const WithSubMenu: Story = {
render: () => (
<ContextMenu>
<ContextMenuTrigger className="flex h-40 w-[320px] items-center justify-center rounded-xl border border-dashed text-sm text-muted-foreground">
Right click this area
</ContextMenuTrigger>
<ContextMenuContent className="w-52">
<ContextMenuItem>
<PencilIcon />
Rename
</ContextMenuItem>
<ContextMenuSub>
<ContextMenuSubTrigger>
<FolderIcon />
Move to
</ContextMenuSubTrigger>
<ContextMenuSubContent>
<ContextMenuItem>Projects</ContextMenuItem>
<ContextMenuItem>Archive</ContextMenuItem>
<ContextMenuItem>Trash</ContextMenuItem>
</ContextMenuSubContent>
</ContextMenuSub>
<ContextMenuSeparator />
<ContextMenuItem variant="destructive">
<Trash2Icon />
Delete
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
),
parameters: {
// Auto-generated by sync-storybook-zephyr - do not add manually
zephyr: { testCaseId: "" },
},
play: async ({ canvasElement, step }) => {
const canvas = within(canvasElement)

await step("Trigger renders", async () => {
expect(canvas.getByText("Right click this area")).toBeInTheDocument()
})

await step("Open menu and verify sub-trigger", async () => {
const trigger = canvas.getByText("Right click this area")
trigger.dispatchEvent(
new MouseEvent("contextmenu", { bubbles: true, cancelable: true, clientX: 8, clientY: 8 }),
)
const body = within(canvasElement.ownerDocument.body)
expect(await body.findByText("Move to")).toBeInTheDocument()
expect(
body.getByText("Move to").closest("[data-slot='context-menu-sub-trigger']"),
).toBeInTheDocument()
})

await step("Hover sub-trigger opens sub-content", async () => {
const body = within(canvasElement.ownerDocument.body)
const subTrigger = body.getByText("Move to")
await userEvent.hover(subTrigger)
await waitFor(() => {
expect(body.getByText("Projects")).toBeInTheDocument()
})
expect(body.getByText("Archive")).toBeInTheDocument()
})
},
}

export const WithCheckboxItems: Story = {
render: () => {
const [showGrid, setShowGrid] = React.useState(true)
const [showRulers, setShowRulers] = React.useState(false)

return (
<ContextMenu>
<ContextMenuTrigger className="flex h-40 w-[320px] items-center justify-center rounded-xl border border-dashed text-sm text-muted-foreground">
Right click this area
</ContextMenuTrigger>
<ContextMenuContent className="w-52">
<ContextMenuLabel>View options</ContextMenuLabel>
<ContextMenuSeparator />
<ContextMenuCheckboxItem checked={showGrid} onCheckedChange={setShowGrid}>
Show grid
</ContextMenuCheckboxItem>
<ContextMenuCheckboxItem checked={showRulers} onCheckedChange={setShowRulers}>
Show rulers
</ContextMenuCheckboxItem>
</ContextMenuContent>
</ContextMenu>
)
},
parameters: {
// Auto-generated by sync-storybook-zephyr - do not add manually
zephyr: { testCaseId: "" },
},
play: async ({ canvasElement, step }) => {
const canvas = within(canvasElement)

await step("Trigger renders", async () => {
expect(canvas.getByText("Right click this area")).toBeInTheDocument()
})

await step("Open menu and verify checkbox items", async () => {
const trigger = canvas.getByText("Right click this area")
trigger.dispatchEvent(
new MouseEvent("contextmenu", { bubbles: true, cancelable: true, clientX: 8, clientY: 8 }),
)
const body = within(canvasElement.ownerDocument.body)
expect(await body.findByText("Show grid")).toBeInTheDocument()
expect(body.getByText("Show rulers")).toBeInTheDocument()
})

await step("Checked item has data-state checked", async () => {
const body = within(canvasElement.ownerDocument.body)
const gridItem = body.getByText("Show grid").closest("[data-slot='context-menu-checkbox-item']")!
expect(gridItem).toHaveAttribute("data-state", "checked")
const rulersItem = body.getByText("Show rulers").closest("[data-slot='context-menu-checkbox-item']")!
expect(rulersItem).toHaveAttribute("data-state", "unchecked")
})
},
}

export const WithRadioItems: Story = {
render: () => {
const [zoom, setZoom] = React.useState("100")

return (
<ContextMenu>
<ContextMenuTrigger className="flex h-40 w-[320px] items-center justify-center rounded-xl border border-dashed text-sm text-muted-foreground">
Right click this area
</ContextMenuTrigger>
<ContextMenuContent className="w-52">
<ContextMenuLabel>Zoom level</ContextMenuLabel>
<ContextMenuSeparator />
<ContextMenuRadioGroup value={zoom} onValueChange={setZoom}>
<ContextMenuRadioItem value="50">50%</ContextMenuRadioItem>
<ContextMenuRadioItem value="100">100%</ContextMenuRadioItem>
<ContextMenuRadioItem value="150">150%</ContextMenuRadioItem>
</ContextMenuRadioGroup>
</ContextMenuContent>
</ContextMenu>
)
},
parameters: {
// Auto-generated by sync-storybook-zephyr - do not add manually
zephyr: { testCaseId: "" },
},
play: async ({ canvasElement, step }) => {
const canvas = within(canvasElement)

await step("Trigger renders", async () => {
expect(canvas.getByText("Right click this area")).toBeInTheDocument()
})

await step("Open menu and verify radio items", async () => {
const trigger = canvas.getByText("Right click this area")
trigger.dispatchEvent(
new MouseEvent("contextmenu", { bubbles: true, cancelable: true, clientX: 8, clientY: 8 }),
)
const body = within(canvasElement.ownerDocument.body)
expect(await body.findByText("100%")).toBeInTheDocument()
expect(body.getByText("50%")).toBeInTheDocument()
expect(body.getByText("150%")).toBeInTheDocument()
})

await step("Selected radio item has data-state checked", async () => {
const body = within(canvasElement.ownerDocument.body)
const hundredItem = body.getByText("100%").closest("[data-slot='context-menu-radio-item']")!
expect(hundredItem).toHaveAttribute("data-state", "checked")
const fiftyItem = body.getByText("50%").closest("[data-slot='context-menu-radio-item']")!
expect(fiftyItem).toHaveAttribute("data-state", "unchecked")
})
},
}
Loading
Loading