1import React from "react";
2
3export interface ButtonGroupItem {
4 label: string;
5 content: React.ReactNode;
6 onClick?: () => void;
7 href?: string;
8 /** Sized to its content; otherwise a square key for a lone icon */
9 wide?: boolean;
10}
11
12interface ButtonGroupProps {
13 items: ButtonGroupItem[];
14 label: string;
15 className?: string;
16}
17
18/* One bezel, one face, several keys: a linked control in the same material
19 as Button (sm), split by grooves rather than gaps. Each key fills from the
20 bottom on hover and sinks when pressed, like a layered Button's face. */
21export function ButtonGroup({ items, label, className = "" }: ButtonGroupProps) {
22 return (
23 <div
24 role="group"
25 aria-label={label}
26 className={["inline-flex w-fit rounded-[12px] p-[3px]", className].join(" ")}
27 style={{ background: "var(--bezel-light)", boxShadow: "var(--bezel-light-shadow)" }}
28 >
29 <div
30 className="flex rounded-[9px] overflow-hidden"
31 style={{ background: "var(--face-light)", boxShadow: "var(--face-light-shadow)" }}
32 >
33 {items.map((item, i) => {
34 const keyClass = [
35 "group/key relative h-9 inline-flex items-center justify-center gap-1.5 cursor-pointer",
36 "text-[13px] font-medium text-neutral-800 outline-none",
37 "transition-[box-shadow,background] duration-150",
38 "active:bg-neutral-900/[0.04] active:[box-shadow:inset_0_1px_2px_rgba(17,17,17,0.08)]",
39 "focus-visible:[box-shadow:inset_0_0_0_2px_rgba(17,17,17,0.85)]",
40 item.wide ? "px-4" : "w-10",
41 ].join(" ");
42 const inner = (
43 <>
44 <span
45 aria-hidden
46 className="pointer-events-none absolute inset-0 origin-bottom scale-y-0 bg-neutral-900/[0.025] transition-transform duration-300 ease-out group-hover/key:scale-y-100"
47 />
48 <span className="relative inline-flex items-center gap-1.5">{item.content}</span>
49 </>
50 );
51 const common = {
52 "aria-label": item.label,
53 title: item.wide ? undefined : item.label,
54 className: keyClass,
55 style: { textShadow: "0 1px 0 rgba(255,255,255,0.9)" },
56 };
57
58 return (
59 <React.Fragment key={item.label}>
60 {i > 0 && (
61 <span
62 aria-hidden
63 className="w-px self-stretch my-2 bg-neutral-900/[0.08]"
64 style={{ boxShadow: "1px 0 0 rgba(255,255,255,0.9)" }}
65 />
66 )}
67 {item.href ? (
68 <a href={item.href} {...common}>
69 {inner}
70 </a>
71 ) : (
72 <button type="button" onClick={item.onClick} {...common}>
73 {inner}
74 </button>
75 )}
76 </React.Fragment>
77 );
78 })}
79 </div>
80 </div>
81 );
82}