{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"color-picker","title":"Color Picker","description":"Color editor with saturation, hue, hex and RGB controls.","dependencies":["react-aria-components@1.20.0"],"registryDependencies":["@siteplane/base","@siteplane/copy-button","@siteplane/button","@siteplane/color-swatch-picker","@siteplane/input"],"files":[{"path":"packages/ui/src/components/color-picker/color-picker.tsx","content":"\"use client\";\n\nimport * as React from \"react\";\nimport { useFormReset } from \"../../system/form/use-form-reset\";\nimport {\n  Button as ColorButtonPrimitive,\n  ColorArea as ColorAreaPrimitive,\n  ColorField as ColorFieldPrimitive,\n  ColorPicker as ColorPickerPrimitive,\n  ColorSlider as ColorSliderPrimitive,\n  ColorSwatch as ColorSwatchPrimitive,\n  ColorThumb as ColorThumbPrimitive,\n  Dialog as ColorDialogPrimitive,\n  DialogTrigger as ColorDialogTriggerPrimitive,\n  Input as ColorInputPrimitive,\n  Popover as ColorPopoverPrimitive,\n  SliderTrack as ColorSliderTrackPrimitive,\n  parseColor,\n} from \"react-aria-components\";\n\nimport {\n  type SiteplaneControlSize,\n  type SiteplaneRadius,\n  SiteplaneUIPortalScope,\n  useResolvedControlSize,\n  useResolvedRadius,\n} from \"../../system/provider/siteplane-ui-provider\";\nimport { surfaceClassNames } from \"../../system/styles/surface\";\nimport { cn } from \"../../system/utils/cn\";\nimport { Button, type ButtonProps } from \"../button/button\";\nimport { CopyButton } from \"../copy-button/copy-button\";\nimport {\n  noOwnerValidation,\n  useFieldControlBridge,\n  useFieldControlProxy,\n} from \"../field/field-control-bridge\";\nimport {\n  ColorSwatchFace,\n  type ColorSwatchIndicator,\n} from \"../color-swatch-picker/color-swatch-face\";\nimport { ExternalInputOwner, Input } from \"../input/input\";\n\nimport \"./color-picker.css\";\n\n/** The accessible names of the controls in the picker's popover. */\nexport type ColorPickerLabels = {\n  /** The popover's dialog. */\n  controls: string;\n  /** The saturation and brightness area. */\n  saturation: string;\n  /** The hue slider. */\n  hue: string;\n  /** The hex value field. */\n  hex: string;\n  /** The copy button. */\n  copy: string;\n  /** The copy button while its check confirms a copy. */\n  copied: string;\n  /** The copy button and its announcement when the clipboard refuses. */\n  copyFailed: string;\n  /** The red, green and blue channel fields. */\n  red: string;\n  green: string;\n  blue: string;\n};\n\nconst defaultColorPickerLabels: ColorPickerLabels = {\n  controls: \"Color controls\",\n  saturation: \"Color saturation and brightness\",\n  hue: \"Color hue\",\n  hex: \"Hex color value\",\n  copy: \"Copy hex value\",\n  copied: \"Copied hex value\",\n  copyFailed: \"Copy unavailable\",\n  red: \"R color channel\",\n  green: \"G color channel\",\n  blue: \"B color channel\",\n};\n\n/**\n * The props the picker supports. The form value is a hidden input the picker\n * owns, so it takes no other input props.\n */\nexport interface ColorPickerProps {\n  \"aria-describedby\"?: string | undefined;\n  \"aria-invalid\"?: React.AriaAttributes[\"aria-invalid\"];\n  \"aria-label\"?: string | undefined;\n  \"aria-labelledby\"?: string | undefined;\n  className?: string | undefined;\n  disabled?: boolean | undefined;\n  /** The id of the form the color belongs to. */\n  form?: string | undefined;\n  /** The trigger's id. */\n  id?: string | undefined;\n  /** Replaces the English names of the controls in the popover. */\n  labels?: Partial<ColorPickerLabels> | undefined;\n  /** Submits the hex value under this name; a Field name takes precedence. */\n  name?: string | undefined;\n  /**\n   * Shows and submits the color; the controls open for reading and copying,\n   * but the color does not change, as in ColorSwatchPicker.\n   */\n  readOnly?: boolean | undefined;\n  required?: boolean | undefined;\n  /** The trigger's style. */\n  style?: React.CSSProperties | undefined;\n  /** -1 takes the trigger out of the tab order. */\n  tabIndex?: number | undefined;\n  /** Controlled hex value. Omit for uncontrolled usage. */\n  value?: string;\n  /** Uncontrolled initial hex value. */\n  defaultValue?: string;\n  onValueChange?: (value: string) => void;\n  /** Controlled open state of the popover. Omit for uncontrolled usage. */\n  open?: boolean;\n  /** Uncontrolled initial open state of the popover. */\n  defaultOpen?: boolean;\n  /** Called when the popover opens or closes. */\n  onOpenChange?: (open: boolean) => void;\n  showValue?: boolean;\n  size?: SiteplaneControlSize;\n  /**\n   * `swatch` shows the trigger as a bare swatch of the current color, drawn\n   * like a ColorSwatchPicker swatch, for a color row that ends in a custom\n   * color. The default is an outlined button with the color and its hex value.\n   */\n  variant?: \"default\" | \"swatch\";\n  /** `swatch` only: the swatch is the chosen color of its row. */\n  selected?: boolean;\n  /** `swatch` only: how a selected swatch shows it, as ColorSwatchPicker. */\n  indicator?: ColorSwatchIndicator;\n}\n\nexport function ColorPicker({\n  \"aria-describedby\": ariaDescribedBy,\n  \"aria-invalid\": ariaInvalid,\n  \"aria-label\": ariaLabel,\n  \"aria-labelledby\": ariaLabelledBy,\n  className,\n  defaultOpen,\n  defaultValue = \"#000000\",\n  disabled: disabledProp,\n  form,\n  id,\n  indicator = \"check\",\n  labels,\n  name: nameProp,\n  onOpenChange,\n  onValueChange,\n  open,\n  readOnly = false,\n  required,\n  selected,\n  showValue = true,\n  size,\n  style,\n  tabIndex,\n  value,\n  variant = \"default\",\n}: ColorPickerProps): React.ReactElement {\n  // Inside a Field, FieldLabel names the trigger, FieldDescription and\n  // FieldError describe it, and the Field supplies name and disabled state.\n  const field = useFieldControlBridge({\n    \"aria-describedby\": ariaDescribedBy,\n    \"aria-labelledby\": ariaLabelledBy,\n    disabled: disabledProp,\n    name: nameProp,\n  });\n  const disabled = field.disabled;\n  const name = field.name;\n  const text = { ...defaultColorPickerLabels, ...labels };\n  const rootRef = React.useRef<HTMLSpanElement>(null);\n  const resolvedSize = useResolvedControlSize(size);\n  const radius = useResolvedRadius<SiteplaneRadius>(undefined);\n  const swatch = variant === \"swatch\";\n  const controlled = value !== undefined;\n  const [resetKey, setResetKey] = React.useState(0);\n  const formMarkerRef = useFormReset<HTMLInputElement>(form, () => {\n    if (!controlled) setResetKey((current) => current + 1);\n  });\n  const normalizedDefault = normalizeHexColor(defaultValue);\n  // A read-only picker keeps its color: the value it shows is pinned.\n  const normalizedValue =\n    controlled || readOnly\n      ? normalizeHexColor(value, normalizedDefault)\n      : undefined;\n\n  return (\n    <ColorPickerPrimitive\n      key={resetKey}\n      {...(normalizedValue === undefined\n        ? { defaultValue: normalizedDefault }\n        : { value: normalizedValue })}\n      onChange={(color) => {\n        if (!readOnly) onValueChange?.(color.toString(\"hex\").toLowerCase());\n      }}\n    >\n      {({ color }) => {\n        const current = color.toString(\"hex\").toLowerCase();\n        const triggerAriaLabel = field.labelledBy\n          ? undefined\n          : (ariaLabel ?? `Choose color ${current.toUpperCase()}`);\n\n        return (\n          <span\n            className={cn(\n              \"sp-base-color-picker\",\n              \"contents\",\n              swatch && \"sp-color-swatch-scope\",\n            )}\n            data-foundation=\"color-picker\"\n            data-indicator={swatch ? indicator : undefined}\n            data-radius={swatch ? radius : undefined}\n            data-size={resolvedSize}\n            ref={rootRef}\n          >\n            <ColorDialogTriggerPrimitive\n              {...present({ defaultOpen, isOpen: open, onOpenChange })}\n            >\n              {swatch ? (\n                <ColorButtonPrimitive\n                  {...present({\n                    \"aria-current\": selected ? (\"true\" as const) : undefined,\n                    \"aria-describedby\": field.describedBy,\n                    \"aria-label\": triggerAriaLabel,\n                    \"aria-labelledby\": field.labelledBy,\n                    id,\n                    style,\n                  })}\n                  className={cn(\n                    \"sp-color-picker\",\n                    \"sp-color-swatch-item\",\n                    className,\n                  )}\n                  data-selected={selected || undefined}\n                  data-slot=\"color-picker\"\n                  data-variant=\"swatch\"\n                  isDisabled={disabled}\n                  type=\"button\"\n                  {...(tabIndex === -1 ? { excludeFromTabOrder: true } : {})}\n                >\n                  <ColorSwatchFace\n                    color={current}\n                    indicator={indicator}\n                    selected={selected}\n                  />\n                </ColorButtonPrimitive>\n              ) : (\n                <Button\n                  aria-describedby={field.describedBy}\n                  aria-invalid={\n                    ariaInvalid ?? (field.valid === false || undefined)\n                  }\n                  aria-label={triggerAriaLabel}\n                  aria-labelledby={field.labelledBy}\n                  className={cn(\n                    \"sp-color-picker-trigger\",\n                    showValue && \"sp-color-picker-trigger-with-value\",\n                    className,\n                  )}\n                  data-show-value={showValue || undefined}\n                  data-slot=\"color-picker\"\n                  disabled={disabled}\n                  id={id}\n                  render={<ColorButtonPrimitive isDisabled={disabled} />}\n                  size={getTriggerSize(resolvedSize, showValue)}\n                  style={style}\n                  tabIndex={tabIndex}\n                  type=\"button\"\n                  variant=\"outline\"\n                >\n                  <ColorSwatchPrimitive\n                    aria-hidden=\"true\"\n                    className=\"sp-color-picker-trigger-swatch\"\n                    data-slot=\"color-picker-trigger-swatch\"\n                  />\n                  {showValue ? (\n                    <span\n                      className=\"sp-color-picker-value\"\n                      data-slot=\"color-picker-value\"\n                    >\n                      {current}\n                    </span>\n                  ) : null}\n                </Button>\n              )}\n              <ColorPopoverPrimitive\n                className={cn(\n                  surfaceClassNames.floatingOverlay,\n                  \"sp-color-picker-popover\",\n                )}\n                data-size={resolvedSize}\n                data-slot=\"color-picker-popover\"\n                offset={6}\n                placement=\"bottom start\"\n              >\n                <SiteplaneUIPortalScope>\n                  <ColorDialogPrimitive\n                    aria-label={text.controls}\n                    className=\"sp-color-picker-panel sp-color-picker-dialog\"\n                    data-slot=\"color-picker-panel\"\n                  >\n                    <div\n                      className=\"sp-color-picker-saturation-field sp-color-picker-area-frame\"\n                      data-slot=\"color-picker-saturation-field\"\n                    >\n                      <ColorAreaPrimitive\n                        aria-label={text.saturation}\n                        className=\"sp-color-picker-saturation sp-color-picker-area\"\n                        colorSpace=\"hsb\"\n                        data-slot=\"color-picker-saturation\"\n                        isDisabled={disabled || readOnly}\n                        xChannel=\"saturation\"\n                        yChannel=\"brightness\"\n                      >\n                        <ColorThumbPrimitive\n                          className=\"sp-color-picker-saturation-thumb sp-color-picker-area-thumb\"\n                          data-slot=\"color-picker-saturation-thumb\"\n                        />\n                      </ColorAreaPrimitive>\n                    </div>\n\n                    <div className=\"sp-color-picker-hue-row\">\n                      <ColorSwatchPrimitive\n                        aria-hidden=\"true\"\n                        className=\"sp-color-picker-current-swatch\"\n                        data-slot=\"color-picker-current-swatch\"\n                      />\n                      <ColorSliderPrimitive\n                        aria-label={text.hue}\n                        channel=\"hue\"\n                        className=\"sp-color-picker-hue\"\n                        colorSpace=\"hsb\"\n                        data-slot=\"color-picker-hue\"\n                        isDisabled={disabled || readOnly}\n                      >\n                        <ColorSliderTrackPrimitive\n                          className=\"sp-color-picker-hue-track\"\n                          data-slot=\"color-picker-hue-track\"\n                        >\n                          <ColorThumbPrimitive\n                            className=\"sp-color-picker-hue-thumb\"\n                            data-slot=\"color-picker-hue-thumb\"\n                          />\n                        </ColorSliderTrackPrimitive>\n                      </ColorSliderPrimitive>\n                    </div>\n\n                    <div className=\"sp-color-picker-hex-row\">\n                      <ColorFieldPrimitive\n                        aria-label={text.hex}\n                        className=\"contents\"\n                        isDisabled={disabled}\n                        isReadOnly={readOnly}\n                      >\n                        <ColorTextInput\n                          className=\"sp-color-picker-hex-input\"\n                          size={resolvedSize}\n                          spellCheck={false}\n                        />\n                      </ColorFieldPrimitive>\n                      <CopyButton\n                        disabled={disabled}\n                        labels={{\n                          copied: text.copied,\n                          copy: text.copy,\n                          copyFailed: text.copyFailed,\n                        }}\n                        size={getCopyButtonSize(resolvedSize)}\n                        value={current}\n                        variant=\"outline\"\n                      />\n                    </div>\n\n                    <div className=\"sp-color-picker-channels\">\n                      <ColorChannelField\n                        channel=\"red\"\n                        disabled={disabled}\n                        label=\"R\"\n                        name={text.red}\n                        readOnly={readOnly}\n                        size={resolvedSize}\n                      />\n                      <ColorChannelField\n                        channel=\"green\"\n                        disabled={disabled}\n                        label=\"G\"\n                        name={text.green}\n                        readOnly={readOnly}\n                        size={resolvedSize}\n                      />\n                      <ColorChannelField\n                        channel=\"blue\"\n                        disabled={disabled}\n                        label=\"B\"\n                        name={text.blue}\n                        readOnly={readOnly}\n                        size={resolvedSize}\n                      />\n                    </div>\n                  </ColorDialogPrimitive>\n                </SiteplaneUIPortalScope>\n              </ColorPopoverPrimitive>\n            </ColorDialogTriggerPrimitive>\n            <Input\n              disabled={disabled}\n              form={form}\n              name={name}\n              readOnly\n              required={required}\n              type=\"hidden\"\n              value={current}\n            />\n            <Input\n              aria-hidden=\"true\"\n              disabled\n              form={form}\n              ref={formMarkerRef}\n              type=\"hidden\"\n            />\n            {field.inField ? (\n              <ColorPickerFieldProxy\n                disabled={disabled}\n                focusTarget={() =>\n                  rootRef.current?.querySelector<HTMLElement>(\n                    '[data-slot=\"color-picker\"]',\n                  ) ?? null\n                }\n                form={form}\n                name={name}\n                readOnly={readOnly}\n                required={required}\n                value={current}\n              />\n            ) : null}\n          </span>\n        );\n      }}\n    </ColorPickerPrimitive>\n  );\n}\n\n// Inside a Field, a visually hidden proxy registers the picker with Field and\n// Form like the date fields: FieldLabel focuses the trigger, and Form values\n// include the color.\nfunction ColorPickerFieldProxy({\n  disabled,\n  focusTarget,\n  form,\n  name,\n  readOnly,\n  required,\n  value,\n}: {\n  disabled: boolean;\n  focusTarget: () => HTMLElement | null;\n  form: string | undefined;\n  name: string | undefined;\n  readOnly: boolean;\n  required: boolean | undefined;\n  value: string;\n}): React.ReactElement | null {\n  const proxyProps = useFieldControlProxy({\n    controlled: false,\n    disabled,\n    displayValidation: noOwnerValidation,\n    focusTarget,\n    form,\n    name,\n    onRevalidate: () => {},\n    realtimeValidation: noOwnerValidation,\n    required,\n    value,\n  });\n\n  return proxyProps ? (\n    <ExternalInputOwner>\n      <Input nativeInput readOnly={readOnly} unstyled {...proxyProps} />\n    </ExternalInputOwner>\n  ) : null;\n}\n\nfunction ColorChannelField({\n  channel,\n  disabled,\n  label,\n  name,\n  readOnly,\n  size,\n}: {\n  channel: \"blue\" | \"green\" | \"red\";\n  disabled: boolean;\n  /** The letter beside the field, hidden from assistive technology. */\n  label: \"B\" | \"G\" | \"R\";\n  /** The field's accessible name. */\n  name: string;\n  readOnly: boolean;\n  size: SiteplaneControlSize;\n}): React.ReactElement {\n  return (\n    <ColorFieldPrimitive\n      aria-label={name}\n      channel={channel}\n      className=\"sp-color-picker-channel\"\n      colorSpace=\"rgb\"\n      isDisabled={disabled}\n      isReadOnly={readOnly}\n    >\n      <ColorTextInput\n        className=\"sp-color-picker-channel-input\"\n        inputMode=\"numeric\"\n        size={size}\n      />\n      <span aria-hidden=\"true\">{label}</span>\n    </ColorFieldPrimitive>\n  );\n}\n\nfunction ColorTextInput({\n  className,\n  inputMode,\n  size,\n  spellCheck,\n}: {\n  className?: string;\n  inputMode?: React.HTMLAttributes<HTMLInputElement>[\"inputMode\"];\n  size: SiteplaneControlSize;\n  spellCheck?: boolean;\n}): React.ReactElement {\n  // The React Aria color field labels this input; an enclosing Field belongs\n  // to the picker trigger.\n  return (\n    <ExternalInputOwner>\n      <Input\n        className={className}\n        inputMode={inputMode}\n        renderInput={<ColorInputPrimitive />}\n        size={size}\n        spellCheck={spellCheck}\n      />\n    </ExternalInputOwner>\n  );\n}\n\n// Props that are set, for components that do not accept an explicit undefined.\nfunction present<T extends Record<string, unknown>>(\n  props: T,\n): { [K in keyof T]?: Exclude<T[K], undefined> } {\n  return Object.fromEntries(\n    Object.entries(props).filter(([, value]) => value !== undefined),\n  ) as { [K in keyof T]?: Exclude<T[K], undefined> };\n}\n\nfunction getTriggerSize(\n  size: SiteplaneControlSize,\n  showValue: boolean,\n): ButtonProps[\"size\"] {\n  if (!showValue) {\n    if (size === \"sm\") return \"icon-sm\";\n    if (size === \"lg\") return \"icon-lg\";\n    if (size === \"xl\") return \"icon-xl\";\n    return \"icon-md\";\n  }\n\n  return size;\n}\n\nfunction getCopyButtonSize(\n  size: SiteplaneControlSize,\n): \"icon-lg\" | \"icon-md\" | \"icon-sm\" | \"icon-xl\" {\n  if (size === \"sm\") return \"icon-sm\";\n  if (size === \"lg\") return \"icon-lg\";\n  if (size === \"xl\") return \"icon-xl\";\n  return \"icon-md\";\n}\n\nfunction normalizeHexColor(\n  value: string | undefined,\n  fallback = \"#000000\",\n): string {\n  const candidate = value?.trim();\n  if (!candidate) return fallback;\n\n  const withHash = candidate.startsWith(\"#\") ? candidate : `#${candidate}`;\n\n  try {\n    return parseColor(withHash).toString(\"hex\").toLowerCase();\n  } catch {\n    return fallback;\n  }\n}\n","type":"registry:ui","target":"~/src/siteplane/components/color-picker/color-picker.tsx"},{"path":"packages/ui/src/components/color-picker/color-picker.css","content":"@layer theme, base, components, siteplane, utilities;\n\n@layer siteplane {\n  .sp-color-picker-trigger {\n    text-transform: uppercase;\n  }\n\n  .sp-color-picker-trigger-with-value {\n    min-width: 8rem;\n    justify-content: flex-start;\n    font-family: var(--sp-semantic-font-family-mono);\n    font-size: var(--sp-rw-font-12);\n    font-weight: 400;\n  }\n\n  /* The swatch is a fixed share (5/8) of the trigger's height, so every size keeps\n   * the same proportion and inset. The heights are the control scale of the\n   * Button, which sets `data-size` on the trigger: a text size, or its icon size\n   * when only the swatch shows. */\n  .sp-color-picker-trigger {\n    --sp-color-picker-trigger-height: var(--sp-rw-control-md);\n  }\n\n  .sp-color-picker-trigger:is([data-size=\"sm\"], [data-size=\"icon-sm\"]) {\n    --sp-color-picker-trigger-height: var(--sp-rw-control-sm);\n  }\n\n  .sp-color-picker-trigger:is([data-size=\"lg\"], [data-size=\"icon-lg\"]) {\n    --sp-color-picker-trigger-height: var(--sp-rw-control-lg);\n  }\n\n  .sp-color-picker-trigger:is([data-size=\"xl\"], [data-size=\"icon-xl\"]) {\n    --sp-color-picker-trigger-height: var(--sp-rw-control-xl);\n  }\n\n  .sp-color-picker-trigger-swatch {\n    width: calc(var(--sp-color-picker-trigger-height) * 0.625);\n    height: calc(var(--sp-color-picker-trigger-height) * 0.625);\n    flex: none;\n    border: var(--sp-rw-border-default) solid var(--sp-rw-border-translucent);\n    border-radius: max(0px, calc(var(--sp-rw-radius-control) - 4px));\n  }\n\n  /* One compact width at every size (250 px): the saturation area, the hue row,\n   * the hex row and the three channel fields fit it without wrapping, so a\n   * larger control size only makes the fields taller. */\n  .sp-color-picker-popover {\n    z-index: 50;\n    width: 15.625rem;\n    max-width: calc(100vw - 1.5rem);\n    padding: var(--sp-rw-space-3);\n    outline: none;\n    transform-origin: var(--trigger-anchor-point);\n  }\n\n  .sp-color-picker-dialog {\n    display: grid;\n    gap: var(--sp-rw-space-3);\n    outline: none;\n  }\n\n  .sp-color-picker-area-frame {\n    width: 100%;\n    height: 8rem;\n    overflow: hidden;\n    border: var(--sp-rw-border-default) solid var(--sp-rw-border-translucent);\n    border-radius: var(--sp-rw-radius-control);\n    background-clip: padding-box;\n  }\n\n  .sp-color-picker-popover[data-size=\"sm\"] .sp-color-picker-area-frame {\n    height: 7rem;\n  }\n\n  .sp-color-picker-popover[data-size=\"lg\"] .sp-color-picker-area-frame {\n    height: 9rem;\n  }\n\n  .sp-color-picker-popover[data-size=\"xl\"] .sp-color-picker-area-frame {\n    height: 10rem;\n  }\n\n  .sp-color-picker-area {\n    position: relative;\n    width: 100%;\n    height: 100%;\n    touch-action: none;\n  }\n\n  .sp-color-picker-area-thumb,\n  .sp-color-picker-hue-thumb {\n    width: 1rem;\n    height: 1rem;\n    border: 2px solid white;\n    border-radius: 999px;\n    background: var(--color, transparent);\n    box-shadow: 0 0 0 1px rgb(0 0 0 / 24%);\n    outline: none;\n    transform: translate(-50%, -50%);\n  }\n\n  /* React Aria places a slider thumb along its track only (`left`, plus the\n   * centring transform above). The cross axis is left to the consumer, so without\n   * this the thumb hangs from the track's top edge. Half the track height puts\n   * its centre on the track's centre line. */\n  .sp-color-picker-hue-thumb {\n    top: 50%;\n  }\n\n  .sp-color-picker-area-thumb[data-focus-visible],\n  .sp-color-picker-hue-thumb[data-focus-visible] {\n    box-shadow:\n      0 0 0 1px rgb(0 0 0 / 24%),\n      0 0 0 3px var(--sp-semantic-color-focus);\n  }\n\n  .sp-color-picker-hue-row {\n    display: flex;\n    align-items: center;\n    gap: var(--sp-rw-space-3);\n  }\n\n  .sp-color-picker-current-swatch {\n    width: 1.75rem;\n    height: 1.75rem;\n    flex: none;\n    border: var(--sp-rw-border-default) solid var(--sp-rw-border-translucent);\n    border-radius: 999px;\n  }\n\n  .sp-color-picker-hue {\n    flex: 1;\n    cursor: pointer;\n    touch-action: none;\n  }\n\n  .sp-color-picker-hue-track {\n    position: relative;\n    height: 0.875rem;\n    border-radius: 999px;\n    background: linear-gradient(\n      to right,\n      #f00,\n      #ff0,\n      #0f0,\n      #0ff,\n      #00f,\n      #f0f,\n      #f00\n    );\n  }\n\n  /* A right-to-left page: the dir attribute is named beside :dir(rtl), which a\n   * build for older browsers turns into a list of right-to-left languages, as\n   * Tailwind's rtl variant does. */\n  :is(:dir(rtl), [dir=\"rtl\"], [dir=\"rtl\"] *) .sp-color-picker-hue-track {\n    background: linear-gradient(\n      to left,\n      #f00,\n      #ff0,\n      #0f0,\n      #0ff,\n      #00f,\n      #f0f,\n      #f00\n    );\n  }\n\n  .sp-color-picker-hex-row {\n    display: grid;\n    grid-template-columns: minmax(0, 1fr) auto;\n    gap: var(--sp-rw-space-2);\n  }\n\n  .sp-color-picker-hex-input,\n  .sp-color-picker-channel-input {\n    font-family: var(--sp-semantic-font-family-mono);\n    font-size: var(--sp-rw-font-12);\n  }\n\n  .sp-color-picker-hex-input input {\n    text-transform: uppercase;\n  }\n\n  .sp-color-picker-channels {\n    display: grid;\n    grid-template-columns: repeat(3, minmax(0, 1fr));\n    gap: var(--sp-rw-space-2);\n  }\n\n  .sp-color-picker-channel {\n    display: grid;\n    gap: var(--sp-rw-space-1);\n    color: var(--sp-rw-fg-tertiary);\n    font-size: var(--sp-rw-font-12);\n    font-weight: var(--sp-rw-font-weight-label);\n    text-align: center;\n  }\n\n  .sp-color-picker-channel-input input {\n    text-align: center;\n  }\n\n  .sp-color-picker-popover[data-entering] {\n    animation: sp-color-picker-enter var(--motion-duration-overlay)\n      var(--motion-ease-standard) both;\n  }\n\n  .sp-color-picker-popover[data-exiting] {\n    animation: sp-color-picker-exit var(--motion-duration-fast)\n      var(--motion-ease-soft-in-out) both;\n  }\n\n  @keyframes sp-color-picker-enter {\n    from {\n      opacity: 0;\n      transform: scale(0.98);\n    }\n  }\n\n  @keyframes sp-color-picker-exit {\n    to {\n      opacity: 0;\n      transform: scale(0.98);\n    }\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    .sp-color-picker-popover[data-entering],\n    .sp-color-picker-popover[data-exiting] {\n      animation-duration: 1ms;\n    }\n  }\n\n  html[data-motion-reduce=\"true\"] .sp-color-picker-popover[data-entering],\n  html[data-motion-reduce=\"true\"] .sp-color-picker-popover[data-exiting],\n  [data-sp-motion=\"reduced\"] .sp-color-picker-popover[data-entering],\n  [data-sp-motion=\"reduced\"] .sp-color-picker-popover[data-exiting] {\n    animation-duration: 1ms;\n  }\n}\n","type":"registry:ui","target":"~/src/siteplane/components/color-picker/color-picker.css"},{"path":"packages/ui/src/components/color-picker/index.ts","content":"export * from \"./color-picker\";\n","type":"registry:ui","target":"~/src/siteplane/components/color-picker/index.ts"}],"type":"registry:ui","docs":"https://ui.siteplane.io/docs-markdown/components/color-picker.md","categories":["Date & color"],"meta":{"avoidWhen":["Avoid when selection is limited to a named palette; use Color Swatch Picker."],"description":"Color editor with saturation, hue, hex and RGB controls.","knownLimitations":["Alpha, locale and popup placement are not exposed.","An invalid hex value falls back to the normalized default, then to black."],"providerNotes":[],"releaseStatus":"stable","searchTerms":["color-picker","color"],"useWhen":["Use when users need to choose an arbitrary color."]}}