基础组件
按钮组
将多个按钮组合成一组,共享边框和圆角,常用于工具栏和操作组。
安装
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" | 排列方向 |
className | string | — | 自定义样式类 |