{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"theme-toggles","title":"Theme Toggles","description":"Animated sun and moon toggles that switch between light and dark mode. Search terms: actions.","dependencies":["lucide-react@1.18.0"],"registryDependencies":["@siteplane/base","@siteplane/toggle"],"files":[{"path":"packages/ui/src/components/theme-toggles/theme-toggles.tsx","content":"\"use client\";\n\nimport { type ComponentProps, type ReactElement, useId } from \"react\";\nimport { MoonIcon, SunIcon } from \"lucide-react\";\n\nimport {\n  type SiteplaneControlSize,\n  useResolvedControlSize,\n} from \"../../system/provider/siteplane-ui-provider\";\nimport { cn } from \"../../system/utils/cn\";\nimport { Toggle } from \"../toggle\";\nimport { KitToggle } from \"../toggle/toggle-internal\";\n\nimport \"./theme-toggles.css\";\n\ntype ThemeToggleInputProps = Omit<\n  ComponentProps<typeof Toggle>,\n  \"children\" | \"pressed\" | \"size\"\n> & {\n  /**\n   * `true` means dark mode: unpressed shows the sun, pressed shows the moon.\n   * The state keeps Toggle's names: `pressed`, `defaultPressed` and\n   * `onPressedChange`.\n   */\n  pressed?: boolean;\n  /** @deprecated Use `pressed`. */\n  checked?: boolean;\n  /** @deprecated Use `defaultPressed`. */\n  defaultChecked?: boolean;\n  /** @deprecated Use `onPressedChange`. */\n  onCheckedChange?: (checked: boolean) => void;\n  size?: SiteplaneControlSize;\n};\n\n/** Calls `onPressedChange` and the deprecated `onCheckedChange` alias. */\nfunction pressedChangeHandler(\n  onPressedChange: ThemeToggleInputProps[\"onPressedChange\"],\n  onCheckedChange: ThemeToggleInputProps[\"onCheckedChange\"],\n): ThemeToggleInputProps[\"onPressedChange\"] {\n  if (!onCheckedChange) return onPressedChange;\n  return (pressed, eventDetails) => {\n    onPressedChange?.(pressed, eventDetails);\n    onCheckedChange(pressed);\n  };\n}\n\nexport type SunMoonMorphToggleProps = ThemeToggleInputProps;\n\nexport function SunMoonMorphToggle({\n  \"aria-label\": ariaLabel = \"Dark mode\",\n  checked,\n  className,\n  defaultChecked,\n  defaultPressed = defaultChecked ?? false,\n  disabled,\n  id: idProp,\n  onCheckedChange,\n  onPressedChange,\n  pressed = checked,\n  size,\n  ...props\n}: SunMoonMorphToggleProps): ReactElement {\n  const generatedId = useId();\n  const id = idProp ?? `sp-sun-moon-${generatedId.replaceAll(\":\", \"\")}`;\n  const maskId = `${id}-mask`;\n  const resolvedSize = useResolvedControlSize(size);\n\n  return (\n    <span\n      className={cn(\n        \"sp-sun-moon-morph-toggle-root\",\n        \"sp-sun-moon-morph-root\",\n        className,\n      )}\n      data-size={resolvedSize}\n      data-slot=\"sun-moon-morph-toggle-root\"\n    >\n      <KitToggle\n        {...props}\n        aria-label={ariaLabel}\n        className=\"sp-sun-moon-morph-toggle\"\n        defaultPressed={defaultPressed}\n        disabled={disabled}\n        id={id}\n        onPressedChange={pressedChangeHandler(onPressedChange, onCheckedChange)}\n        padding=\"icon\"\n        profile=\"theme\"\n        pressed={pressed}\n        size={resolvedSize}\n        variant=\"default\"\n      >\n        <svg\n          aria-hidden=\"true\"\n          className=\"sp-sun-moon-icon opacity-80\"\n          fill=\"currentColor\"\n          viewBox=\"0 0 20 20\"\n        >\n          <mask id={maskId}>\n            <rect fill=\"white\" height=\"20\" width=\"20\" />\n            <circle cx=\"11\" cy=\"3\" fill=\"black\" r=\"8\" />\n          </mask>\n          <circle\n            className=\"sp-sun-moon-shape\"\n            cx=\"10\"\n            cy=\"10\"\n            mask={`url(#${maskId})`}\n            r=\"8\"\n          />\n          <g>\n            <circle\n              className=\"sp-sun-moon-ray sp-sun-moon-ray-1\"\n              cx=\"18\"\n              cy=\"10\"\n              r=\"1.5\"\n            />\n            <circle\n              className=\"sp-sun-moon-ray sp-sun-moon-ray-2\"\n              cx=\"14\"\n              cy=\"16.928\"\n              r=\"1.5\"\n            />\n            <circle\n              className=\"sp-sun-moon-ray sp-sun-moon-ray-3\"\n              cx=\"6\"\n              cy=\"16.928\"\n              r=\"1.5\"\n            />\n            <circle\n              className=\"sp-sun-moon-ray sp-sun-moon-ray-4\"\n              cx=\"2\"\n              cy=\"10\"\n              r=\"1.5\"\n            />\n            <circle\n              className=\"sp-sun-moon-ray sp-sun-moon-ray-5\"\n              cx=\"6\"\n              cy=\"3.172\"\n              r=\"1.5\"\n            />\n            <circle\n              className=\"sp-sun-moon-ray sp-sun-moon-ray-6\"\n              cx=\"14\"\n              cy=\"3.172\"\n              r=\"1.5\"\n            />\n          </g>\n        </svg>\n      </KitToggle>\n    </span>\n  );\n}\n\nexport type LayeredThemeToggleProps = ThemeToggleInputProps;\n\nexport function LayeredThemeToggle({\n  \"aria-label\": ariaLabel = \"Dark mode\",\n  checked,\n  className,\n  defaultChecked,\n  defaultPressed = defaultChecked ?? false,\n  disabled,\n  id: idProp,\n  onCheckedChange,\n  onPressedChange,\n  pressed = checked,\n  size,\n  ...props\n}: LayeredThemeToggleProps): ReactElement {\n  const resolvedSize = useResolvedControlSize(size);\n\n  return (\n    <span\n      className={cn(\n        \"sp-layered-theme-toggle-root\",\n        \"sp-layered-theme-root\",\n        className,\n      )}\n      data-size={resolvedSize}\n      data-slot=\"layered-theme-toggle-root\"\n    >\n      <KitToggle\n        {...props}\n        aria-label={ariaLabel}\n        className=\"sp-layered-theme-toggle\"\n        defaultPressed={defaultPressed}\n        disabled={disabled}\n        id={idProp}\n        onPressedChange={pressedChangeHandler(onPressedChange, onCheckedChange)}\n        padding=\"icon\"\n        profile=\"theme\"\n        pressed={pressed}\n        size={resolvedSize}\n        variant=\"default\"\n      >\n        <span className=\"sp-layered-theme-icon sp-layered-theme-moon\">\n          <MoonIcon aria-hidden=\"true\" className=\"opacity-80\" />\n        </span>\n        <span className=\"sp-layered-theme-icon sp-layered-theme-sun\">\n          <SunIcon aria-hidden=\"true\" className=\"opacity-80\" />\n        </span>\n      </KitToggle>\n    </span>\n  );\n}\n","type":"registry:ui","target":"~/src/siteplane/components/theme-toggles/theme-toggles.tsx"},{"path":"packages/ui/src/components/theme-toggles/theme-toggles.css","content":"@layer theme, base, components, siteplane, utilities;\n\n@layer siteplane {\n  /* Sun Moon Morph */\n  .sp-sun-moon-morph-toggle-root {\n    --sp-sun-moon-icon-size: 1.5625rem;\n    display: inline-flex;\n  }\n\n  .sp-sun-moon-morph-toggle-root .sp-sun-moon-morph-toggle {\n    position: relative;\n    display: grid;\n    place-items: center;\n  }\n\n  .sp-sun-moon-morph-toggle-root[data-size=\"sm\"] {\n    --sp-sun-moon-icon-size: 1.375rem;\n  }\n\n  .sp-sun-moon-morph-toggle-root[data-size=\"md\"] {\n    --sp-sun-moon-icon-size: 1.5625rem;\n  }\n\n  .sp-sun-moon-morph-toggle-root[data-size=\"lg\"] {\n    --sp-sun-moon-icon-size: 1.6875rem;\n  }\n\n  .sp-sun-moon-morph-toggle-root[data-size=\"xl\"] {\n    --sp-sun-moon-icon-size: 1.9375rem;\n  }\n\n  @media (min-width: 640px) {\n    .sp-sun-moon-morph-toggle-root {\n      --sp-sun-moon-icon-size: 1.375rem;\n    }\n\n    .sp-sun-moon-morph-toggle-root[data-size=\"sm\"] {\n      --sp-sun-moon-icon-size: 1.25rem;\n    }\n\n    .sp-sun-moon-morph-toggle-root[data-size=\"md\"] {\n      --sp-sun-moon-icon-size: 1.375rem;\n    }\n\n    .sp-sun-moon-morph-toggle-root[data-size=\"lg\"] {\n      --sp-sun-moon-icon-size: 1.5625rem;\n    }\n\n    .sp-sun-moon-morph-toggle-root[data-size=\"xl\"] {\n      --sp-sun-moon-icon-size: 1.6875rem;\n    }\n  }\n\n  /* Unpressed (light mode) shows the sun. */\n  .sp-sun-moon-morph-toggle-root .sp-sun-moon-icon {\n    position: absolute;\n    width: var(--sp-sun-moon-icon-size);\n    height: var(--sp-sun-moon-icon-size);\n    pointer-events: none;\n    transform: rotate(90deg);\n    transition: transform var(--motion-duration-linger)\n      var(--motion-ease-soft-in-out);\n  }\n\n  .sp-sun-moon-morph-toggle-root .sp-sun-moon-shape {\n    transform-origin: center;\n    transform: scale(0.55);\n    transition: transform var(--motion-duration-linger)\n      var(--motion-ease-soft-in-out);\n  }\n\n  .sp-sun-moon-morph-toggle-root mask circle {\n    transform: translate(16px, -3px);\n    transition: transform var(--motion-duration-linger)\n      var(--motion-ease-standard);\n  }\n\n  /* Rays burst out in sequence when the sun returns. Transitions, unlike a\n   * keyframe animation, do not replay when a toggle mounts. */\n  .sp-sun-moon-morph-toggle-root .sp-sun-moon-ray {\n    transform-origin: center;\n    transform: scale(1);\n    transition: transform var(--motion-duration-linger)\n      var(--motion-ease-standard);\n  }\n\n  .sp-sun-moon-morph-toggle-root .sp-sun-moon-ray-2 {\n    transition-delay: calc(var(--motion-duration-micro) / 2);\n  }\n\n  .sp-sun-moon-morph-toggle-root .sp-sun-moon-ray-3 {\n    transition-delay: var(--motion-duration-micro);\n  }\n\n  .sp-sun-moon-morph-toggle-root .sp-sun-moon-ray-4 {\n    transition-delay: var(--motion-duration-fast);\n  }\n\n  .sp-sun-moon-morph-toggle-root .sp-sun-moon-ray-5 {\n    transition-delay: var(--motion-duration-base);\n  }\n\n  .sp-sun-moon-morph-toggle-root .sp-sun-moon-ray-6 {\n    transition-delay: var(--motion-duration-moderate);\n  }\n\n  /* Pressed (dark mode) shows the moon. */\n  .sp-sun-moon-morph-toggle-root .sp-sun-moon-morph-toggle[data-pressed] svg {\n    color: inherit;\n    transform: rotate(40deg);\n  }\n\n  .sp-sun-moon-morph-toggle-root\n    .sp-sun-moon-morph-toggle[data-pressed]\n    svg\n    mask\n    circle {\n    transform: translate(0, 0);\n  }\n\n  .sp-sun-moon-morph-toggle-root\n    .sp-sun-moon-morph-toggle[data-pressed]\n    svg\n    .sp-sun-moon-shape {\n    transform: scale(1);\n  }\n\n  /* Rays retract together into the growing disc. */\n  .sp-sun-moon-morph-toggle-root\n    .sp-sun-moon-morph-toggle[data-pressed]\n    svg\n    .sp-sun-moon-ray {\n    transform: scale(0);\n    transition-delay: 0ms;\n    transition-duration: var(--motion-duration-base);\n    transition-timing-function: var(--motion-ease-soft-in-out);\n  }\n\n  html[data-motion-reduce=\"true\"]\n    .sp-sun-moon-morph-toggle-root\n    :is(.sp-sun-moon-icon, .sp-sun-moon-shape, .sp-sun-moon-ray, mask circle),\n  :where([data-sp-motion=\"reduced\"])\n    .sp-sun-moon-morph-toggle-root\n    :is(.sp-sun-moon-icon, .sp-sun-moon-shape, .sp-sun-moon-ray, mask circle) {\n    transition-delay: 0ms !important;\n    transition-duration: 0ms !important;\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    .sp-sun-moon-morph-toggle-root\n      :is(\n        .sp-sun-moon-icon,\n        .sp-sun-moon-shape,\n        .sp-sun-moon-ray,\n        mask circle\n      ) {\n      transition-delay: 0ms !important;\n      transition-duration: 0ms !important;\n    }\n  }\n\n  /* Layered */\n  .sp-layered-theme-toggle-root {\n    --sp-layered-theme-icon-size: 1.375rem;\n    display: inline-flex;\n  }\n\n  .sp-layered-theme-toggle-root .sp-layered-theme-toggle {\n    position: relative;\n    display: grid;\n    place-items: center;\n    line-height: 1;\n  }\n\n  .sp-layered-theme-toggle-root[data-size=\"sm\"] {\n    --sp-layered-theme-icon-size: 1.25rem;\n  }\n\n  .sp-layered-theme-toggle-root[data-size=\"md\"] {\n    --sp-layered-theme-icon-size: 1.375rem;\n  }\n\n  .sp-layered-theme-toggle-root[data-size=\"lg\"] {\n    --sp-layered-theme-icon-size: 1.5rem;\n  }\n\n  .sp-layered-theme-toggle-root[data-size=\"xl\"] {\n    --sp-layered-theme-icon-size: 1.625rem;\n  }\n\n  @media (min-width: 640px) {\n    .sp-layered-theme-toggle-root {\n      --sp-layered-theme-icon-size: 1.25rem;\n    }\n\n    .sp-layered-theme-toggle-root[data-size=\"sm\"] {\n      --sp-layered-theme-icon-size: 1.125rem;\n    }\n\n    .sp-layered-theme-toggle-root[data-size=\"md\"] {\n      --sp-layered-theme-icon-size: 1.25rem;\n    }\n\n    .sp-layered-theme-toggle-root[data-size=\"lg\"] {\n      --sp-layered-theme-icon-size: 1.375rem;\n    }\n\n    .sp-layered-theme-toggle-root[data-size=\"xl\"] {\n      --sp-layered-theme-icon-size: 1.5rem;\n    }\n  }\n\n  .sp-layered-theme-toggle-root .sp-layered-theme-icon {\n    grid-column: 1;\n    grid-row: 1;\n    line-height: 0.1;\n    transform: rotate(0deg) scale(1);\n    transition: transform var(--motion-duration-moderate)\n      var(--motion-ease-soft-in-out);\n  }\n\n  .sp-layered-theme-toggle-root .sp-layered-theme-icon svg {\n    width: var(--sp-layered-theme-icon-size);\n    height: var(--sp-layered-theme-icon-size);\n  }\n\n  /* The sun while unpressed (light mode), the moon while pressed (dark mode). */\n  .sp-layered-theme-toggle-root .sp-layered-theme-moon {\n    transform: rotate(0deg) scale(0);\n  }\n\n  .sp-layered-theme-toggle-root\n    .sp-layered-theme-toggle[data-pressed]\n    .sp-layered-theme-moon {\n    transform: rotate(360deg) scale(1);\n  }\n\n  .sp-layered-theme-toggle-root\n    .sp-layered-theme-toggle[data-pressed]\n    .sp-layered-theme-sun {\n    transform: rotate(360deg) scale(0);\n  }\n\n  html[data-motion-reduce=\"true\"]\n    .sp-layered-theme-toggle-root\n    .sp-layered-theme-icon,\n  :where([data-sp-motion=\"reduced\"])\n    .sp-layered-theme-toggle-root\n    .sp-layered-theme-icon {\n    transition-duration: 0ms;\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    .sp-layered-theme-toggle-root .sp-layered-theme-icon {\n      transition-duration: 0ms;\n    }\n  }\n}\n","type":"registry:ui","target":"~/src/siteplane/components/theme-toggles/theme-toggles.css"},{"path":"packages/ui/src/components/theme-toggles/index.ts","content":"export * from \"./theme-toggles\";\n","type":"registry:ui","target":"~/src/siteplane/components/theme-toggles/index.ts"}],"type":"registry:ui","docs":"https://ui.siteplane.io/docs-markdown/components/theme-toggles.md","categories":["Animated"],"meta":{"avoidWhen":["Avoid for a choice among light, dark and system; use Toggle Group."],"description":"Animated sun and moon toggles that switch between light and dark mode.","knownLimitations":["The toggle reports its state but does not apply the theme; the application provider does."],"providerNotes":[],"releaseStatus":"stable","searchTerms":["theme-toggles","theme toggles","actions","theme","toggles"],"useWhen":["Use for a theme control that morphs between a sun and a moon, in a header or a settings row."]}}