mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
Add shadcn/ui dropdown menu component
This commit is contained in:
parent
6da05598bf
commit
a7101a9b94
8 changed files with 1210 additions and 1290 deletions
|
|
@ -2,12 +2,7 @@ import type { StorybookConfig } from "@storybook/react-vite"
|
|||
|
||||
const config: StorybookConfig = {
|
||||
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
|
||||
addons: [
|
||||
"@storybook/addon-onboarding",
|
||||
"@storybook/addon-essentials",
|
||||
"@chromatic-com/storybook",
|
||||
"@storybook/addon-interactions",
|
||||
],
|
||||
addons: ["@storybook/addon-essentials", "@storybook/addon-interactions"],
|
||||
framework: {
|
||||
name: "@storybook/react-vite",
|
||||
options: {},
|
||||
|
|
|
|||
2041
webview-ui/package-lock.json
generated
2041
webview-ui/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -13,6 +13,8 @@
|
|||
"build-storybook": "storybook build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.5",
|
||||
"@radix-ui/react-icons": "^1.3.2",
|
||||
"@radix-ui/react-slot": "^1.1.1",
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"@vscode/webview-ui-toolkit": "^1.4.0",
|
||||
|
|
@ -36,14 +38,12 @@
|
|||
"vscrui": "^0.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chromatic-com/storybook": "^3.2.4",
|
||||
"@storybook/addon-essentials": "^8.5.2",
|
||||
"@storybook/addon-interactions": "^8.5.2",
|
||||
"@storybook/addon-onboarding": "^8.5.2",
|
||||
"@storybook/addon-essentials": "^8.5.3",
|
||||
"@storybook/addon-interactions": "^8.5.3",
|
||||
"@storybook/blocks": "^8.5.2",
|
||||
"@storybook/react": "^8.5.2",
|
||||
"@storybook/react-vite": "^8.5.2",
|
||||
"@storybook/test": "^8.5.2",
|
||||
"@storybook/react": "^8.5.3",
|
||||
"@storybook/react-vite": "^8.5.3",
|
||||
"@storybook/test": "^8.5.3",
|
||||
"@testing-library/jest-dom": "^5.17.0",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
|
|
@ -65,7 +65,7 @@
|
|||
"jest": "^27.5.1",
|
||||
"jest-environment-jsdom": "^27.5.1",
|
||||
"jest-simple-dot-reporter": "^1.0.5",
|
||||
"storybook": "^8.5.2",
|
||||
"storybook": "^8.5.3",
|
||||
"ts-jest": "^27.1.5",
|
||||
"typescript": "^4.9.5",
|
||||
"vite": "6.0.11"
|
||||
|
|
|
|||
177
webview-ui/src/components/ui/dropdown-menu.tsx
Normal file
177
webview-ui/src/components/ui/dropdown-menu.tsx
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
import * as React from "react"
|
||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
||||
import { CheckIcon, ChevronRightIcon, DotFilledIcon } from "@radix-ui/react-icons"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const DropdownMenu = DropdownMenuPrimitive.Root
|
||||
|
||||
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
|
||||
|
||||
const DropdownMenuGroup = DropdownMenuPrimitive.Group
|
||||
|
||||
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
|
||||
|
||||
const DropdownMenuSub = DropdownMenuPrimitive.Sub
|
||||
|
||||
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
|
||||
|
||||
const DropdownMenuSubTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
|
||||
inset?: boolean
|
||||
}
|
||||
>(({ className, inset, children, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.SubTrigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||
inset && "pl-8",
|
||||
className,
|
||||
)}
|
||||
{...props}>
|
||||
{children}
|
||||
<ChevronRightIcon className="ml-auto" />
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
))
|
||||
DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName
|
||||
|
||||
const DropdownMenuSubContent = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.SubContent
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-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",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName
|
||||
|
||||
const DropdownMenuContent = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
||||
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Portal>
|
||||
<DropdownMenuPrimitive.Content
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-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",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
))
|
||||
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
|
||||
|
||||
const DropdownMenuItem = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
||||
inset?: boolean
|
||||
}
|
||||
>(({ className, inset, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",
|
||||
inset && "pl-8",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
|
||||
|
||||
const DropdownMenuCheckboxItem = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
|
||||
>(({ className, children, checked, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.CheckboxItem
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className,
|
||||
)}
|
||||
checked={checked}
|
||||
{...props}>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<CheckIcon className="h-4 w-4" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</DropdownMenuPrimitive.CheckboxItem>
|
||||
))
|
||||
DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName
|
||||
|
||||
const DropdownMenuRadioItem = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.RadioItem
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className,
|
||||
)}
|
||||
{...props}>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<DotFilledIcon className="h-2 w-2 fill-current" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</DropdownMenuPrimitive.RadioItem>
|
||||
))
|
||||
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
|
||||
|
||||
const DropdownMenuLabel = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
|
||||
inset?: boolean
|
||||
}
|
||||
>(({ className, inset, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
|
||||
|
||||
const DropdownMenuSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Separator ref={ref} className={cn("-mx-1 my-1 h-px bg-muted", className)} {...props} />
|
||||
))
|
||||
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
|
||||
|
||||
const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||
return <span className={cn("ml-auto text-xs tracking-widest opacity-60", className)} {...props} />
|
||||
}
|
||||
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
|
||||
|
||||
export {
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuPortal,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuRadioGroup,
|
||||
}
|
||||
2
webview-ui/src/components/ui/index.ts
Normal file
2
webview-ui/src/components/ui/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./button"
|
||||
export * from "./dropdown-menu"
|
||||
|
|
@ -1,15 +1,47 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react"
|
||||
import { fn } from "@storybook/test"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Button } from "@/components/ui"
|
||||
|
||||
const meta = {
|
||||
title: "Example/Button",
|
||||
title: "@shadcn/Button",
|
||||
component: Button,
|
||||
parameters: { layout: "centered" },
|
||||
tags: ["autodocs"],
|
||||
argTypes: {},
|
||||
args: { onClick: fn(), children: "Button" },
|
||||
argTypes: {
|
||||
variant: {
|
||||
control: "select",
|
||||
options: ["default", "secondary", "outline", "ghost", "link", "destructive"],
|
||||
type: "string",
|
||||
table: {
|
||||
defaultValue: {
|
||||
summary: "default",
|
||||
},
|
||||
},
|
||||
},
|
||||
size: {
|
||||
control: "select",
|
||||
options: ["default", "sm", "lg", "icon"],
|
||||
type: "string",
|
||||
table: {
|
||||
defaultValue: {
|
||||
summary: "default",
|
||||
},
|
||||
},
|
||||
},
|
||||
children: {
|
||||
table: {
|
||||
disable: true,
|
||||
},
|
||||
},
|
||||
asChild: {
|
||||
table: {
|
||||
disable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
args: {
|
||||
children: "Button",
|
||||
},
|
||||
} satisfies Meta<typeof Button>
|
||||
|
||||
export default meta
|
||||
|
|
@ -17,37 +49,5 @@ export default meta
|
|||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
variant: "default",
|
||||
},
|
||||
}
|
||||
|
||||
export const Secondary: Story = {
|
||||
args: {
|
||||
variant: "secondary",
|
||||
},
|
||||
}
|
||||
|
||||
export const Outline: Story = {
|
||||
args: {
|
||||
variant: "outline",
|
||||
},
|
||||
}
|
||||
|
||||
export const Ghost: Story = {
|
||||
args: {
|
||||
variant: "ghost",
|
||||
},
|
||||
}
|
||||
|
||||
export const Link: Story = {
|
||||
args: {
|
||||
variant: "link",
|
||||
},
|
||||
}
|
||||
|
||||
export const Destructive: Story = {
|
||||
args: {
|
||||
variant: "destructive",
|
||||
},
|
||||
name: "Button",
|
||||
}
|
||||
|
|
|
|||
134
webview-ui/src/stories/DropdownMenu.stories.tsx
Normal file
134
webview-ui/src/stories/DropdownMenu.stories.tsx
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react"
|
||||
import {
|
||||
HamburgerMenuIcon,
|
||||
BorderLeftIcon,
|
||||
BorderRightIcon,
|
||||
BorderBottomIcon,
|
||||
BorderTopIcon,
|
||||
TextAlignLeftIcon,
|
||||
TextAlignCenterIcon,
|
||||
TextAlignRightIcon,
|
||||
} from "@radix-ui/react-icons"
|
||||
|
||||
import {
|
||||
Button,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuPortal,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui"
|
||||
|
||||
const meta = {
|
||||
title: "@shadcn/DropdownMenu",
|
||||
component: DropdownMenu,
|
||||
parameters: { layout: "centered" },
|
||||
tags: ["autodocs"],
|
||||
} satisfies Meta<typeof DropdownMenu>
|
||||
|
||||
export default meta
|
||||
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
name: "DropdownMenu",
|
||||
render: () => (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon">
|
||||
<HamburgerMenuIcon />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuLabel>Label</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>Item 1</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
Item 2<DropdownMenuShortcut>⌘2</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>Submenu</DropdownMenuSubTrigger>
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuSubContent>
|
||||
<DropdownMenuItem>Foo</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
Bar
|
||||
<DropdownMenuShortcut>⌘B</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>Baz</DropdownMenuItem>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuPortal>
|
||||
</DropdownMenuSub>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
),
|
||||
}
|
||||
|
||||
type DropdownMenuVariantProps = {
|
||||
side?: "top" | "bottom" | "left" | "right"
|
||||
align?: "start" | "center" | "end"
|
||||
children?: React.ReactNode
|
||||
}
|
||||
|
||||
const DropdownMenuVariant = ({ side = "bottom", align = "center", children }: DropdownMenuVariantProps) => (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon">
|
||||
{children}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent side={side} align={align}>
|
||||
<DropdownMenuItem>Foo</DropdownMenuItem>
|
||||
<DropdownMenuItem>Bar</DropdownMenuItem>
|
||||
<DropdownMenuItem>Baz</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
|
||||
export const Placements: Story = {
|
||||
render: () => (
|
||||
<div className="flex gap-2">
|
||||
<DropdownMenuVariant side="top">
|
||||
<BorderTopIcon />
|
||||
</DropdownMenuVariant>
|
||||
<DropdownMenuVariant side="bottom">
|
||||
<BorderBottomIcon />
|
||||
</DropdownMenuVariant>
|
||||
<DropdownMenuVariant side="left">
|
||||
<BorderLeftIcon />
|
||||
</DropdownMenuVariant>
|
||||
<DropdownMenuVariant side="right">
|
||||
<BorderRightIcon />
|
||||
</DropdownMenuVariant>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
|
||||
export const Alignments: Story = {
|
||||
render: () => (
|
||||
<div className="flex gap-2">
|
||||
<DropdownMenuVariant align="center">
|
||||
<TextAlignCenterIcon />
|
||||
</DropdownMenuVariant>
|
||||
<DropdownMenuVariant align="end">
|
||||
<TextAlignRightIcon />
|
||||
</DropdownMenuVariant>
|
||||
<DropdownMenuVariant align="start">
|
||||
<TextAlignLeftIcon />
|
||||
</DropdownMenuVariant>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
47
webview-ui/src/stories/vscrui/Dropdown.stories.tsx
Normal file
47
webview-ui/src/stories/vscrui/Dropdown.stories.tsx
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react"
|
||||
|
||||
import { Dropdown } from "vscrui"
|
||||
|
||||
const meta = {
|
||||
title: "@vscrui/Dropdown",
|
||||
component: () => (
|
||||
<Dropdown
|
||||
value="foo"
|
||||
role="combobox"
|
||||
options={[
|
||||
{ value: "foo", label: "Foo" },
|
||||
{ value: "bar", label: "Bar" },
|
||||
{ value: "baz", label: "Baz" },
|
||||
]}
|
||||
/>
|
||||
),
|
||||
parameters: { layout: "centered" },
|
||||
tags: ["autodocs"],
|
||||
argTypes: {},
|
||||
args: {},
|
||||
} satisfies Meta<typeof Dropdown>
|
||||
|
||||
export default meta
|
||||
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
args: {},
|
||||
parameters: {
|
||||
docs: {
|
||||
source: {
|
||||
code: `
|
||||
<Dropdown
|
||||
value="foo"
|
||||
role="combobox"
|
||||
options={[
|
||||
{ value: "foo", label: "Foo" },
|
||||
{ value: "bar", label: "Bar" },
|
||||
{ value: "baz", label: "Baz" }
|
||||
]}
|
||||
/>`,
|
||||
language: "tsx",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue