From 5729fe8f8784d44ebce580ffaf5426b68e93d035 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Wed, 24 Jun 2026 22:31:18 +0200 Subject: [PATCH] Add v2 IconButton component MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement the IconButton component from the Probo Radix UI Figma: a square, icon-only + ); +} diff --git a/packages/ui/src/v2/IconButton/IconButtonSkeleton.tsx b/packages/ui/src/v2/IconButton/IconButtonSkeleton.tsx new file mode 100644 index 000000000..d38f7d8eb --- /dev/null +++ b/packages/ui/src/v2/IconButton/IconButtonSkeleton.tsx @@ -0,0 +1,28 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import type { ComponentProps } from "react"; +import type { VariantProps } from "tailwind-variants/lite"; + +import { iconButtonSkeleton } from "./variants"; + +export type IconButtonSkeletonProps = Omit, "children"> & VariantProps; + +// Loading placeholder paired with IconButton: a square pulse block matching its +// size and radius. +export function IconButtonSkeleton(props: IconButtonSkeletonProps) { + const { size, className, ...rest } = props; + + return ; +} diff --git a/packages/ui/src/v2/IconButton/variants.ts b/packages/ui/src/v2/IconButton/variants.ts new file mode 100644 index 000000000..054d21510 --- /dev/null +++ b/packages/ui/src/v2/IconButton/variants.ts @@ -0,0 +1,134 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { tv } from "tailwind-variants/lite"; + +// Square, icon-only sibling of Button (Radix "IconButton"). The variant × color +// surface treatment mirrors Button's matrix; only the sizing differs (square, +// no text padding). +export const iconButton = tv({ + base: [ + "inline-flex shrink-0 items-center justify-center border border-transparent", + "cursor-pointer outline-none transition-colors select-none", + "focus-visible:ring-2 focus-visible:ring-sand-8 focus-visible:ring-offset-1 focus-visible:ring-offset-sand-1", + "disabled:pointer-events-none disabled:opacity-50", + ], + variants: { + // Corner radius is bound to size (per the Figma, radius is global/theme, + // not a per-instance prop). + size: { + 1: "size-6 rounded-2 [&_svg]:size-4", + 2: "size-8 rounded-2 [&_svg]:size-4", + 3: "size-10 rounded-3 [&_svg]:size-5", + 4: "size-12 rounded-3 [&_svg]:size-5", + }, + variant: { + classic: "", + solid: "", + soft: "", + surface: "", + outline: "", + ghost: "", + }, + color: { + neutral: "", + gold: "", + red: "", + green: "", + amber: "", + sky: "", + }, + highContrast: { + true: "", + false: "", + }, + }, + compoundVariants: [ + // solid / classic + { variant: ["solid", "classic"], color: "neutral", class: "bg-sand-9 text-white hover:bg-sand-10" }, + { variant: ["solid", "classic"], color: "gold", class: "bg-gold-9 text-white hover:bg-gold-10" }, + { variant: ["solid", "classic"], color: "red", class: "bg-red-9 text-white hover:bg-red-10" }, + { variant: ["solid", "classic"], color: "green", class: "bg-green-9 text-white hover:bg-green-10" }, + { variant: ["solid", "classic"], color: "amber", class: "bg-amber-9 text-amber-12 hover:bg-amber-10" }, + { variant: ["solid", "classic"], color: "sky", class: "bg-sky-9 text-sky-12 hover:bg-sky-10" }, + { variant: "classic", class: "shadow-2" }, + { variant: ["solid", "classic"], color: "neutral", highContrast: true, class: "bg-sand-12 text-sand-1 hover:bg-sand-12" }, + { variant: ["solid", "classic"], color: "gold", highContrast: true, class: "bg-gold-12 text-gold-1 hover:bg-gold-12" }, + { variant: ["solid", "classic"], color: "red", highContrast: true, class: "bg-red-12 text-red-1 hover:bg-red-12" }, + { variant: ["solid", "classic"], color: "green", highContrast: true, class: "bg-green-12 text-green-1 hover:bg-green-12" }, + { variant: ["solid", "classic"], color: "amber", highContrast: true, class: "bg-amber-12 text-amber-1 hover:bg-amber-12" }, + { variant: ["solid", "classic"], color: "sky", highContrast: true, class: "bg-sky-12 text-sky-1 hover:bg-sky-12" }, + + // soft + { variant: "soft", color: "neutral", class: "bg-sand-3 text-sand-11 hover:bg-sand-4" }, + { variant: "soft", color: "gold", class: "bg-gold-3 text-gold-11 hover:bg-gold-4" }, + { variant: "soft", color: "red", class: "bg-red-3 text-red-11 hover:bg-red-4" }, + { variant: "soft", color: "green", class: "bg-green-3 text-green-11 hover:bg-green-4" }, + { variant: "soft", color: "amber", class: "bg-amber-3 text-amber-11 hover:bg-amber-4" }, + { variant: "soft", color: "sky", class: "bg-sky-3 text-sky-11 hover:bg-sky-4" }, + + // surface + { variant: "surface", color: "neutral", class: "bg-sand-2 text-sand-11 border-sand-6 hover:bg-sand-3" }, + { variant: "surface", color: "gold", class: "bg-gold-2 text-gold-11 border-gold-6 hover:bg-gold-3" }, + { variant: "surface", color: "red", class: "bg-red-2 text-red-11 border-red-6 hover:bg-red-3" }, + { variant: "surface", color: "green", class: "bg-green-2 text-green-11 border-green-6 hover:bg-green-3" }, + { variant: "surface", color: "amber", class: "bg-amber-2 text-amber-11 border-amber-6 hover:bg-amber-3" }, + { variant: "surface", color: "sky", class: "bg-sky-2 text-sky-11 border-sky-6 hover:bg-sky-3" }, + + // outline + { variant: "outline", color: "neutral", class: "text-sand-11 border-sand-7 hover:bg-sand-3" }, + { variant: "outline", color: "gold", class: "text-gold-11 border-gold-7 hover:bg-gold-3" }, + { variant: "outline", color: "red", class: "text-red-11 border-red-7 hover:bg-red-3" }, + { variant: "outline", color: "green", class: "text-green-11 border-green-7 hover:bg-green-3" }, + { variant: "outline", color: "amber", class: "text-amber-11 border-amber-7 hover:bg-amber-3" }, + { variant: "outline", color: "sky", class: "text-sky-11 border-sky-7 hover:bg-sky-3" }, + + // ghost + { variant: "ghost", color: "neutral", class: "text-sand-11 hover:bg-sand-3" }, + { variant: "ghost", color: "gold", class: "text-gold-11 hover:bg-gold-3" }, + { variant: "ghost", color: "red", class: "text-red-11 hover:bg-red-3" }, + { variant: "ghost", color: "green", class: "text-green-11 hover:bg-green-3" }, + { variant: "ghost", color: "amber", class: "text-amber-11 hover:bg-amber-3" }, + { variant: "ghost", color: "sky", class: "text-sky-11 hover:bg-sky-3" }, + + // high-contrast text for the tinted variants + { variant: ["soft", "surface", "outline", "ghost"], color: "neutral", highContrast: true, class: "text-sand-12" }, + { variant: ["soft", "surface", "outline", "ghost"], color: "gold", highContrast: true, class: "text-gold-12" }, + { variant: ["soft", "surface", "outline", "ghost"], color: "red", highContrast: true, class: "text-red-12" }, + { variant: ["soft", "surface", "outline", "ghost"], color: "green", highContrast: true, class: "text-green-12" }, + { variant: ["soft", "surface", "outline", "ghost"], color: "amber", highContrast: true, class: "text-amber-12" }, + { variant: ["soft", "surface", "outline", "ghost"], color: "sky", highContrast: true, class: "text-sky-12" }, + ], + defaultVariants: { + size: 2, + variant: "solid", + color: "gold", + highContrast: false, + }, +}); + +export const iconButtonSkeleton = tv({ + base: "inline-block shrink-0 animate-pulse bg-sand-3 align-middle", + variants: { + size: { + 1: "size-6 rounded-2", + 2: "size-8 rounded-2", + 3: "size-10 rounded-3", + 4: "size-12 rounded-3", + }, + }, + defaultVariants: { + size: 2, + }, +});