Skip to main content
PolyUI/docs

基础组件

按钮组

将多个按钮组合成一组,共享边框和圆角,常用于工具栏和操作组。

安装

bash
npx polyui add button-group

基础

tsx
import { Button } from "@polyui/react/button"
export function ButtonGroupBasic() {
  return (
    <div className="inline-flex w-fit -space-x-px rounded-md shadow-xs rtl:space-x-reverse">
      <Button variant="outline" className="rounded-none rounded-l-md shadow-none focus-visible:z-10">
        Cut
      </Button>
      <Button variant="outline" className="rounded-none shadow-none focus-visible:z-10">
        Copy
      </Button>
      <Button variant="outline" className="rounded-none rounded-r-md shadow-none focus-visible:z-10">
        Paste
      </Button>
    </div>
  )
}

下载

15k
tsx
import { Button } from "@polyui/react/button"
import { DownloadIcon } from "lucide-react"
export function ButtonGroupDownload() {
  return (
    <div className="inline-flex w-fit -space-x-px rounded-md shadow-xs rtl:space-x-reverse">
      <Button variant="outline" className="rounded-none rounded-l-md shadow-none focus-visible:z-10">
        <DownloadIcon />
        Download
      </Button>
      <span className="bg-background dark:border-input dark:bg-input/30 flex items-center rounded-r-md border px-3 text-sm font-medium">
        15k
      </span>
    </div>
  )
}

点赞

46
tsx
import { useState } from "react"
import { Button } from "@polyui/react/button"
import { HeartIcon } from "lucide-react"
import { cn } from "@polyui/core/lib/utils"
export function ButtonGroupLike() {
  const [liked, setLiked] = useState(true)
  return (
    <div className="inline-flex w-fit -space-x-px rounded-md shadow-xs rtl:space-x-reverse">
      <Button
        variant="outline"
        className="rounded-none rounded-l-md shadow-none focus-visible:z-10"
        onClick={() => setLiked(!liked)}
      >
        <HeartIcon className={cn({ "fill-destructive stroke-destructive": liked })} />
        Like
      </Button>
      <span className="bg-background dark:border-input dark:bg-input/30 flex items-center rounded-r-md border px-3 text-sm font-medium">
        {liked ? 46 : 45}
      </span>
    </div>
  )
}

工具栏

tsx
import { Button } from "@polyui/react/button"
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from "@polyui/react/tooltip"
import { MousePointerIcon, SquareIcon, CropIcon, CopyIcon, EllipsisVerticalIcon } from "lucide-react"
import { cn } from "@polyui/core/lib/utils"
export function ButtonGroupToolbar() {
  return (
    <TooltipProvider>
      <div className="inline-flex w-fit -space-x-px rounded-md shadow-xs rtl:space-x-reverse">
        {[
          { icon: MousePointerIcon, label: "Select" },
          { icon: SquareIcon, label: "Shapes" },
          { icon: CropIcon, label: "Crop" },
          { icon: CopyIcon, label: "Duplicate" },
          { icon: EllipsisVerticalIcon, label: "More" },
        ].map(({ icon: Icon, label }, i, arr) => (
          <Tooltip key={label}>
            <TooltipTrigger
              render={
                <Button
                  variant="outline"
                  className={cn(
                    "rounded-none shadow-none focus-visible:z-10",
                    i === 0 && "rounded-l-md",
                    i === arr.length - 1 && "rounded-r-md"
                  )}
                />
              }
            >
              <Icon />
              <span className="sr-only">{label}</span>
            </TooltipTrigger>
            <TooltipContent className="px-2 py-1 text-xs">{label}</TooltipContent>
          </Tooltip>
        ))}
      </div>
    </TooltipProvider>
  )
}

圆角

tsx
import { Button } from "@polyui/react/button"
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from "@polyui/react/tooltip"
import { SkipBackIcon, PlayIcon, PauseIcon, SkipForwardIcon } from "lucide-react"
import { cn } from "@polyui/core/lib/utils"
export function ButtonGroupRounded() {
  return (
    <TooltipProvider>
      <div className="divide-primary-foreground/30 inline-flex w-fit divide-x rounded-full shadow-xs">
        {[
          { icon: SkipBackIcon, label: "Skip Back" },
          { icon: PlayIcon, label: "Play" },
          { icon: PauseIcon, label: "Pause" },
          { icon: SkipForwardIcon, label: "Skip Forward" },
        ].map(({ icon: Icon, label }, i, arr) => (
          <Tooltip key={label}>
            <TooltipTrigger
              render={
                <Button
                  className={cn(
                    "rounded-none focus-visible:z-10",
                    i === 0 && "rounded-l-full",
                    i === arr.length - 1 && "rounded-r-full"
                  )}
                />
              }
            >
              <Icon />
              <span className="sr-only">{label}</span>
            </TooltipTrigger>
            <TooltipContent className="px-2 py-1 text-xs">{label}</TooltipContent>
          </Tooltip>
        ))}
      </div>
    </TooltipProvider>
  )
}

社交登录

tsx
import { Button } from "@polyui/react/button"
import { TvIcon, CircleDotIcon, CameraIcon, UsersIcon } from "lucide-react"
export function ButtonGroupSocial() {
  return (
    <div className="inline-flex w-fit -space-x-px rounded-md shadow-xs rtl:space-x-reverse">
      <Button
        variant="outline"
        nativeButton={false}
        className="rounded-none rounded-l-md shadow-none hover:!bg-[#9146ff]/10 focus-visible:z-10"
        render={<a href="#" target="_blank" rel="noopener noreferrer" />}
      >
        <TvIcon className="stroke-[#9146ff]" />
        <span className="sr-only">Twitch</span>
      </Button>
      <Button
        variant="outline"
        nativeButton={false}
        className="rounded-none shadow-none hover:!bg-[#EA4C89]/10 focus-visible:z-10"
        render={<a href="#" target="_blank" rel="noopener noreferrer" />}
      >
        <CircleDotIcon className="stroke-[#EA4C89]" />
        <span className="sr-only">Dribbble</span>
      </Button>
      <Button
        variant="outline"
        nativeButton={false}
        className="rounded-none shadow-none hover:!bg-[#fb169a]/10 focus-visible:z-10"
        render={<a href="#" target="_blank" rel="noopener noreferrer" />}
      >
        <CameraIcon className="stroke-[#fb169a]" />
        <span className="sr-only">Instagram</span>
      </Button>
      <Button
        variant="outline"
        nativeButton={false}
        className="rounded-none rounded-r-md shadow-none hover:!bg-[#0866ff]/10 focus-visible:z-10"
        render={<a href="#" target="_blank" rel="noopener noreferrer" />}
      >
        <UsersIcon className="stroke-[#0866ff]" />
        <span className="sr-only">Facebook</span>
      </Button>
    </div>
  )
}

缩放

95%
tsx
import { useState } from "react"
import { Button } from "@polyui/react/button"
import { ZoomInIcon, ZoomOutIcon } from "lucide-react"
export function ButtonGroupZoom() {
  const [zoom, setZoom] = useState(95)
  return (
    <div className="inline-flex w-fit -space-x-px rounded-md shadow-xs rtl:space-x-reverse">
      <Button
        variant="outline"
        size="icon"
        className="rounded-none rounded-l-md shadow-none focus-visible:z-10"
        onClick={() => setZoom((z) => Math.max(0, z - 5))}
        disabled={zoom === 0}
      >
        <ZoomOutIcon />
        <span className="sr-only">Zoom out</span>
      </Button>
      <span className="bg-background dark:border-input dark:bg-input/30 flex items-center border px-3 text-sm font-medium">
        {zoom}%
      </span>
      <Button
        variant="outline"
        size="icon"
        className="rounded-none rounded-r-md shadow-none focus-visible:z-10"
        onClick={() => setZoom((z) => Math.min(100, z + 5))}
        disabled={zoom === 100}
      >
        <ZoomInIcon />
        <span className="sr-only">Zoom in</span>
      </Button>
    </div>
  )
}

数字步进

216px
tsx
import { useState } from "react"
import { Button } from "@polyui/react/button"
import { MinusIcon, PlusIcon } from "lucide-react"
export function ButtonGroupNumber() {
  const [value, setValue] = useState(216)
  return (
    <div className="divide-primary-foreground/30 inline-flex w-fit divide-x rounded-md shadow-xs">
      <Button
        size="icon"
        className="rounded-none rounded-l-full focus-visible:z-10"
        onClick={() => setValue((v) => v - 1)}
      >
        <MinusIcon />
        <span className="sr-only">Minus</span>
      </Button>
      <span className="bg-primary text-primary-foreground inline-flex items-center px-3 py-2 text-sm font-medium">
        {value}px
      </span>
      <Button
        size="icon"
        className="rounded-none rounded-r-full focus-visible:z-10"
        onClick={() => setValue((v) => v + 1)}
      >
        <PlusIcon />
        <span className="sr-only">Plus</span>
      </Button>
    </div>
  )
}

预览链接

tsx
import { Button } from "@polyui/react/button"
import { ExternalLinkIcon } from "lucide-react"
export function ButtonGroupPreview() {
  return (
    <div className="inline-flex w-fit -space-x-px rounded-md shadow-xs rtl:space-x-reverse">
      <Button
        variant="outline"
        nativeButton={false}
        className="rounded-none rounded-l-md shadow-none focus-visible:z-10"
        render={<a href="#" />}
      >
        Live preview
      </Button>
      <Button
        variant="outline"
        nativeButton={false}
        size="icon"
        className="rounded-none rounded-r-md shadow-none focus-visible:z-10"
        render={<a href="#" target="_blank" rel="noopener noreferrer" />}
      >
        <ExternalLinkIcon />
        <span className="sr-only">External link</span>
      </Button>
    </div>
  )
}

操作

tsx
import { Button } from "@polyui/react/button"
import { CopyIcon, SquarePenIcon, Trash2Icon } from "lucide-react"
export function ButtonGroupActions() {
  return (
    <div className="inline-flex w-fit -space-x-px rounded-md shadow-xs rtl:space-x-reverse">
      <Button variant="outline" className="rounded-none rounded-l-md shadow-none focus-visible:z-10">
        <SquarePenIcon />
        Edit
      </Button>
      <Button variant="outline" className="rounded-none shadow-none focus-visible:z-10">
        <CopyIcon />
        Duplicate
      </Button>
      <Button variant="outline" className="rounded-none rounded-r-md shadow-none focus-visible:z-10">
        <Trash2Icon />
        Delete
      </Button>
    </div>
  )
}

翻转控制

tsx
import { Button } from "@polyui/react/button"
import { FlipHorizontalIcon, FlipVerticalIcon } from "lucide-react"
export function ButtonGroupFlip() {
  return (
    <div className="divide-primary-foreground/30 inline-flex w-fit divide-x rounded-md shadow-xs">
      <Button size="icon" className="rounded-none rounded-l-md focus-visible:z-10">
        <FlipHorizontalIcon />
        <span className="sr-only">Flip Horizontal</span>
      </Button>
      <Button size="icon" className="rounded-none rounded-r-md focus-visible:z-10">
        <FlipVerticalIcon />
        <span className="sr-only">Flip Vertical</span>
      </Button>
    </div>
  )
}

合并下拉

tsx
import { useState } from "react"
import { Button } from "@polyui/react/button"
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuRadioGroup, DropdownMenuRadioItem } from "@polyui/react/dropdown-menu"
import { ChevronDownIcon } from "lucide-react"
export function ButtonGroupDropdownMerge() {
  const [selectedIndex, setSelectedIndex] = useState("0")

  return (
    <div className="divide-primary-foreground/30 inline-flex w-fit divide-x rounded-md shadow-xs">
      <Button className="rounded-none rounded-l-md focus-visible:z-10">
        {mergeOptions[Number(selectedIndex)].label}
      </Button>
      <DropdownMenu>
        <DropdownMenuTrigger render={<Button size="icon" className="rounded-none rounded-r-md focus-visible:z-10" />}>
          <ChevronDownIcon />
          <span className="sr-only">Select option</span>
        </DropdownMenuTrigger>
        <DropdownMenuContent side="bottom" sideOffset={4} align="end" className="max-w-64 md:max-w-xs!">
          <DropdownMenuRadioGroup value={selectedIndex} onValueChange={setSelectedIndex}>
            {mergeOptions.map((option, index) => (
              <DropdownMenuRadioItem key={option.label} value={String(index)} className="items-start [&>span]:pt-1.5">
                <div className="flex flex-col gap-1">
                  <span className="text-sm font-medium">{option.label}</span>
                  <span className="text-muted-foreground text-xs">{option.description}</span>
                </div>
              </DropdownMenuRadioItem>
            ))}
          </DropdownMenuRadioGroup>
        </DropdownMenuContent>
      </DropdownMenu>
    </div>
  )
}

幽灵导航

tsx
import { Button } from "@polyui/react/button"
import { BoxIcon, ChartBarBigIcon, SettingsIcon } from "lucide-react"
export function ButtonGroupGhostNav() {
  return (
    <div className="inline-flex w-fit rounded-md">
      <Button variant="ghost" className="rounded-none rounded-l-md focus-visible:z-10">
        <SettingsIcon />
        Settings
      </Button>
      <Button variant="ghost" className="rounded-none focus-visible:z-10">
        <BoxIcon />
        Dashboard
      </Button>
      <Button variant="ghost" className="rounded-none rounded-r-md focus-visible:z-10">
        <ChartBarBigIcon />
        Analytics
      </Button>
    </div>
  )
}

邮件操作

tsx
import { Button } from "@polyui/react/button"
import { ArchiveIcon, InboxIcon, SendHorizonalIcon } from "lucide-react"
export function ButtonGroupMailActions() {
  return (
    <div className="inline-flex w-fit -space-x-px rounded-md shadow-xs rtl:space-x-reverse">
      <Button variant="outline" className="rounded-none rounded-l-md shadow-none focus-visible:z-10">
        <InboxIcon />
        Inbox
      </Button>
      <Button variant="outline" className="rounded-none shadow-none focus-visible:z-10">
        <ArchiveIcon />
        Archived
      </Button>
      <Button variant="outline" className="rounded-none rounded-r-md shadow-none focus-visible:z-10">
        <SendHorizonalIcon />
        Sent
      </Button>
    </div>
  )
}

悬停显示

tsx
import { Button } from "@polyui/react/button"
import { ThumbsUpIcon, ThumbsDownIcon } from "lucide-react"
export function ButtonGroupReveal() {
  return (
    <div className="inline-flex w-fit -space-x-px rounded-md shadow-xs rtl:space-x-reverse">
      <Button
        variant="outline"
        className="group w-20 justify-start gap-3 overflow-hidden rounded-none rounded-l-md shadow-none transition-all duration-200 not-hover:w-10 hover:bg-sky-500/10 hover:text-sky-500 focus-visible:z-10 dark:hover:bg-sky-400/10 dark:hover:text-sky-400"
      >
        <ThumbsUpIcon />
        Like
      </Button>
      <Button
        variant="outline"
        className="hover:bg-destructive/10! group hover:text-destructive w-24.5 justify-end gap-3 overflow-hidden rounded-none rounded-r-md shadow-none transition-all duration-200 not-hover:w-10 focus-visible:z-10"
      >
        Dislike
        <ThumbsDownIcon />
      </Button>
    </div>
  )
}

上一页/下一页

tsx
import { Button } from "@polyui/react/button"
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"
export function ButtonGroupPrevNext() {
  return (
    <div className="divide-primary-foreground/30 inline-flex w-fit divide-x rounded-md shadow-xs">
      <Button className="rounded-none rounded-l-md transition-none active:scale-90 focus-visible:z-10">
        <ChevronLeftIcon />
        Previous
      </Button>
      <Button className="rounded-none rounded-r-md transition-none active:scale-90 focus-visible:z-10">
        Next
        <ChevronRightIcon />
      </Button>
    </div>
  )
}

光泽效果

tsx
import { Button } from "@polyui/react/button"
import { cn } from "@polyui/core/lib/utils"
export function ButtonGroupShine() {
  const shineClass =
    "relative overflow-hidden rounded-none before:absolute before:inset-0 before:rounded-[inherit] before:bg-[linear-gradient(45deg,transparent_25%,rgba(255,255,255,0.35)_50%,transparent_75%,transparent_100%)] before:bg-[length:250%_250%,100%_100%] before:bg-[position:200%_0,0_0] before:bg-no-repeat before:transition-[background-position_0s_ease] before:duration-1000 hover:before:bg-[position:-100%_0,0_0] focus-visible:z-10 dark:before:bg-[linear-gradient(45deg,transparent_25%,rgba(0,0,0,0.2)_50%,transparent_75%,transparent_100%)]"

  return (
    <div className="divide-primary-foreground/30 inline-flex w-fit divide-x rounded-md shadow-xs">
      <Button className={cn(shineClass, "rounded-l-md")}>Upload</Button>
      <Button className={shineClass}>Download</Button>
      <Button className={cn(shineClass, "rounded-r-md")}>Share</Button>
    </div>
  )
}

属性

属性类型默认值说明
orientation"horizontal" | "vertical""horizontal"排列方向
classNamestring自定义样式类