{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "action-pill", "type": "registry:ui", "title": "ActionPill", "description": "Toolbar pill of icon actions that collapse and grow as the page's context changes.", "categories": [ "navigation", "actions" ], "dependencies": [ "class-variance-authority" ], "registryDependencies": [ "https://whiskeyjack.net/r/utils.json" ], "files": [ { "path": "components/ui/action-pill.tsx", "type": "registry:ui", "target": "components/ui/action-pill.tsx", "content": "import * as React from 'react'\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport { cn } from '@/lib/utils'\n\n// ------------------------------------------------------------------\n// ActionPill container\n// ------------------------------------------------------------------\n\nconst actionPillVariants = cva(\n ['flex items-center rounded-full', 'shadow-md overflow-hidden transition-all duration-300'],\n {\n variants: {\n size: {\n desktop: 'h-10',\n mobile: 'h-12',\n },\n variant: {\n accent: 'bg-[var(--color-accent-500)] text-[var(--color-accent-foreground-icon)]',\n surface: [\n 'backdrop-blur-[16px]',\n 'bg-[color-mix(in_srgb,var(--color-surface-light)_80%,transparent)]',\n 'dark:bg-[color-mix(in_srgb,var(--color-surface-dark)_80%,transparent)]',\n 'border border-[var(--color-border-light)] dark:border-[var(--color-border-dark)]',\n 'text-[var(--color-accent-500)]',\n ].join(' '),\n },\n },\n defaultVariants: {\n size: 'desktop',\n variant: 'accent',\n },\n }\n)\n\nexport type ActionPillVariant = 'accent' | 'surface'\n\nexport interface ActionPillProps\n extends React.HTMLAttributes,\n VariantProps {}\n\n/**\n * Pill container for icon-only toolbar action buttons. Place one or more\n * `ActionPillButton` children inside.\n *\n * Size variants: `desktop` (h-10 / 40px, buttons w-10) and `mobile`\n * (h-12 / 48px, buttons w-12).\n *\n * Visual variants: `accent` (the primary pill -- accent bg, foreground-icon\n * color) and `surface` (secondary pill -- frosted surface bg + border with\n * accent-colored icons, same chrome as BottomNav / elevated toolbar panes).\n * Use `accent` for THE primary action group and `surface` for secondary\n * groups sitting next to it, so adjacent pills read as distinct groups\n * rather than one blob. Pass the same `variant` to the buttons inside.\n */\nconst ActionPill = React.forwardRef(\n ({ className, size, variant, ...props }, ref) => (\n \n )\n)\nActionPill.displayName = 'ActionPill'\n\n// ------------------------------------------------------------------\n// Shared button hover/active treatment per pill variant\n// ------------------------------------------------------------------\n\nconst buttonVariantClasses: Record = {\n accent:\n 'hover:bg-[var(--color-accent-foreground-icon-10)] active:bg-[var(--color-accent-foreground-icon-20)]',\n surface:\n 'hover:bg-[var(--color-warm-100)] dark:hover:bg-[var(--color-neutral-800)] active:bg-[var(--color-warm-200)] dark:active:bg-[var(--color-neutral-700)] disabled:opacity-50 disabled:pointer-events-none',\n}\n\n// ------------------------------------------------------------------\n// ActionPillButton\n// ------------------------------------------------------------------\n\nexport interface ActionPillButtonProps\n extends React.ButtonHTMLAttributes {\n /** When false the button collapses to w-0 and becomes inert. */\n visible?: boolean\n /**\n * ReactNode icon to render. The DS avoids bundling an icon library so the\n * caller passes the icon element directly (e.g. ``).\n */\n icon: React.ReactNode\n /** aria-label and title for the button -- required for accessibility. */\n label: string\n /** Size variant inherited from the parent ActionPill (pass through explicitly). */\n size?: 'desktop' | 'mobile'\n /** Visual variant inherited from the parent ActionPill (pass through explicitly). */\n variant?: ActionPillVariant\n}\n\n/**\n * Icon-only button for use inside `ActionPill`.\n *\n * When `visible` is `false` the button collapses to zero width, opacity 0, and\n * `pointer-events-none` with `tabIndex=-1` so it is completely inert. This is\n * the morphing-pill pattern: the pill container shrinks/grows as context-\n * dependent buttons are shown or hidden.\n *\n * `label` is applied as both `aria-label` and `title` (tooltip on hover).\n */\nconst ActionPillButton = React.forwardRef(\n ({ visible = true, icon, label, size = 'desktop', variant = 'accent', className, ...props }, ref) => {\n const dim = size === 'mobile' ? 'w-12 h-12' : 'w-10 h-10'\n return (\n \n {icon}\n \n )\n }\n)\nActionPillButton.displayName = 'ActionPillButton'\n\n// ------------------------------------------------------------------\n// ActionPillStaticButton -- always-visible button (no collapse)\n// ------------------------------------------------------------------\n\nexport interface ActionPillStaticButtonProps\n extends React.ButtonHTMLAttributes {\n icon: React.ReactNode\n label: string\n size?: 'desktop' | 'mobile'\n variant?: ActionPillVariant\n}\n\n/**\n * Always-visible button for use inside `ActionPill`. Use for actions that are\n * never context-dependent (e.g., the \"+\" create button). Contrast with\n * `ActionPillButton` which can be hidden.\n */\nconst ActionPillStaticButton = React.forwardRef(\n ({ icon, label, size = 'desktop', variant = 'accent', className, ...props }, ref) => {\n const dim = size === 'mobile' ? 'w-12 h-12' : 'w-10 h-10'\n return (\n \n {icon}\n \n )\n }\n)\nActionPillStaticButton.displayName = 'ActionPillStaticButton'\n\nexport { ActionPill, ActionPillButton, ActionPillStaticButton, actionPillVariants }\n" } ], "docs": "Contextual actions (ActionPillButton) collapse away when they do not apply; the primary action (ActionPillStaticButton) stays put. Use size=\"mobile\" in the bottom bar and size=\"desktop\" in the header, with the same buttons in the same order. Pass the same `variant` to the pill and the buttons inside it.", "meta": { "group": "navigation", "related": [ "bottom-nav", "mobile-bottom-nav" ], "exports": [ "ActionPill", "ActionPillButton", "ActionPillStaticButton" ], "siteSlug": "action-pill" } }