Skip to content
Lenso UI

Dropdown 下拉菜单

展示用户可选择的操作或选项列表

用法

import { Dropdown } from '@lenso/ui';

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";
// Adapted from HeroUI v3.2.6 dropdown-default (Apache-2.0).import { Button, Dropdown } from "@lenso/ui";import { ActionItem, Popup } from "./_shared";
export function Default() {  return (    <Dropdown>      <Dropdown.Trigger render={<Button aria-label="Menu" variant="secondary" />}>        Actions      </Dropdown.Trigger>      <Popup>        <ActionItem label="New file" onClick={() => console.log("Selected: new-file")} />        <ActionItem label="Copy link" onClick={() => console.log("Selected: copy-link")} />        <ActionItem label="Edit file" onClick={() => console.log("Selected: edit-file")} />        <ActionItem          label="Delete file"          variant="danger"          onClick={() => console.log("Selected: delete-file")}        />      </Popup>    </Dropdown>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

组件结构

import { Dropdown, Button, Label, Description, Header, Kbd, Separator } from '@lenso/ui';
export default () => (  <Dropdown>    <Dropdown.Trigger>      <Button />    </Dropdown.Trigger>    <Dropdown.Popover>      <Dropdown.Menu>        <Dropdown.Item>          <Label />          <Description />          <Kbd slot="keyboard" />          <Dropdown.ItemIndicator />        </Dropdown.Item>        <Separator />        <Dropdown.Section>          <Header />          <Dropdown.Item />        </Dropdown.Section>        <Dropdown.SubmenuTrigger>          <Dropdown.Item>            <Label />            <Dropdown.SubmenuIndicator />          </Dropdown.Item>          <Dropdown.Popover>            <Dropdown.Menu>              <Dropdown.Item />            </Dropdown.Menu>          </Dropdown.Popover>        </Dropdown.SubmenuTrigger>      </Dropdown.Menu>    </Dropdown.Popover>  </Dropdown>)

示例

带图标

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";
// Adapted from HeroUI v3.2.6 with-icons, Apache-2.0.import { FloppyDisk, FolderOpen, SquarePlus, TrashBin } from "@gravity-ui/icons";import * as stylex from "@stylexjs/stylex";import { Button, Dropdown } from "@lenso/ui";import { ActionItem, Popup, Shortcut, styles } from "./_shared";
export function WithIcons() {  return (    <Dropdown>      <Dropdown.Trigger render={<Button aria-label="Menu" variant="secondary" />}>        Actions      </Dropdown.Trigger>      <Popup>        <ActionItem          label="New file"          icon={<SquarePlus {...stylex.props(styles.icon)} />}          shortcut={<Shortcut letter="N" />}          onClick={() => console.log("Selected: new-file")}        />        <ActionItem          label="Open file"          icon={<FolderOpen {...stylex.props(styles.icon)} />}          shortcut={<Shortcut letter="O" />}          onClick={() => console.log("Selected: open-file")}        />        <ActionItem          label="Save file"          icon={<FloppyDisk {...stylex.props(styles.icon)} />}          shortcut={<Shortcut letter="S" />}          onClick={() => console.log("Selected: save-file")}        />        <ActionItem          label="Delete file"          variant="danger"          icon={<TrashBin {...stylex.props(styles.dangerIcon)} />}          shortcut={<Shortcut letter="D" shift />}          onClick={() => console.log("Selected: delete-file")}        />      </Popup>    </Dropdown>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

带描述

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";
// Adapted from HeroUI v3.2.6 with-descriptions, Apache-2.0.import { FloppyDisk, FolderOpen, SquarePlus, TrashBin } from "@gravity-ui/icons";import * as stylex from "@stylexjs/stylex";import { Button, Dropdown } from "@lenso/ui";import { ActionItem, Popup, Shortcut, styles } from "./_shared";
export function WithDescriptions() {  return (    <Dropdown>      <Dropdown.Trigger render={<Button aria-label="Menu" variant="secondary" />}>        Actions      </Dropdown.Trigger>      <Popup>        <ActionItem          label="New file"          description="Create a new file"          icon={<SquarePlus {...stylex.props(styles.icon)} />}          shortcut={<Shortcut letter="N" />}          onClick={() => console.log("Selected: new-file")}        />        <ActionItem          label="Open file"          description="Open an existing file"          icon={<FolderOpen {...stylex.props(styles.icon)} />}          shortcut={<Shortcut letter="O" />}          onClick={() => console.log("Selected: open-file")}        />        <ActionItem          label="Save file"          description="Save the current file"          icon={<FloppyDisk {...stylex.props(styles.icon)} />}          shortcut={<Shortcut letter="S" />}          onClick={() => console.log("Selected: save-file")}        />        <ActionItem          label="Delete file"          description="Move to trash"          variant="danger"          icon={<TrashBin {...stylex.props(styles.dangerIcon)} />}          shortcut={<Shortcut letter="D" shift />}          onClick={() => console.log("Selected: delete-file")}        />      </Popup>    </Dropdown>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

含禁用项

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";
// Adapted from HeroUI v3.2.6 with-disabled-items, Apache-2.0.import { Bars, Pencil, SquarePlus, TrashBin } from "@gravity-ui/icons";import * as stylex from "@stylexjs/stylex";import { Button, Dropdown } from "@lenso/ui";import { ActionItem, Popup, Shortcut, styles } from "./_shared";
export function WithDisabledItems() {  return (    <Dropdown>      <Dropdown.Trigger render={<Button isIconOnly aria-label="Menu" variant="secondary" />}>        <Bars />      </Dropdown.Trigger>      <Popup xstyle={styles.disabledWidth}>        <Dropdown.Section>          <Dropdown.Section.Label>Actions</Dropdown.Section.Label>          <ActionItem            label="New file"            description="Create a new file"            icon={<SquarePlus {...stylex.props(styles.icon)} />}            shortcut={<Shortcut letter="N" />}            onClick={() => console.log("Selected: new-file")}          />          <ActionItem            label="Edit file"            description="Make changes"            icon={<Pencil {...stylex.props(styles.icon)} />}            shortcut={<Shortcut letter="E" />}            onClick={() => console.log("Selected: edit-file")}          />        </Dropdown.Section>        <Dropdown.Separator />        <Dropdown.Section>          <Dropdown.Section.Label>Danger zone</Dropdown.Section.Label>          <ActionItem            disabled            label="Delete file"            description="Move to trash"            variant="danger"            icon={<TrashBin {...stylex.props(styles.dangerIcon)} />}            shortcut={<Shortcut letter="D" shift />}            onClick={() => console.log("Selected: delete-file")}          />        </Dropdown.Section>      </Popup>    </Dropdown>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

分组选项

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";
// Adapted from HeroUI v3.2.6 with-sections, Apache-2.0.import { EllipsisVertical, Pencil, SquarePlus, TrashBin } from "@gravity-ui/icons";import * as stylex from "@stylexjs/stylex";import { Button, Dropdown } from "@lenso/ui";import { ActionItem, Popup, Shortcut, styles } from "./_shared";
export function WithSections() {  return (    <Dropdown>      <Dropdown.Trigger render={<Button isIconOnly aria-label="Menu" variant="secondary" />}>        <EllipsisVertical />      </Dropdown.Trigger>      <Popup>        <Dropdown.Section>          <Dropdown.Section.Label>Actions</Dropdown.Section.Label>          <ActionItem            label="New file"            description="Create a new file"            icon={<SquarePlus {...stylex.props(styles.icon)} />}            shortcut={<Shortcut letter="N" />}            onClick={() => console.log("Selected: new-file")}          />          <ActionItem            label="Edit file"            description="Make changes"            icon={<Pencil {...stylex.props(styles.icon)} />}            shortcut={<Shortcut letter="E" />}            onClick={() => console.log("Selected: edit-file")}          />        </Dropdown.Section>        <Dropdown.Separator />        <Dropdown.Section>          <Dropdown.Section.Label>Danger zone</Dropdown.Section.Label>          <ActionItem            label="Delete file"            description="Move to trash"            variant="danger"            icon={<TrashBin {...stylex.props(styles.dangerIcon)} />}            shortcut={<Shortcut letter="D" shift />}            onClick={() => console.log("Selected: delete-file")}          />        </Dropdown.Section>      </Popup>    </Dropdown>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

多选

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";
// Adapted from HeroUI v3.2.6 with-multiple-selection, Apache-2.0.import { useState } from "react";import { Button, Dropdown, MenuItem } from "@lenso/ui";import { Checkmark, Popup, styles } from "./_shared";
export function WithMultipleSelection() {  const [selected, setSelected] = useState(new Set(["apple"]));  const [open, setOpen] = useState(false);  const fruit = (value: string, label: string) => (    <Dropdown.CheckboxItem      key={value}      checked={selected.has(value)}      closeOnClick={false}      onKeyDown={(event) => {        if (event.key === "Enter") setOpen(false);      }}      onCheckedChange={(checked) =>        setSelected((previous) => {          const next = new Set(previous);          if (checked) next.add(value);          else next.delete(value);          return next;        })      }    >      <Dropdown.CheckboxItemIndicator keepMounted xstyle={styles.selectionIndicator}>        <Checkmark />      </Dropdown.CheckboxItemIndicator>      <MenuItem.Label>{label}</MenuItem.Label>    </Dropdown.CheckboxItem>  );  return (    <Dropdown open={open} onOpenChange={setOpen}>      <Dropdown.Trigger render={<Button aria-label="Menu" variant="secondary" />}>        Preferred Fruits      </Dropdown.Trigger>      <Popup xstyle={styles.wide}>        <Dropdown.Section>          <Dropdown.Section.Label>Select a fruit</Dropdown.Section.Label>          {fruit("apple", "Apple")}          {fruit("banana", "Banana")}          {fruit("cherry", "Cherry")}        </Dropdown.Section>        {fruit("orange", "Orange")}        {fruit("pear", "Pear")}      </Popup>    </Dropdown>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

受控组件

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";
// Adapted from HeroUI v3.2.6 controlled, Apache-2.0.import { useState } from "react";import * as stylex from "@stylexjs/stylex";import { Button, Dropdown, MenuItem } from "@lenso/ui";import { Checkmark, Popup, styles } from "./_shared";
export function Controlled() {  const [selected, setSelected] = useState(new Set(["bold"]));  const [open, setOpen] = useState(false);  const selectedItems = Array.from(selected);  return (    <div {...stylex.props(styles.controlled)}>      <p {...stylex.props(styles.status)}>        Selected: {selectedItems.length > 0 ? selectedItems.join(", ") : "None"}      </p>      <Dropdown open={open} onOpenChange={setOpen}>        <Dropdown.Trigger render={<Button aria-label="Menu" variant="secondary" />}>          Actions        </Dropdown.Trigger>        <Popup>          {(["bold", "italic", "underline"] as const).map((value) => (            <Dropdown.CheckboxItem              key={value}              checked={selected.has(value)}              closeOnClick={false}              onKeyDown={(event) => {                if (event.key === "Enter") setOpen(false);              }}              onCheckedChange={(checked) =>                setSelected((previous) => {                  const next = new Set(previous);                  if (checked) next.add(value);                  else next.delete(value);                  return next;                })              }            >              <MenuItem.Label>{value.charAt(0).toUpperCase() + value.slice(1)}</MenuItem.Label>              <Dropdown.CheckboxItemIndicator keepMounted xstyle={styles.selectionIndicator}>                <Checkmark />              </Dropdown.CheckboxItemIndicator>            </Dropdown.CheckboxItem>          ))}        </Popup>      </Dropdown>    </div>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

受控展开状态

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";
// Adapted from HeroUI v3.2.6 controlled-open-state, Apache-2.0.import { useState } from "react";import * as stylex from "@stylexjs/stylex";import { Button, Dropdown } from "@lenso/ui";import { ActionItem, Popup, styles } from "./_shared";
export function ControlledOpenState() {  const [open, setOpen] = useState(false);  return (    <div {...stylex.props(styles.controlled)}>      <p {...stylex.props(styles.status)}>        Dropdown is: <strong>{open ? "open" : "closed"}</strong>      </p>      <Dropdown open={open} onOpenChange={setOpen}>        <Dropdown.Trigger render={<Button aria-label="Menu" variant="secondary" />}>          Actions        </Dropdown.Trigger>        <Popup>          <ActionItem label="New file" />          <ActionItem label="Open file" />          <ActionItem label="Save file" />          <ActionItem label="Delete file" variant="danger" />        </Popup>      </Dropdown>    </div>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

单选

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";
// Adapted from HeroUI v3.2.6 with-single-selection, Apache-2.0.import { useState } from "react";import { Button, Dropdown, MenuItem } from "@lenso/ui";import { Checkmark, Popup, styles } from "./_shared";
export function WithSingleSelection() {  const [selected, setSelected] = useState("apple");  const fruit = (value: string, label: string) => (    <Dropdown.RadioItem key={value} value={value} closeOnClick>      <Dropdown.RadioItemIndicator keepMounted xstyle={styles.selectionIndicator}>        <Checkmark />      </Dropdown.RadioItemIndicator>      <MenuItem.Label>{label}</MenuItem.Label>    </Dropdown.RadioItem>  );  return (    <Dropdown>      <Dropdown.Trigger render={<Button aria-label="Menu" variant="secondary" />}>        Fruit      </Dropdown.Trigger>      <Popup xstyle={styles.wide}>        <Dropdown.RadioGroup value={selected} onValueChange={setSelected}>          <Dropdown.Section>            <Dropdown.Section.Label>Select a fruit</Dropdown.Section.Label>            {fruit("apple", "Apple")}            {fruit("banana", "Banana")}            {fruit("cherry", "Cherry")}          </Dropdown.Section>          {fruit("orange", "Orange")}          {fruit("pear", "Pear")}        </Dropdown.RadioGroup>      </Popup>    </Dropdown>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

单选自定义指示器

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";
// Adapted from HeroUI v3.2.6 single-with-custom-indicator, Apache-2.0.import { useState } from "react";import * as stylex from "@stylexjs/stylex";import { Button, Dropdown, MenuItem } from "@lenso/ui";import { Popup, styles } from "./_shared";
export function SingleWithCustomIndicator() {  const [selected, setSelected] = useState("apple");  const customCheckmarkIcon = (    <svg      height="16"      viewBox="0 0 16 16"      width="16"      xmlns="http://www.w3.org/2000/svg"      aria-hidden="true"    >      <path        {...stylex.props(styles.accent)}        clipRule="evenodd"        d="M8 15A7 7 0 1 0 8 1a7 7 0 0 0 0 14m3.1-8.55a.75.75 0 1 0-1.2-.9L7.419 8.858L6.03 7.47a.75.75 0 0 0-1.06 1.06l2 2a.75.75 0 0 0 1.13-.08z"        fill="currentColor"        fillRule="evenodd"      />    </svg>  );  const fruit = (value: string, label: string) => (    <Dropdown.RadioItem key={value} value={value} closeOnClick>      <Dropdown.RadioItemIndicator keepMounted xstyle={styles.selectionIndicator}>        {customCheckmarkIcon}      </Dropdown.RadioItemIndicator>      <MenuItem.Label>{label}</MenuItem.Label>    </Dropdown.RadioItem>  );  return (    <Dropdown>      <Dropdown.Trigger render={<Button aria-label="Menu" variant="secondary" />}>        Fruits      </Dropdown.Trigger>      <Popup xstyle={styles.wide}>        <Dropdown.RadioGroup value={selected} onValueChange={setSelected}>          <Dropdown.Section>            <Dropdown.Section.Label>Select a fruit</Dropdown.Section.Label>            {fruit("apple", "Apple")}            {fruit("banana", "Banana")}            {fruit("cherry", "Cherry")}          </Dropdown.Section>          {fruit("orange", "Orange")}          {fruit("pear", "Pear")}        </Dropdown.RadioGroup>      </Popup>    </Dropdown>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

分组级选择

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";
// Adapted from HeroUI v3.2.6 with-section-level-selection, Apache-2.0.import { useState } from "react";import { Button, Dropdown, MenuItem } from "@lenso/ui";import { ActionItem, Checkmark, Dotmark, Popup, Shortcut, styles } from "./_shared";
export function WithSectionLevelSelection() {  const [textStyles, setTextStyles] = useState(new Set(["bold", "italic"]));  const [textAlignment, setTextAlignment] = useState("left");  const [open, setOpen] = useState(false);  return (    <Dropdown open={open} onOpenChange={setOpen}>      <Dropdown.Trigger render={<Button aria-label="Menu" variant="secondary" />}>        Styles      </Dropdown.Trigger>      <Popup xstyle={styles.wide}>        <Dropdown.Section>          <Dropdown.Section.Label>Actions</Dropdown.Section.Label>          <ActionItem label="Cut" shortcut={<Shortcut letter="X" />} />          <ActionItem label="Copy" shortcut={<Shortcut letter="C" />} />          <ActionItem label="Paste" shortcut={<Shortcut letter="U" />} />        </Dropdown.Section>        <Dropdown.Separator />        <Dropdown.Section>          <Dropdown.Section.Label>Text Style</Dropdown.Section.Label>          {(["bold", "italic", "underline"] as const).map((value) => (            <Dropdown.CheckboxItem              key={value}              checked={textStyles.has(value)}              aria-label={value.charAt(0).toUpperCase() + value.slice(1)}              closeOnClick={false}              onKeyDown={(event) => {                if (event.key === "Enter") setOpen(false);              }}              onCheckedChange={(checked) =>                setTextStyles((previous) => {                  const next = new Set(previous);                  if (checked) next.add(value);                  else next.delete(value);                  return next;                })              }            >              <Dropdown.CheckboxItemIndicator keepMounted xstyle={styles.selectionIndicator}>                <Checkmark />              </Dropdown.CheckboxItemIndicator>              <MenuItem.Label>{value.charAt(0).toUpperCase() + value.slice(1)}</MenuItem.Label>              <Shortcut letter={value === "underline" ? "U" : value.charAt(0).toUpperCase()} />            </Dropdown.CheckboxItem>          ))}        </Dropdown.Section>        <Dropdown.Separator />        <Dropdown.Section>          <Dropdown.Section.Label>Text Alignment</Dropdown.Section.Label>          <Dropdown.RadioGroup value={textAlignment} onValueChange={setTextAlignment}>            {(["left", "center", "right"] as const).map((value) => (              <Dropdown.RadioItem                key={value}                value={value}                aria-label={value.charAt(0).toUpperCase() + value.slice(1)}                closeOnClick              >                <Dropdown.RadioItemIndicator keepMounted xstyle={styles.selectionIndicator}>                  <Dotmark />                </Dropdown.RadioItemIndicator>                <MenuItem.Label>{value.charAt(0).toUpperCase() + value.slice(1)}</MenuItem.Label>                <Shortcut                  modifier="alt"                  letter={value === "left" ? "A" : value === "center" ? "H" : "D"}                />              </Dropdown.RadioItem>            ))}          </Dropdown.RadioGroup>        </Dropdown.Section>      </Popup>    </Dropdown>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

带快捷键

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";
// Adapted from HeroUI v3.2.6 with-keyboard-shortcuts, Apache-2.0.import { Button, Dropdown } from "@lenso/ui";import { ActionItem, Popup, Shortcut } from "./_shared";
export function WithKeyboardShortcuts() {  return (    <Dropdown>      <Dropdown.Trigger render={<Button aria-label="Menu" variant="secondary" />}>        Actions      </Dropdown.Trigger>      <Popup>        <ActionItem          label="New"          shortcut={<Shortcut letter="N" />}          onClick={() => console.log("Selected: new")}        />        <ActionItem          label="Open"          shortcut={<Shortcut letter="O" />}          onClick={() => console.log("Selected: open")}        />        <ActionItem          label="Save"          shortcut={<Shortcut letter="S" />}          onClick={() => console.log("Selected: save")}        />        <ActionItem          label="Delete"          variant="danger"          shortcut={<Shortcut letter="D" shift />}          onClick={() => console.log("Selected: delete")}        />      </Popup>    </Dropdown>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

含子菜单

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";
// Adapted from HeroUI v3.2.6 with-submenus, Apache-2.0.import * as stylex from "@stylexjs/stylex";import { Button, Dropdown, IconChevronRight, MenuItem } from "@lenso/ui";import { ActionItem, Popup, styles } from "./_shared";
export function WithSubmenus() {  return (    <Dropdown>      <Dropdown.Trigger render={<Button aria-label="Menu" variant="secondary" />}>        Share      </Dropdown.Trigger>      <Popup>        <ActionItem label="Copy Link" onClick={() => console.log("Selected: copy-link")} />        <ActionItem label="Facebook" onClick={() => console.log("Selected: facebook")} />        <ActionItem          label="X / Twitter"          navigationLabel="Twitter"          onClick={() => console.log("Selected: twitter")}        />        <Dropdown.SubmenuRoot>          <Dropdown.SubmenuTrigger label="Share">            <MenuItem.Label>Other</MenuItem.Label>            <MenuItem.SubmenuIndicator>              <IconChevronRight {...stylex.props(styles.smallIcon)} />            </MenuItem.SubmenuIndicator>          </Dropdown.SubmenuTrigger>          <Popup>            <ActionItem label="WhatsApp" />            <ActionItem label="Telegram" />            <ActionItem label="Discord" />            <Dropdown.SubmenuRoot>              <Dropdown.SubmenuTrigger>                <MenuItem.Label>Email</MenuItem.Label>                <MenuItem.SubmenuIndicator>                  <IconChevronRight {...stylex.props(styles.smallIcon)} />                </MenuItem.SubmenuIndicator>              </Dropdown.SubmenuTrigger>              <Popup>                <ActionItem label="Work email" />                <ActionItem label="Personal email" />              </Popup>            </Dropdown.SubmenuRoot>          </Popup>        </Dropdown.SubmenuRoot>      </Popup>    </Dropdown>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

自定义子菜单指示器

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";
// Adapted from HeroUI v3.2.6 with-custom-submenu-indicator, Apache-2.0.import { ArrowRight } from "@gravity-ui/icons";import * as stylex from "@stylexjs/stylex";import { Button, Dropdown, IconChevronRight, MenuItem } from "@lenso/ui";import { ActionItem, Popup, styles } from "./_shared";
export function WithCustomSubmenuIndicator() {  return (    <Dropdown>      <Dropdown.Trigger render={<Button aria-label="Menu" variant="secondary" />}>        Share      </Dropdown.Trigger>      <Popup>        <ActionItem label="Copy Link" onClick={() => console.log("Selected: copy-link")} />        <ActionItem label="Facebook" onClick={() => console.log("Selected: facebook")} />        <Dropdown.SubmenuRoot>          <Dropdown.SubmenuTrigger label="Share">            <MenuItem.Label>More options</MenuItem.Label>            <MenuItem.SubmenuIndicator>              <ArrowRight {...stylex.props(styles.smallIcon)} />            </MenuItem.SubmenuIndicator>          </Dropdown.SubmenuTrigger>          <Popup>            <ActionItem label="WhatsApp" />            <ActionItem label="Telegram" />            <Dropdown.SubmenuRoot>              <Dropdown.SubmenuTrigger>                <MenuItem.Label>Email</MenuItem.Label>                <MenuItem.SubmenuIndicator>                  <svg                    {...stylex.props(styles.smallIcon)}                    fill="none"                    stroke="currentColor"                    strokeLinecap="round"                    strokeLinejoin="round"                    strokeWidth="2"                    viewBox="0 0 24 24"                    aria-hidden="true"                  >                    <path d="M9 18l6-6-6-6" />                  </svg>                </MenuItem.SubmenuIndicator>              </Dropdown.SubmenuTrigger>              <Popup>                <ActionItem label="Work email" />                <ActionItem label="Personal email" />              </Popup>            </Dropdown.SubmenuRoot>            <ActionItem label="Discord" />          </Popup>        </Dropdown.SubmenuRoot>        <Dropdown.SubmenuRoot>          <Dropdown.SubmenuTrigger label="Other">            <MenuItem.Label>Other (default indicator)</MenuItem.Label>            <MenuItem.SubmenuIndicator>              <IconChevronRight {...stylex.props(styles.smallIcon)} />            </MenuItem.SubmenuIndicator>          </Dropdown.SubmenuTrigger>          <Popup>            <ActionItem label="SMS" />          </Popup>        </Dropdown.SubmenuRoot>      </Popup>    </Dropdown>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

自定义触发器

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";
// Adapted from HeroUI v3.2.6 custom-trigger, Apache-2.0; original avatar asset retained.import { ArrowRightFromSquare, Gear, Persons } from "@gravity-ui/icons";import * as stylex from "@stylexjs/stylex";import { Avatar, Dropdown, MenuItem } from "@lenso/ui";import { ActionItem, Popup, styles } from "./_shared";
const avatar = "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg";
export function CustomTrigger() {  return (    <Dropdown>      <Dropdown.Trigger xstyle={styles.round} aria-label="Account menu">        <Avatar>          <Avatar.Image alt="Junior Garcia" src={avatar} />          <Avatar.Fallback delay={600}>JD</Avatar.Fallback>        </Avatar>      </Dropdown.Trigger>      <Popup>        <div {...stylex.props(styles.profile)}>          <div {...stylex.props(styles.profileRow)}>            <Avatar size="sm">              <Avatar.Image alt="Jane" src={avatar} />              <Avatar.Fallback delay={600}>JD</Avatar.Fallback>            </Avatar>            <div {...stylex.props(styles.text)}>              <p {...stylex.props(styles.profileName)}>Jane Doe</p>              <p {...stylex.props(styles.profileEmail)}>[email protected]</p>            </div>          </div>        </div>        <ActionItem label="Dashboard" />        <ActionItem label="Profile" />        <Dropdown.Item label="Settings">          <div {...stylex.props(styles.itemRow)}>            <MenuItem.Label>Settings</MenuItem.Label>            <Gear {...stylex.props(styles.smallIcon)} />          </div>        </Dropdown.Item>        <Dropdown.Item label="New project">          <div {...stylex.props(styles.itemRow)}>            <MenuItem.Label>Create Team</MenuItem.Label>            <Persons {...stylex.props(styles.smallIcon)} />          </div>        </Dropdown.Item>        <Dropdown.Item label="Logout" variant="danger">          <div {...stylex.props(styles.itemRow)}>            <MenuItem.Label>Log Out</MenuItem.Label>            <ArrowRightFromSquare {...stylex.props(styles.smallDangerIcon)} />          </div>        </Dropdown.Item>      </Popup>    </Dropdown>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

长按触发

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";
// Adapted from HeroUI v3.2.6 long-press-trigger, Apache-2.0.import { useEffect, useRef, useState } from "react";import { Button, Dropdown } from "@lenso/ui";import { ActionItem, Popup } from "./_shared";
export function LongPressTrigger() {  const [open, setOpen] = useState(false);  const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);  const pointerActive = useRef(false);  const cancel = () => {    clearTimeout(timer.current);    timer.current = undefined;  };  useEffect(() => () => clearTimeout(timer.current), []);  return (    <Dropdown      open={open}      onOpenChange={(next, details) => {        if (details.reason === "trigger-press" && pointerActive.current) return;        setOpen(next);      }}    >      <Dropdown.Trigger        render={<Button aria-label="Menu" variant="secondary" />}        onPointerDown={(event) => {          if (event.button !== 0) return;          pointerActive.current = true;          cancel();          timer.current = setTimeout(() => setOpen(true), 500);        }}        onPointerUp={cancel}        onPointerCancel={cancel}        onPointerLeave={cancel}        onKeyDown={() => {          pointerActive.current = false;        }}      >        Long Press      </Dropdown.Trigger>      <Popup>        <ActionItem label="New file" />        <ActionItem label="Open file" />        <ActionItem label="Save file" />        <ActionItem label="Delete file" variant="danger" />      </Popup>    </Dropdown>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

自定义样式

Tailwind CSS

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";
// Adapted from HeroUI v3.2.6 custom-styles, Apache-2.0.import { Button, Dropdown } from "@lenso/ui";import { ActionItem, Popup, styles } from "./_shared";
export function CustomStyles() {  return (    <Dropdown>      <Dropdown.Trigger render={<Button variant="secondary" />}>Actions</Dropdown.Trigger>      <Popup xstyle={styles.customPopup}>        <ActionItem label="Rename" xstyle={styles.customItem} />        <ActionItem label="Duplicate" xstyle={styles.customItem} />        <ActionItem label="Delete" variant="danger" xstyle={styles.customDangerItem} />      </Popup>    </Dropdown>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

全局 CSS

可使用 @layer components 指令自定义 Dropdown 组件类。 了解更多。

@layer components {  .dropdown {    @apply flex flex-col gap-1;  }
  .dropdown__trigger {    @apply outline-none;  }
  .dropdown__popover {    @apply rounded-lg border border-border bg-overlay p-2;  }
  .dropdown__menu {    @apply flex flex-col gap-1;  }}

样式参考

HeroUI 遵循 BEM 方法论,确保组件变体与状态可复用且易于自定义。

CSS 类

Dropdown 组件使用以下 CSS 类(查看源码样式):

基础类 [!toc]

  • .dropdown - Dropdown 根容器
  • .dropdown__trigger - 触发 Dropdown 的按钮或元素
  • .dropdown__popover - Popover 容器
  • .dropdown__menu - Popover 内的菜单容器

状态类 [!toc]

  • .dropdown__trigger[data-focus-visible="true"] - 触发器聚焦状态
  • .dropdown__trigger[data-disabled="true"] - 触发器禁用状态
  • .dropdown__trigger[data-pressed="true"] - 触发器按下状态
  • .dropdown__popover[data-entering] - 进入动画状态
  • .dropdown__popover[data-exiting] - 退出动画状态
  • .dropdown__menu[data-selection-mode="single"] - 单选模式
  • .dropdown__menu[data-selection-mode="multiple"] - 多选模式

菜单组件类

Dropdown 使用 Menu、MenuItem 与 MenuSection 作为底层组件。以下类名也可用于自定义:

菜单类 [!toc]

  • .menu - 菜单容器(menu.css)
    • [data-slot="separator"] - 菜单内的分隔线元素

菜单项类 [!toc]

  • .menu-item - 菜单项容器(menu-item.css)
  • .menu-item__indicator - 选中指示器(对勾或圆点)
    • [data-slot="menu-item-indicator--checkmark"] - 对勾指示器 SVG
    • [data-slot="menu-item-indicator--dot"] - 圆点指示器 SVG
  • .menu-item__indicator--submenu - 子菜单指示器(箭头)
  • .menu-item--default - 默认样式变体
  • .menu-item--danger - 危险样式变体

菜单项状态类 [!toc]

  • .menu-item[data-focus-visible="true"] - 聚焦状态(键盘焦点)
  • .menu-item[data-focus="true"] - 聚焦状态
  • .menu-item[data-pressed] - 按下状态
  • .menu-item[data-hovered] - 悬停状态
  • .menu-item[data-selected="true"] - 选中状态
  • .menu-item[data-disabled] - 禁用状态
  • .menu-item[data-has-submenu="true"] - 带子菜单的项
  • .menu-item[data-selection-mode="single"] - 单选模式
  • .menu-item[data-selection-mode="multiple"] - 多选模式
  • .menu-item[aria-checked="true"] - 已勾选(ARIA)
  • .menu-item[aria-selected="true"] - 已选中(ARIA)

菜单分区类 [!toc]

交互状态

组件同时支持 CSS 伪类与 data 属性:

  • Hover:触发器与菜单项上 :hover 或 [data-hovered="true"]
  • Focus:触发器与菜单项上 :focus-visible 或 [data-focus-visible="true"]
  • Disabled:触发器与菜单项上 :disabled 或 [data-disabled="true"]
  • Pressed:触发器与菜单项上 :active 或 [data-pressed="true"]
  • Selected:菜单项上 [data-selected="true"] 或 [aria-selected="true"]

API 参考

Prop类型默认值描述
isOpenboolean-设置菜单展开状态(受控)。
defaultOpenboolean-设置菜单默认展开状态(非受控)。
onOpenChange(isOpen: boolean) => void-展开状态变化时调用的事件处理函数。
trigger"press" | "longPress""press"触发菜单的交互类型。
classNamestring-额外的 Tailwind CSS 类。
childrenReactNode-Dropdown 内容。
Prop类型默认值描述
classNamestring-额外的 Tailwind CSS 类。
childrenReactNode | RenderFunction-触发器内容或渲染函数。

使用 Button 作为触发器时,同样支持所有 Button props。

Prop类型默认值描述
placement"bottom" | "bottom left" | "bottom right" | "bottom start" | "bottom end" | "top" | "top left" | "top right" | "top start" | "top end" | "left" | "left top" | "left bottom" | "start" | "start top" | "start bottom" | "right" | "right top" | "right bottom" | "end" | "end top" | "end bottom""bottom"相对于触发器的 Popover 位置。
classNamestring-额外的 Tailwind CSS 类。
childrenReactNode-子内容。

同样支持所有 Popover props。

Prop类型默认值描述
selectionMode"single" | "multiple" | "none""none"是否启用单选、多选或不启用选择。
selectedKeysIterable<Key>-当前选中的 key(受控)。
defaultSelectedKeysIterable<Key>-初始选中的 key(非受控)。
onSelectionChange(keys: Selection) => void-选中变化时调用的事件处理函数。
disabledKeysIterable<Key>-禁用项的 key。
onAction(key: Key) => void-激活菜单项时调用的事件处理函数。
classNamestring-额外的 Tailwind CSS 类。
childrenReactNode-菜单内容。

同样支持所有 Menu props。

Prop类型默认值描述
selectionMode"single" | "multiple"-该分组内菜单项的选择模式。
selectedKeysIterable<Key>-当前选中的 key(受控)。
defaultSelectedKeysIterable<Key>-初始选中的 key(非受控)。
onSelectionChange(keys: Selection) => void-选中变化时调用的事件处理函数。
disabledKeysIterable<Key>-禁用项的 key。
classNamestring-额外的 Tailwind CSS 类。
childrenReactNode-分组内容。

同样支持所有 MenuSection props。

Prop类型默认值描述
idKey-菜单项唯一标识。
textValuestring-用于首字母导航的文本内容。
variant"default" | "danger""default"菜单项视觉变体。
classNamestring-额外的 Tailwind CSS 类。
childrenReactNode | RenderFunction-菜单项内容或渲染函数。

同样支持所有 MenuItem props。

Prop类型默认值描述
type"checkmark" | "dot""checkmark"指示器类型。
classNamestring-额外的 Tailwind CSS 类。
childrenReactNode | RenderFunction-自定义指示器内容或渲染函数。

使用渲染函数时,会传入以下值:

Prop类型描述
isSelectedboolean该项是否选中。
isIndeterminateboolean该项是否处于不确定状态。
Prop类型默认值描述
classNamestring-额外的 Tailwind CSS 类。
childrenReactNode-自定义指示器内容。
Prop类型默认值描述
classNamestring-额外的 Tailwind CSS 类。
childrenReactNode-子菜单触发器内容。

同样支持所有 SubmenuTrigger props。

Render Props

在 Dropdown.Item 中使用渲染函数时,会传入以下值:

Prop类型描述
isSelectedboolean该项是否选中。
isFocusedboolean该项是否聚焦。
isDisabledboolean该项是否禁用。
isPressedboolean该项是否处于按下状态。

示例

基本用法

import { Dropdown, Button, Label } from '@lenso/ui';
<Dropdown>  <Button aria-label="Menu" variant="secondary">    Actions  </Button>  <Dropdown.Popover>    <Dropdown.Menu onAction={(key) => alert(`Selected: ${key}`)}>      <Dropdown.Item id="new-file" textValue="New file">        <Label>New file</Label>      </Dropdown.Item>      <Dropdown.Item id="open-file" textValue="Open file">        <Label>Open file</Label>      </Dropdown.Item>      <Dropdown.Item id="delete-file" textValue="Delete file" variant="danger">        <Label>Delete file</Label>      </Dropdown.Item>    </Dropdown.Menu>  </Dropdown.Popover></Dropdown>

代码示例:分组选项

import { Dropdown, Button, Label, Header, Separator } from '@lenso/ui';
<Dropdown>  <Button aria-label="Menu" variant="secondary">    Actions  </Button>  <Dropdown.Popover>    <Dropdown.Menu onAction={(key) => alert(`Selected: ${key}`)}>      <Dropdown.Section>        <Header>Actions</Header>        <Dropdown.Item id="new-file" textValue="New file">          <Label>New file</Label>        </Dropdown.Item>        <Dropdown.Item id="edit-file" textValue="Edit file">          <Label>Edit file</Label>        </Dropdown.Item>      </Dropdown.Section>      <Separator />      <Dropdown.Section>        <Header>Danger zone</Header>        <Dropdown.Item id="delete-file" textValue="Delete file" variant="danger">          <Label>Delete file</Label>        </Dropdown.Item>      </Dropdown.Section>    </Dropdown.Menu>  </Dropdown.Popover></Dropdown>

受控选择

import type { Selection } from '@lenso/ui';
import { Dropdown, Button, Label } from '@lenso/ui';import { useState } from 'react';
function ControlledDropdown() {  const [selected, setSelected] = useState<Selection>(new Set(['bold']));
  return (    <Dropdown>      <Button aria-label="Menu" variant="secondary">        Actions      </Button>      <Dropdown.Popover>        <Dropdown.Menu          selectedKeys={selected}          selectionMode="multiple"          onSelectionChange={setSelected}        >          <Dropdown.Item id="bold" textValue="Bold">            <Label>Bold</Label>            <Dropdown.ItemIndicator />          </Dropdown.Item>          <Dropdown.Item id="italic" textValue="Italic">            <Label>Italic</Label>            <Dropdown.ItemIndicator />          </Dropdown.Item>        </Dropdown.Menu>      </Dropdown.Popover>    </Dropdown>  );}

代码示例:含子菜单

import { Dropdown, Button, Label } from '@lenso/ui';
<Dropdown>  <Button aria-label="Menu" variant="secondary">    Share  </Button>  <Dropdown.Popover>    <Dropdown.Menu onAction={(key) => alert(`Selected: ${key}`)}>      <Dropdown.Item id="copy-link" textValue="Copy Link">        <Label>Copy Link</Label>      </Dropdown.Item>      <Dropdown.SubmenuTrigger>        <Dropdown.Item id="share" textValue="Share">          <Label>Other</Label>          <Dropdown.SubmenuIndicator />        </Dropdown.Item>        <Dropdown.Popover>          <Dropdown.Menu>            <Dropdown.Item id="whatsapp" textValue="WhatsApp">              <Label>WhatsApp</Label>            </Dropdown.Item>            <Dropdown.Item id="telegram" textValue="Telegram">              <Label>Telegram</Label>            </Dropdown.Item>          </Dropdown.Menu>        </Dropdown.Popover>      </Dropdown.SubmenuTrigger>    </Dropdown.Menu>  </Dropdown.Popover></Dropdown>

无障碍

Dropdown 组件实现 ARIA 菜单模式,并提供:

  • 完整键盘导航(方向键、Home/End、首字母导航)
  • 屏幕阅读器对操作与选中变化的播报
  • 合理的焦点管理
  • 禁用态支持
  • 长按交互支持
  • 子菜单导航

更多信息见 React Aria Menu 文档。

相关组件