gglc-ui

Button

Enam varian di empat tinggi (24 · 28 · 32 · 36), radius 10. API-nya kompatibel dengan Button milik shadcn.

Instalasi

npx shadcn@latest add @glc/button

Penggunaan

import { Button } from "@/components/ui/button"

<Button variant="outline" size="sm">Simpan</Button>

Varian

Enam nama yang sama persis dengan shadcn, jadi kode dari hulu langsung jalan. Yang berbeda hanya kulit destructive.

destructive bukan isian solid

Berkas Figma menggambar aksi destruktif sebagai isian merah 10% alpha dengan teks merah — bukan blok merah penuh seperti shadcn. Ini disengaja: di layar akuntansi, tombol hapus muncul di banyak baris sekaligus, dan merah pekat berulang-ulang membuat halaman terasa seperti kondisi darurat.

Ukuran

24/28/32/36 — bukan 32/36/40 milik shadcn. Angka ini diambil dari berkas Figma.

Hati-hati mencampur

Tombol shadcn dari hulu memakai tinggi 32/36/40. Sewarna, tapi tidak setinggi. Dalam satu toolbar atau satu baris form, selisihnya terlihat.

Dengan ikon

Pakai leading dan trailing, bukan menaruh ikon langsung sebagai children — supaya jaraknya konsisten.

Tombol yang isinya hanya ikon wajib punya aria-label. Tanpa itu, pembaca layar hanya mengumumkan “tombol”.

asChild

Meneruskan gaya ke elemen anak. Dipakai saat yang kamu butuhkan sebenarnya sebuah tautan.

Saat asChild aktif, leading dan trailing diabaikan — Slot hanya menerima satu anak, jadi susun ikonnya di dalam elemen anak itu sendiri.

Status

Props

Selain yang di bawah ini, seluruh atribut <button> diteruskan apa adanya.
PropTipeDefaultKeterangan
variant"default" | "secondary" | "outline" | "ghost" | "link" | "destructive""default"Kulit visual tombol.
size"xs" | "sm" | "default" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg""default"Tinggi kontrol. Varian icon-* berbentuk bujur sangkar tanpa padding horizontal.
fullWidthbooleanfalseMelebar memenuhi kontainer induk.
asChildbooleanfalseMerender elemen anak alih-alih <button>. Untuk membungkus tautan.
leadingReact.ReactNodeNode sebelum label. Diabaikan saat asChild aktif.
trailingReact.ReactNodeNode sesudah label. Diabaikan saat asChild aktif.

Kode sumber

Persis berkas yang disalin CLI ke repositorimu.
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "@/registry/glc/lib/utils"

/**
 * Company Books button — the `Button` set from Company BookV1.0.fig.
 *
 * Deliberately API-compatible with shadcn/ui's Button: same six variant names,
 * same `asChild`, same `size="icon"`. Anything written against shadcn's Button
 * keeps working; what changes is the geometry (the file's 24/28/32/36 heights
 * rather than shadcn's 32/36/40) and the destructive skin, which the file draws
 * as a red *tint* with red text rather than a solid red fill.
 */
const buttonVariants = cva(
  [
    "inline-flex shrink-0 items-center justify-center whitespace-nowrap",
    "rounded-lg border border-transparent font-medium",
    "transition-[background-color,color,box-shadow] duration-150",
    "outline-none focus-visible:ring-[3px] focus-visible:ring-ring/20 focus-visible:border-ring",
    "disabled:pointer-events-none disabled:opacity-50",
    "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
  ],
  {
    variants: {
      variant: {
        default:
          "bg-primary text-primary-foreground hover:bg-[var(--accent-hover)]",
        secondary:
          "bg-secondary text-secondary-foreground hover:bg-[var(--surface-pressed)]",
        outline:
          "border-border bg-card text-foreground hover:bg-accent hover:text-accent-foreground",
        ghost: "text-foreground hover:bg-accent hover:text-accent-foreground",
        link: "text-foreground underline underline-offset-[3px] hover:text-[var(--text-brand)]",
        // The file draws destructive as a 10%-alpha red fill with red label.
        destructive:
          "bg-[var(--danger-tint)] text-[var(--text-error)] hover:bg-[var(--danger-tint-strong)] focus-visible:ring-destructive/20",
      },
      size: {
        xs: "h-6 gap-1 px-2 text-xs leading-4 has-[>svg]:px-1.5",
        sm: "h-7 gap-1 px-2.5 text-sm leading-5 has-[>svg]:px-2",
        default: "h-8 gap-1.5 px-2.5 text-sm leading-5 has-[>svg]:px-2",
        lg: "h-9 gap-1.5 px-2.5 text-sm leading-5 has-[>svg]:px-2",
        icon: "size-8 p-0",
        "icon-xs": "size-6 p-0",
        "icon-sm": "size-7 p-0",
        "icon-lg": "size-9 p-0",
      },
      fullWidth: {
        true: "w-full",
        false: "",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
      fullWidth: false,
    },
  }
)

export interface ButtonProps
  extends React.ComponentProps<"button">,
    VariantProps<typeof buttonVariants> {
  /** Render as the child element (shadcn's `asChild`), e.g. wrapping a link. */
  asChild?: boolean
  /** Node rendered before the label. Prefer a Phosphor icon. */
  leading?: React.ReactNode
  /** Node rendered after the label. */
  trailing?: React.ReactNode
}

function Button({
  className,
  variant,
  size,
  fullWidth,
  asChild = false,
  leading,
  trailing,
  children,
  ...props
}: ButtonProps) {
  const Comp = asChild ? Slot : "button"

  return (
    <Comp
      data-slot="button"
      className={cn(buttonVariants({ variant, size, fullWidth, className }))}
      {...props}
    >
      {/* Slot accepts exactly one child, so `asChild` passes `children`
       * through untouched — compose icons inside the child element instead. */}
      {asChild ? (
        children
      ) : (
        <>
          {leading}
          {children}
          {trailing}
        </>
      )}
    </Comp>
  )
}

export { Button, buttonVariants }