{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "control-center",
  "title": "Control Center",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "The macOS Control Center panel in frosted glass: toggle tiles with round badges, grouped Wi-Fi, Bluetooth and AirDrop rows, pill sliders with the icon in the track, and a Now Playing tile. Real toggle buttons and Radix sliders.",
  "dependencies": [
    "radix-ui",
    "lucide-react"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.ballmac.com/r/i18n.json",
    "https://ui.ballmac.com/r/media.json"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/control-center.tsx",
      "content": "// Ballmac UI: Control Center. https://ui.ballmac.com/components/control-center\n\"use client\"\n\nimport * as React from \"react\"\nimport { Pause, Play, SkipBack, SkipForward } from \"lucide-react\"\nimport { Slider as SliderPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\nimport { useMessages } from \"@/lib/ballmac/i18n\"\nimport { Media, isMediaImage, type MediaSource } from \"@/components/ballmac/media\"\n\n/** Pressed state that works controlled or not. */\nfunction usePressed(pressed: boolean | undefined, defaultPressed: boolean, onChange?: (pressed: boolean) => void) {\n  const [state, setState] = React.useState(defaultPressed)\n  const value = pressed ?? state\n  return [\n    value,\n    () => {\n      setState(!value)\n      onChange?.(!value)\n    },\n  ] as const\n}\n\ntype ControlCenterProps = React.ComponentProps<\"section\"> & {\n  /** Accessible name of the panel. */\n  label?: string\n}\n\n/** The Control Center panel: frosted glass holding a grid of tiles, sliders and a media player. */\nfunction ControlCenter({ label, className, ...props }: ControlCenterProps) {\n  const msg = useMessages()\n  label ??= msg(\"control-center.label\", \"Control Center\")\n  return (\n    <section\n      data-slot=\"control-center\"\n      aria-label={label}\n      className={cn(\n        \"grid w-[22.5rem] max-w-full grid-cols-2 gap-2.5 rounded-[26px] border border-white/20 bg-background/55 p-3 text-foreground shadow-[0_24px_60px_-12px_rgb(0_0_0/0.45),inset_0_1px_0_rgb(255_255_255/0.25)] backdrop-blur-3xl backdrop-saturate-200 [--mac-accent:oklch(0.53_0.2_258)] dark:bg-neutral-900/60\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nconst tile = \"rounded-[18px] bg-foreground/[0.07] dark:bg-white/[0.09]\"\n\ntype ControlTileProps = Omit<React.ComponentProps<\"button\">, \"onChange\"> & {\n  /** Icon in the round badge. */\n  icon: React.ReactNode\n  /** Name of the control. */\n  label: string\n  /** Second line, such as the current state. */\n  status?: string\n  /** Controlled on state. */\n  pressed?: boolean\n  /** Initial on state when uncontrolled. */\n  defaultPressed?: boolean\n  /** Called with the new on state. */\n  onPressedChange?: (pressed: boolean) => void\n  /** Stack the badge over the label (a square tile) instead of beside it. */\n  stacked?: boolean\n}\n\n/** A toggle tile: a round icon badge that fills with the accent color when on. It is a real toggle button. */\nfunction ControlTile({ icon, label, status, pressed, defaultPressed = false, onPressedChange, stacked, className, onClick, ...props }: ControlTileProps) {\n  const [on, toggle] = usePressed(pressed, defaultPressed, onPressedChange)\n  return (\n    <button\n      type=\"button\"\n      data-slot=\"control-tile\"\n      aria-pressed={on}\n      onClick={(e) => {\n        onClick?.(e)\n        if (!e.defaultPrevented) toggle()\n      }}\n      className={cn(\n        tile,\n        \"group flex min-h-[4.25rem] items-center gap-2.5 p-2.5 text-start outline-none transition-[background-color,transform] duration-150 hover:bg-foreground/[0.11] focus-visible:ring-[3px] focus-visible:ring-ring/60 active:scale-[0.98] motion-reduce:transition-none motion-reduce:active:scale-100\",\n        stacked && \"flex-col items-start justify-between\",\n        className\n      )}\n      {...props}\n    >\n      <span\n        aria-hidden=\"true\"\n        className=\"flex size-9 shrink-0 items-center justify-center rounded-full bg-foreground/15 text-foreground transition-colors duration-200 group-aria-pressed:bg-(--mac-accent) group-aria-pressed:text-white motion-reduce:transition-none [&_svg]:size-[1.1rem]\"\n      >\n        {icon}\n      </span>\n      <span className=\"min-w-0 leading-tight\">\n        <span className=\"block truncate text-[13px] font-semibold\">{label}</span>\n        {status && <span className=\"block truncate text-xs text-muted-foreground\">{status}</span>}\n      </span>\n    </button>\n  )\n}\n\n/** A tile that groups related toggle rows, such as Wi-Fi, Bluetooth and AirDrop. Place it first in the grid; it spans two rows. */\nfunction ControlCluster({ className, ...props }: React.ComponentProps<\"div\">) {\n  return <div data-slot=\"control-cluster\" role=\"group\" className={cn(tile, \"row-span-2 flex flex-col justify-around gap-1 p-2.5\", className)} {...props} />\n}\n\ntype ControlRowProps = Omit<ControlTileProps, \"stacked\">\n\n/** One row inside a cluster: round badge plus name and state. */\nfunction ControlRow({ icon, label, status, pressed, defaultPressed = false, onPressedChange, className, onClick, ...props }: ControlRowProps) {\n  const [on, toggle] = usePressed(pressed, defaultPressed, onPressedChange)\n  return (\n    <button\n      type=\"button\"\n      data-slot=\"control-row\"\n      aria-pressed={on}\n      onClick={(e) => {\n        onClick?.(e)\n        if (!e.defaultPrevented) toggle()\n      }}\n      className={cn(\n        \"group flex items-center gap-2.5 rounded-xl p-1 text-start outline-none transition-colors hover:bg-foreground/[0.06] focus-visible:ring-[3px] focus-visible:ring-ring/60 motion-reduce:transition-none\",\n        className\n      )}\n      {...props}\n    >\n      <span\n        aria-hidden=\"true\"\n        className=\"flex size-9 shrink-0 items-center justify-center rounded-full bg-foreground/15 transition-colors duration-200 group-aria-pressed:bg-(--mac-accent) group-aria-pressed:text-white motion-reduce:transition-none [&_svg]:size-[1.1rem]\"\n      >\n        {icon}\n      </span>\n      <span className=\"min-w-0 leading-tight\">\n        <span className=\"block truncate text-[13px] font-semibold\">{label}</span>\n        {status && <span className=\"block truncate text-xs text-muted-foreground\">{status}</span>}\n      </span>\n    </button>\n  )\n}\n\ntype ControlSliderProps = Omit<React.ComponentProps<typeof SliderPrimitive.Root>, \"value\" | \"defaultValue\" | \"onValueChange\" | \"min\" | \"max\"> & {\n  /** Name shown above the slider and used as its accessible name. */\n  label: string\n  /** Icon drawn in the thumb's track (left side). */\n  icon?: React.ReactNode\n  /** Controlled value from 0 to 100. */\n  value?: number\n  /** Initial value when uncontrolled. */\n  defaultValue?: number\n  /** Called with the new value. */\n  onValueChange?: (value: number) => void\n}\n\n/** A full-width pill slider with the icon inside the track and a white thumb, like Display and Sound in Control Center. */\nfunction ControlSlider({ label, icon, value, defaultValue = 50, onValueChange, className, ...props }: ControlSliderProps) {\n  const [inner, setInner] = React.useState(defaultValue)\n  const current = value ?? inner\n  return (\n    <div data-slot=\"control-slider\" className={cn(tile, \"col-span-2 px-3 pt-2 pb-2.5\", className)}>\n      <p className=\"mb-1.5 text-[13px] font-semibold\">{label}</p>\n      <SliderPrimitive.Root\n        value={[current]}\n        min={0}\n        max={100}\n        step={1}\n        onValueChange={([v]) => {\n          setInner(v!)\n          onValueChange?.(v!)\n        }}\n        className=\"group relative flex h-7 w-full touch-none items-center select-none\"\n        {...props}\n      >\n        <SliderPrimitive.Track className=\"relative h-full grow overflow-hidden rounded-full bg-foreground/15 shadow-[inset_0_1px_2px_rgb(0_0_0/0.15)]\">\n          <SliderPrimitive.Range className=\"absolute h-full bg-foreground/85 dark:bg-white/90\" />\n          {icon && (\n            <span aria-hidden=\"true\" className=\"pointer-events-none absolute inset-y-0 start-2 flex items-center text-background mix-blend-normal dark:text-neutral-800 [&_svg]:size-4\">\n              {icon}\n            </span>\n          )}\n        </SliderPrimitive.Track>\n        <SliderPrimitive.Thumb aria-label={label} className=\"block size-7 rounded-full border border-black/10 bg-white shadow-[0_1px_4px_rgb(0_0_0/0.3)] outline-none focus-visible:ring-[3px] focus-visible:ring-ring/70\" />\n      </SliderPrimitive.Root>\n    </div>\n  )\n}\n\ntype ControlNowPlayingProps = Omit<React.ComponentProps<\"div\">, \"title\"> & {\n  /** Track title. */\n  title: string\n  /** Artist or show. */\n  artist: string\n  /** Artwork: an image URL, an object with alt text and a dark-mode file, or any element. Decorative: the title and artist carry the meaning. */\n  artwork?: MediaSource\n  /** Whether it is playing (controlled). */\n  playing?: boolean\n  /** Initial state when uncontrolled. */\n  defaultPlaying?: boolean\n  /** Called with the new playing state. */\n  onPlayingChange?: (playing: boolean) => void\n  /** Called for the previous button. */\n  onPrevious?: () => void\n  /** Called for the next button. */\n  onNext?: () => void\n}\n\n/** A media tile with artwork, title, artist and previous, play or pause, next buttons. */\nfunction ControlNowPlaying({ title, artist, artwork, playing, defaultPlaying = false, onPlayingChange, onPrevious, onNext, className, ...props }: ControlNowPlayingProps) {\n  const msg = useMessages()\n  const [on, toggle] = usePressed(playing, defaultPlaying, onPlayingChange)\n  const btn =\n    \"flex size-9 items-center justify-center rounded-full outline-none transition-colors hover:bg-foreground/10 focus-visible:ring-[3px] focus-visible:ring-ring/60 motion-reduce:transition-none [&_svg]:size-[1.15rem] [&_svg]:fill-current\"\n  return (\n    <div data-slot=\"control-now-playing\" className={cn(tile, \"col-span-2 flex items-center gap-3 p-2.5\", className)} {...props}>\n      <div aria-hidden=\"true\" className=\"size-14 shrink-0 overflow-hidden rounded-xl bg-gradient-to-br from-chart-4 to-chart-5 shadow-md [&>img]:size-full [&>img]:object-cover\">\n        {isMediaImage(artwork) ? <Media media={artwork} alt=\"\" fill className=\"size-full\" /> : artwork}\n      </div>\n      <div className=\"min-w-0 flex-1 leading-tight\">\n        <p className=\"truncate text-[13px] font-semibold\">{title}</p>\n        <p className=\"truncate text-xs text-muted-foreground\">{artist}</p>\n        <div className=\"-ms-2 mt-1 flex items-center\">\n          <button type=\"button\" aria-label={msg(\"control-center.previousTrack\", \"Previous track\")} onClick={onPrevious} className={btn}>\n            <SkipBack aria-hidden=\"true\" />\n          </button>\n          <button type=\"button\" aria-label={on ? msg(\"control-center.pause\", \"Pause\") : msg(\"control-center.play\", \"Play\")} onClick={toggle} className={btn}>\n            {on ? <Pause aria-hidden=\"true\" /> : <Play aria-hidden=\"true\" />}\n          </button>\n          <button type=\"button\" aria-label={msg(\"control-center.nextTrack\", \"Next track\")} onClick={onNext} className={btn}>\n            <SkipForward aria-hidden=\"true\" />\n          </button>\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport {\n  ControlCenter,\n  ControlTile,\n  ControlCluster,\n  ControlRow,\n  ControlSlider,\n  ControlNowPlaying,\n  type ControlCenterProps,\n  type ControlTileProps,\n  type ControlSliderProps,\n  type ControlNowPlayingProps,\n}\n",
      "type": "registry:ui",
      "target": "@components/ballmac/control-center.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "control-center",
      "macos",
      "toggles",
      "slider",
      "settings",
      "panel",
      "media"
    ],
    "version": "1.0.0",
    "updated": "2026-10-01",
    "ai": {
      "summary": "<ControlCenter> is a 2-column grid. Put <ControlCluster> with <ControlRow> toggles first, then <ControlTile>, <ControlSlider label icon> and <ControlNowPlaying>. Toggles are controlled with pressed / onPressedChange or uncontrolled.",
      "whenToUse": [
        "Mac system UI mockups",
        "Quick-settings popovers"
      ],
      "whenNotToUse": [
        "Full settings pages (use field, switch, slider)"
      ],
      "composesWith": [
        "menu-bar",
        "mac-window",
        "hud",
        "switch"
      ],
      "a11y": [
        {
          "keys": "Tab / Space / Enter",
          "action": "Every tile and row is a button with aria-pressed"
        },
        {
          "keys": "Arrow keys",
          "action": "Adjust a slider; Home and End jump to the ends"
        },
        {
          "keys": "Reduced motion",
          "action": "No press scale or color transitions"
        }
      ],
      "customization": [
        "ControlTile: icon, label, status, pressed, stacked",
        "ControlSlider: label, icon, value, onValueChange",
        "ControlNowPlaying: title, artist, artwork, playing"
      ]
    },
    "examples": [
      "control-center-demo",
      "control-center-compact"
    ],
    "url": "https://ui.ballmac.com/components/control-center"
  },
  "categories": [
    "macos"
  ],
  "type": "registry:ui"
}