mirror of
https://github.com/langgenius/dify.git
synced 2026-05-05 09:06:56 +08:00
fix(web): align dropdown-menu styles with Figma design (#32922)
This commit is contained in:
parent
6002fd09b4
commit
477bf6e075
317
web/app/components/base/ui/dropdown-menu/index.stories.tsx
Normal file
317
web/app/components/base/ui/dropdown-menu/index.stories.tsx
Normal file
@ -0,0 +1,317 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuCheckboxItem,
|
||||||
|
DropdownMenuCheckboxItemIndicator,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuGroup,
|
||||||
|
DropdownMenuGroupLabel,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuRadioGroup,
|
||||||
|
DropdownMenuRadioItem,
|
||||||
|
DropdownMenuRadioItemIndicator,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuSub,
|
||||||
|
DropdownMenuSubContent,
|
||||||
|
DropdownMenuSubTrigger,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from '.'
|
||||||
|
|
||||||
|
const TriggerButton = ({ label = 'Open Menu' }: { label?: string }) => (
|
||||||
|
<DropdownMenuTrigger
|
||||||
|
render={<button type="button" className="rounded-lg border border-divider-subtle bg-components-button-secondary-bg px-3 py-1.5 text-sm text-text-secondary shadow-xs hover:bg-state-base-hover" />}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
)
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Base/Navigation/DropdownMenu',
|
||||||
|
component: DropdownMenu,
|
||||||
|
parameters: {
|
||||||
|
layout: 'centered',
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component: 'Compound dropdown menu built on Base UI Menu. Supports items, separators, group labels, submenus, radio groups, checkbox items, destructive items, and disabled states.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tags: ['autodocs'],
|
||||||
|
} satisfies Meta<typeof DropdownMenu>
|
||||||
|
|
||||||
|
export default meta
|
||||||
|
type Story = StoryObj<typeof meta>
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
render: () => (
|
||||||
|
<DropdownMenu>
|
||||||
|
<TriggerButton />
|
||||||
|
<DropdownMenuContent>
|
||||||
|
<DropdownMenuItem>Edit</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>Duplicate</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>Archive</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithSeparator: Story = {
|
||||||
|
render: () => (
|
||||||
|
<DropdownMenu>
|
||||||
|
<TriggerButton />
|
||||||
|
<DropdownMenuContent>
|
||||||
|
<DropdownMenuItem>Cut</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>Copy</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>Paste</DropdownMenuItem>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem>Select All</DropdownMenuItem>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem>Find and Replace</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithGroupLabel: Story = {
|
||||||
|
render: () => (
|
||||||
|
<DropdownMenu>
|
||||||
|
<TriggerButton />
|
||||||
|
<DropdownMenuContent>
|
||||||
|
<DropdownMenuGroup>
|
||||||
|
<DropdownMenuGroupLabel>Actions</DropdownMenuGroupLabel>
|
||||||
|
<DropdownMenuItem>Edit</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>Duplicate</DropdownMenuItem>
|
||||||
|
</DropdownMenuGroup>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuGroup>
|
||||||
|
<DropdownMenuGroupLabel>Export</DropdownMenuGroupLabel>
|
||||||
|
<DropdownMenuItem>Export as PDF</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>Export as CSV</DropdownMenuItem>
|
||||||
|
</DropdownMenuGroup>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithDestructiveItem: Story = {
|
||||||
|
render: () => (
|
||||||
|
<DropdownMenu>
|
||||||
|
<TriggerButton />
|
||||||
|
<DropdownMenuContent>
|
||||||
|
<DropdownMenuItem>Edit</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>Duplicate</DropdownMenuItem>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem destructive>Delete</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithSubmenu: Story = {
|
||||||
|
render: () => (
|
||||||
|
<DropdownMenu>
|
||||||
|
<TriggerButton />
|
||||||
|
<DropdownMenuContent>
|
||||||
|
<DropdownMenuItem>New File</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>Open</DropdownMenuItem>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuSub>
|
||||||
|
<DropdownMenuSubTrigger>Share</DropdownMenuSubTrigger>
|
||||||
|
<DropdownMenuSubContent>
|
||||||
|
<DropdownMenuItem>Email</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>Slack</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>Copy Link</DropdownMenuItem>
|
||||||
|
</DropdownMenuSubContent>
|
||||||
|
</DropdownMenuSub>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem>Download</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
const WithRadioItemsDemo = () => {
|
||||||
|
const [value, setValue] = useState('comfortable')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu>
|
||||||
|
<TriggerButton label={`Density: ${value}`} />
|
||||||
|
<DropdownMenuContent>
|
||||||
|
<DropdownMenuRadioGroup value={value} onValueChange={setValue}>
|
||||||
|
<DropdownMenuRadioItem value="compact">
|
||||||
|
Compact
|
||||||
|
<DropdownMenuRadioItemIndicator />
|
||||||
|
</DropdownMenuRadioItem>
|
||||||
|
<DropdownMenuRadioItem value="comfortable">
|
||||||
|
Comfortable
|
||||||
|
<DropdownMenuRadioItemIndicator />
|
||||||
|
</DropdownMenuRadioItem>
|
||||||
|
<DropdownMenuRadioItem value="spacious">
|
||||||
|
Spacious
|
||||||
|
<DropdownMenuRadioItemIndicator />
|
||||||
|
</DropdownMenuRadioItem>
|
||||||
|
</DropdownMenuRadioGroup>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithRadioItems: Story = {
|
||||||
|
render: () => <WithRadioItemsDemo />,
|
||||||
|
}
|
||||||
|
|
||||||
|
const WithCheckboxItemsDemo = () => {
|
||||||
|
const [showToolbar, setShowToolbar] = useState(true)
|
||||||
|
const [showSidebar, setShowSidebar] = useState(false)
|
||||||
|
const [showStatusBar, setShowStatusBar] = useState(true)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu>
|
||||||
|
<TriggerButton label="View Options" />
|
||||||
|
<DropdownMenuContent>
|
||||||
|
<DropdownMenuCheckboxItem checked={showToolbar} onCheckedChange={setShowToolbar}>
|
||||||
|
Toolbar
|
||||||
|
<DropdownMenuCheckboxItemIndicator />
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
|
<DropdownMenuCheckboxItem checked={showSidebar} onCheckedChange={setShowSidebar}>
|
||||||
|
Sidebar
|
||||||
|
<DropdownMenuCheckboxItemIndicator />
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
|
<DropdownMenuCheckboxItem checked={showStatusBar} onCheckedChange={setShowStatusBar}>
|
||||||
|
Status Bar
|
||||||
|
<DropdownMenuCheckboxItemIndicator />
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithCheckboxItems: Story = {
|
||||||
|
render: () => <WithCheckboxItemsDemo />,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithDisabledItems: Story = {
|
||||||
|
render: () => (
|
||||||
|
<DropdownMenu>
|
||||||
|
<TriggerButton />
|
||||||
|
<DropdownMenuContent>
|
||||||
|
<DropdownMenuItem>Edit</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem disabled>Duplicate</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>Archive</DropdownMenuItem>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem disabled>Restore</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem destructive>Delete</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithIcons: Story = {
|
||||||
|
render: () => (
|
||||||
|
<DropdownMenu>
|
||||||
|
<TriggerButton />
|
||||||
|
<DropdownMenuContent>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<span aria-hidden className="i-ri-pencil-line size-4 shrink-0 text-text-tertiary" />
|
||||||
|
Edit
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<span aria-hidden className="i-ri-file-copy-line size-4 shrink-0 text-text-tertiary" />
|
||||||
|
Duplicate
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<span aria-hidden className="i-ri-archive-line size-4 shrink-0 text-text-tertiary" />
|
||||||
|
Archive
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem destructive>
|
||||||
|
<span aria-hidden className="i-ri-delete-bin-line size-4 shrink-0" />
|
||||||
|
Delete
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
const ComplexDemo = () => {
|
||||||
|
const [sortOrder, setSortOrder] = useState('newest')
|
||||||
|
const [showArchived, setShowArchived] = useState(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu>
|
||||||
|
<TriggerButton label="Actions" />
|
||||||
|
<DropdownMenuContent>
|
||||||
|
<DropdownMenuGroup>
|
||||||
|
<DropdownMenuGroupLabel>Edit</DropdownMenuGroupLabel>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<span aria-hidden className="i-ri-pencil-line size-4 shrink-0 text-text-tertiary" />
|
||||||
|
Rename
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<span aria-hidden className="i-ri-file-copy-line size-4 shrink-0 text-text-tertiary" />
|
||||||
|
Duplicate
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem disabled>
|
||||||
|
<span aria-hidden className="i-ri-lock-line size-4 shrink-0 text-text-tertiary" />
|
||||||
|
Move to Workspace
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuGroup>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuSub>
|
||||||
|
<DropdownMenuSubTrigger>
|
||||||
|
<span aria-hidden className="i-ri-share-line size-4 shrink-0 text-text-tertiary" />
|
||||||
|
Share
|
||||||
|
</DropdownMenuSubTrigger>
|
||||||
|
<DropdownMenuSubContent>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<span aria-hidden className="i-ri-mail-line size-4 shrink-0 text-text-tertiary" />
|
||||||
|
Email
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<span aria-hidden className="i-ri-chat-1-line size-4 shrink-0 text-text-tertiary" />
|
||||||
|
Slack
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<span aria-hidden className="i-ri-link size-4 shrink-0 text-text-tertiary" />
|
||||||
|
Copy Link
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuSubContent>
|
||||||
|
</DropdownMenuSub>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuGroup>
|
||||||
|
<DropdownMenuGroupLabel>Sort by</DropdownMenuGroupLabel>
|
||||||
|
<DropdownMenuRadioGroup value={sortOrder} onValueChange={setSortOrder}>
|
||||||
|
<DropdownMenuRadioItem value="newest">
|
||||||
|
Newest first
|
||||||
|
<DropdownMenuRadioItemIndicator />
|
||||||
|
</DropdownMenuRadioItem>
|
||||||
|
<DropdownMenuRadioItem value="oldest">
|
||||||
|
Oldest first
|
||||||
|
<DropdownMenuRadioItemIndicator />
|
||||||
|
</DropdownMenuRadioItem>
|
||||||
|
<DropdownMenuRadioItem value="name">
|
||||||
|
Name
|
||||||
|
<DropdownMenuRadioItemIndicator />
|
||||||
|
</DropdownMenuRadioItem>
|
||||||
|
</DropdownMenuRadioGroup>
|
||||||
|
</DropdownMenuGroup>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuCheckboxItem checked={showArchived} onCheckedChange={setShowArchived}>
|
||||||
|
<span aria-hidden className="i-ri-archive-line size-4 shrink-0 text-text-tertiary" />
|
||||||
|
Show Archived
|
||||||
|
<DropdownMenuCheckboxItemIndicator />
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem destructive>
|
||||||
|
<span aria-hidden className="i-ri-delete-bin-line size-4 shrink-0" />
|
||||||
|
Delete
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Complex: Story = {
|
||||||
|
render: () => <ComplexDemo />,
|
||||||
|
}
|
||||||
@ -13,8 +13,8 @@ export const DropdownMenuSub = Menu.SubmenuRoot
|
|||||||
export const DropdownMenuGroup = Menu.Group
|
export const DropdownMenuGroup = Menu.Group
|
||||||
export const DropdownMenuRadioGroup = Menu.RadioGroup
|
export const DropdownMenuRadioGroup = Menu.RadioGroup
|
||||||
|
|
||||||
const menuRowBaseClassName = 'mx-1 flex h-8 cursor-pointer select-none items-center rounded-lg px-2 outline-none'
|
const menuRowBaseClassName = 'mx-1 flex h-8 cursor-pointer select-none items-center gap-1 rounded-lg px-2 outline-none'
|
||||||
const menuRowStateClassName = 'data-[highlighted]:bg-state-base-hover data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50'
|
const menuRowStateClassName = 'data-[highlighted]:bg-state-base-hover data-[disabled]:cursor-not-allowed data-[disabled]:opacity-30'
|
||||||
|
|
||||||
export function DropdownMenuRadioItem({
|
export function DropdownMenuRadioItem({
|
||||||
className,
|
className,
|
||||||
@ -89,7 +89,7 @@ export function DropdownMenuGroupLabel({
|
|||||||
return (
|
return (
|
||||||
<Menu.GroupLabel
|
<Menu.GroupLabel
|
||||||
className={cn(
|
className={cn(
|
||||||
'px-3 py-1 text-text-tertiary system-2xs-medium-uppercase',
|
'px-3 pb-0.5 pt-1 text-text-tertiary system-xs-medium-uppercase',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@ -148,7 +148,7 @@ function renderDropdownMenuPopup({
|
|||||||
>
|
>
|
||||||
<Menu.Popup
|
<Menu.Popup
|
||||||
className={cn(
|
className={cn(
|
||||||
'max-h-[var(--available-height)] overflow-y-auto overflow-x-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg py-1 text-sm text-text-secondary shadow-lg',
|
'max-h-[var(--available-height)] overflow-y-auto overflow-x-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur py-1 text-sm text-text-secondary shadow-lg backdrop-blur-[5px]',
|
||||||
'origin-[var(--transform-origin)] transition-[transform,scale,opacity] data-[ending-style]:scale-95 data-[starting-style]:scale-95 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0 motion-reduce:transition-none',
|
'origin-[var(--transform-origin)] transition-[transform,scale,opacity] data-[ending-style]:scale-95 data-[starting-style]:scale-95 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0 motion-reduce:transition-none',
|
||||||
popupClassName,
|
popupClassName,
|
||||||
)}
|
)}
|
||||||
@ -204,7 +204,7 @@ export function DropdownMenuSubTrigger({
|
|||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
<span aria-hidden className="i-ri-arrow-right-s-line ml-auto size-[14px] shrink-0 text-text-tertiary" />
|
<span aria-hidden className="i-ri-arrow-right-s-line ml-auto size-4 shrink-0 text-text-tertiary" />
|
||||||
</Menu.SubmenuTrigger>
|
</Menu.SubmenuTrigger>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -270,7 +270,7 @@ export function DropdownMenuSeparator({
|
|||||||
}: React.ComponentPropsWithoutRef<typeof Menu.Separator>) {
|
}: React.ComponentPropsWithoutRef<typeof Menu.Separator>) {
|
||||||
return (
|
return (
|
||||||
<Menu.Separator
|
<Menu.Separator
|
||||||
className={cn('my-1 h-px bg-divider-regular', className)}
|
className={cn('my-1 h-px bg-divider-subtle', className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user