diff --git a/components/ui/8bit/button-group.tsx b/components/ui/8bit/button-group.tsx new file mode 100644 index 00000000..fb233050 --- /dev/null +++ b/components/ui/8bit/button-group.tsx @@ -0,0 +1,114 @@ +import type * as React from "react"; + +import { cn } from "@/lib/utils"; + +import { + ButtonGroup as ShadcnButtonGroup, + ButtonGroupText as ShadcnButtonGroupText, + buttonGroupVariants, +} from "@/components/ui/button-group"; +import { Separator } from "@/components/ui/8bit/separator"; + +import "@/components/ui/8bit/styles/retro.css"; + +export { buttonGroupVariants }; + +// ─── ButtonGroup ────────────────────────────────────────────────────────────── + +export type BitButtonGroupProps = React.ComponentProps< + typeof ShadcnButtonGroup +>; + +/** + * 8-bit ButtonGroup wraps the shadcn ButtonGroup and adds a shared retro + * pixelated border around the whole group. + * + * API matches shadcn: `orientation`, `data-slot="button-group"`. + * No React context — child button sizing and layout is handled via CSS + * child selectors in `buttonGroupVariants`, identical to shadcn. + */ +function ButtonGroup({ + className, + orientation = "horizontal", + children, + ...props +}: BitButtonGroupProps) { + return ( +
+ {/* Shared outer pixelated border */} + {/* Top */} +
+ {/* Bottom */} +
+ {/* Left */} +
+ {/* Right */} +
+ {/* Corners */} +
+
+
+
+ + + {children} + +
+ ); +} + +// ─── ButtonGroupSeparator ──────────────────────────────────────────────────── + +export type BitButtonGroupSeparatorProps = React.ComponentProps< + typeof Separator +>; + +/** + * 8-bit ButtonGroupSeparator renders a pixel-art dashed divider between items. + * Wraps the 8-bit Separator component (which uses a pixel-dash background pattern). + * Defaults to `orientation="vertical"` for use inside a horizontal ButtonGroup. + */ +function ButtonGroupSeparator({ + className, + orientation = "vertical", + ...props +}: BitButtonGroupSeparatorProps) { + return ( + + ); +} + +// ─── ButtonGroupText ───────────────────────────────────────────────────────── + +export type BitButtonGroupTextProps = React.ComponentProps< + typeof ShadcnButtonGroupText +>; + +/** + * 8-bit ButtonGroupText renders a retro-styled text label inside a ButtonGroup. + * Useful for split-button patterns (e.g. a label prefix before action buttons). + */ +function ButtonGroupText({ className, ...props }: BitButtonGroupTextProps) { + return ( + + ); +} + +export { ButtonGroup, ButtonGroupSeparator, ButtonGroupText }; diff --git a/components/ui/8bit/button.tsx b/components/ui/8bit/button.tsx index 8b12184c..254bf951 100644 --- a/components/ui/8bit/button.tsx +++ b/components/ui/8bit/button.tsx @@ -61,8 +61,8 @@ function Button({ children, asChild, ...props }: BitButtonProps) { {children} {variant !== "ghost" && variant !== "link" && size !== "icon" && ( - <> - {/* Pixelated border */} +
+ {/* Pixelated border — hidden when inside ButtonGroup */}
@@ -84,18 +84,18 @@ function Button({ children, asChild, ...props }: BitButtonProps) {
)} - +
)} {size === "icon" && ( - <> +
- +
)} ) : ( @@ -103,8 +103,8 @@ function Button({ children, asChild, ...props }: BitButtonProps) { {children} {variant !== "ghost" && variant !== "link" && size !== "icon" && ( - <> - {/* Pixelated border */} +
+ {/* Pixelated border — hidden when inside ButtonGroup */}
@@ -126,18 +126,18 @@ function Button({ children, asChild, ...props }: BitButtonProps) {
)} - +
)} {size === "icon" && ( - <> +
- +
)} )} diff --git a/components/ui/button-group.tsx b/components/ui/button-group.tsx new file mode 100644 index 00000000..cd550d7a --- /dev/null +++ b/components/ui/button-group.tsx @@ -0,0 +1,83 @@ +import { cva, type VariantProps } from "class-variance-authority" +import { Slot } from "radix-ui" + +import { cn } from "@/lib/utils" +import { Separator } from "@/components/ui/separator" + +const buttonGroupVariants = cva( + "flex w-fit items-stretch has-[>[data-slot=button-group]]:gap-2 [&>*]:focus-visible:relative [&>*]:focus-visible:z-10 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1", + { + variants: { + orientation: { + horizontal: + "[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none", + vertical: + "flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none", + }, + }, + defaultVariants: { + orientation: "horizontal", + }, + } +) + +function ButtonGroup({ + className, + orientation, + ...props +}: React.ComponentProps<"div"> & VariantProps) { + return ( +
+ ) +} + +function ButtonGroupText({ + className, + asChild = false, + ...props +}: React.ComponentProps<"div"> & { + asChild?: boolean +}) { + const Comp = asChild ? Slot.Root : "div" + + return ( + + ) +} + +function ButtonGroupSeparator({ + className, + orientation = "vertical", + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + ButtonGroup, + ButtonGroupSeparator, + ButtonGroupText, + buttonGroupVariants, +} diff --git a/config/nav-items.ts b/config/nav-items.ts index e4ac665c..5be18f31 100644 --- a/config/nav-items.ts +++ b/config/nav-items.ts @@ -27,6 +27,10 @@ const components = [ title: "Button", url: "/docs/components/button", }, + { + title: "Button Group", + url: "/docs/components/button-group", + }, { title: "Calendar", url: "/docs/components/calendar", diff --git a/content/docs/components/button-group.mdx b/content/docs/components/button-group.mdx new file mode 100644 index 00000000..ab766086 --- /dev/null +++ b/content/docs/components/button-group.mdx @@ -0,0 +1,178 @@ +--- +title: Button Group +description: A container that groups related 8-bit buttons together with a shared retro pixelated border. +--- + +import { Button } from "@/components/ui/8bit/button"; +import { ButtonGroup, ButtonGroupSeparator, ButtonGroupText } from "@/components/ui/8bit/button-group"; +import CopyCommandButton from "@/components/copy-command-button"; +import InstallationCommands from "@/components/installation-commands"; +import ComponentPreview from "@/components/component-preview"; + +
+ +
+ + + + + + + + + + + +## Examples + +--- + +### Horizontal (default) + + + + + + + + + + + +```tsx + + + + + + + +``` + +### Vertical + + + + + + + + + + + +```tsx + + + + + + + +``` + +### With Outline Variant + + + + + + + + + + + +```tsx + + + + + + + +``` + +### With Text Label + + + + Mode + + + + + + + +```tsx + + Mode + + + + + +``` + +## Installation + +--- + + + +## Usage + +--- + +```tsx +import { Button } from "@/components/ui/8bit/button"; +import { + ButtonGroup, + ButtonGroupSeparator, + ButtonGroupText, +} from "@/components/ui/8bit/button-group"; +``` + +```tsx + + + + + + + +``` + +## Props + +--- + +### ButtonGroup + +| Prop | Type | Default | +| --- | --- | --- | +| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | +| `aria-label` | `string` | — | +| `className` | `string` | — | + +### ButtonGroupSeparator + +Renders a pixel-art dashed divider. Wraps the 8-bit `Separator` component — defaults to `orientation="vertical"` for horizontal groups. + +| Prop | Type | Default | +| --- | --- | --- | +| `orientation` | `"horizontal" \| "vertical"` | `"vertical"` | +| `className` | `string` | — | + +### ButtonGroupText + +Renders a retro-styled text label inside the group — useful for split-button patterns like a mode prefix. + +| Prop | Type | Default | +| --- | --- | --- | +| `asChild` | `boolean` | `false` | +| `className` | `string` | — | diff --git a/registry.json b/registry.json index d5eafad9..e1ea2d84 100644 --- a/registry.json +++ b/registry.json @@ -47,6 +47,35 @@ } ] }, + { + "name": "button-group", + "type": "registry:component", + "title": "8-bit Button Group", + "description": "Groups multiple 8-bit buttons together inside a shared retro pixelated border.", + "registryDependencies": ["button", "button-group", "separator"], + "files": [ + { + "path": "components/ui/8bit/button-group.tsx", + "type": "registry:component", + "target": "components/ui/8bit/button-group.tsx" + }, + { + "path": "components/ui/8bit/button.tsx", + "type": "registry:component", + "target": "components/ui/8bit/button.tsx" + }, + { + "path": "components/ui/8bit/separator.tsx", + "type": "registry:component", + "target": "components/ui/8bit/separator.tsx" + }, + { + "path": "components/ui/8bit/styles/retro.css", + "type": "registry:component", + "target": "components/ui/8bit/styles/retro.css" + } + ] + }, { "name": "input", "type": "registry:component",