{
  "name": "ColorPicker",
  "source": "src/components/ColorPicker.tsx",
  "propsType": "ColorPickerProps",
  "description": "ColorPicker — a colour chosen by a person: a brand accent, a label's\ncolour, a chart series. A hex field with a swatch button; the button opens\na saturation and brightness area, a hue strip and any preset swatches.\n\nTHE FIELD FIRST\n\nTyping `#0B5FFF` is the fastest way to a known colour and the only one that\nneeds no pointer, so the field is always there and the popover is the\nsecond way in. The two cannot disagree: they are one value.\n\nTHE HUE IS KEPT\n\nHex has no hue for a grey or for black. Dragged to the bottom of the area,\n`#000000` would come back as hue 0 and the strip would jump to red. The\npicker holds its own HSB colour and emits hex from it, taking a new value\nfrom the caller only when its hex differs — so a round trip through the\ncaller's state loses nothing.\n\nWHAT IT DOES NOT DO\n\n  - No alpha. A transparent brand colour renders differently on every\n    ground; ask for a solid one.\n  - No contrast check: the picker cannot know what goes on the colour.\n    When text will sit on it, check the pair and say so with `isInvalid`.",
  "import": "import { ColorPicker } from 'ionbase-ui';",
  "status": "stable",
  "summary": "A colour chosen by a person — a brand accent, a label, a chart series — as a hex field with a swatch button that opens a saturation and brightness area, a hue strip and any preset swatches, taking and returning `#RRGGBB`.",
  "useWhen": [
    "the colour is the user's to choose: a workspace's brand accent, a label's or a tag's colour, a series in a chart they own",
    "any colour should be possible, not only a list — with the likely ones offered as `swatches`",
    "the value is stored and shown back as a colour, not as a name"
  ],
  "useInstead": [
    {
      "when": "the choice is among a few named colours and nothing else is allowed",
      "use": "RadioGroup",
      "why": "an open picker invites a colour the product then has to refuse; radios name each choice and allow no other"
    },
    {
      "when": "the colour marks a status — failed, running, healthy",
      "use": "StatusIndicator",
      "why": "status colours are the system's, each with a word and a shape beside it; a picked colour carries neither, and means nothing to the next reader"
    },
    {
      "when": "the choice is a theme — light, dark, the system's",
      "use": "SegmentedControl",
      "why": "a theme is a named mode with every colour decided; a picker would ask for one colour and leave the rest to chance"
    }
  ],
  "composition": {
    "order": [
      "ColorPicker"
    ],
    "note": "One field. `value` is `#RRGGBB`; `onChange` fires on every step of a drag, `onChangeEnd` once a change is finished — save or recompute there. Offer the likely colours as `swatches`, each with a `label` when it has a name people use. When text will sit on the colour, check the pair yourself and pass `isInvalid` with an `errorMessage` that gives the ratio.",
    "example": "<ColorPicker label=\"Brand colour\" description=\"The Approve button in approval emails.\" value={brand} onChange={setBrand} swatches={[{ value: '#0B5FFF', label: 'Northwind blue' }]} />"
  },
  "variants": {
    "size": {
      "sm": {
        "use": "dense forms, a colour in a table row"
      },
      "md": {
        "use": "the default"
      },
      "lg": {
        "use": "prominent single-field forms"
      }
    }
  },
  "slots": {
    "description": {
      "accepts": "text",
      "note": "where the colour is used, and any rule it must meet; replaced by `errorMessage` while the field is invalid"
    },
    "labels": {
      "accepts": "text",
      "note": "the swatch button's name and the presets' group name"
    }
  },
  "a11y": {
    "requires": [
      "`label`, or an `aria-label` when no visible label is rendered",
      "a `label` on every swatch that has a name people use — \"Northwind blue\" says more than \"vibrant blue\""
    ],
    "guarantees": [
      "the hex is a text field named by the label, typed and committed on Enter or blur; shorthand such as `f00` is accepted as typing and expanded, and anything that is not a colour goes back to the last one",
      "the swatch button is named by `labels.picker`, not by the field's label, and opens a modal dialog named by the field's label, with focus in the area",
      "the area is a two-dimensional slider: the arrow keys move saturation and brightness, Page Up and Down take bigger steps, and each step is announced with the colour's name",
      "the hue strip is a slider named Hue, in the reader's language; in a right-to-left locale it runs from the right and the arrows follow it",
      "presets are one radio group named by `labels.swatches`: the arrow keys move and pick, the chosen one is ringed as well as coloured, and a colour off every preset leaves none checked",
      "a preset with no `label` is named by its colour in the reader's language",
      "Tab moves from the area to the hue strip to the presets and stays in the dialog; Escape or a click outside closes it and focus returns to the button",
      "the hue is kept at a grey or black: dragged to black and back up, a blue comes back blue",
      "a caller that does not take the change keeps its value, and the field shows it",
      "in forced colours the chip, the swatches and the thumbs keep their colours; their edges and the chosen ring take the system's",
      "a name, `rgb()` or shorthand passed as a prop throws with the prop's name, rather than rendering an empty field",
      "`name` posts the hex with the form"
    ],
    "notes": [
      "No alpha: a translucent colour looks different on every ground it lands on. Ask for a solid one.",
      "No contrast check: the picker cannot know what goes on the colour. When text sits on it, measure the pair and say so with `isInvalid`.",
      "The colour names are React Aria's, and follow the locale."
    ]
  },
  "antiPatterns": [
    {
      "dont": "`value=\"red\"`, `value=\"#f00\"` or `value=\"rgb(255, 0, 0)\"`",
      "do": "`value=\"#FF0000\"`",
      "why": "a value the picker cannot show as it came would come back different, and the form would read as edited the first time it was saved"
    },
    {
      "dont": "saving on `onChange`",
      "do": "`onChangeEnd={(hex) => save(hex)}`",
      "why": "`onChange` fires on every pixel of a drag — dozens of saves for one gesture, answered out of order"
    },
    {
      "dont": "a brand colour taken with no check when white text will sit on it",
      "do": "measure the pair, and `isInvalid` with \"Too light for white text: 2.1:1. Needs 4.5:1.\"",
      "why": "the picker offers every colour, and half of them make the text on them unreadable"
    },
    {
      "dont": "a ColorPicker for an interface colour — a button's, a status's",
      "do": "the token",
      "why": "interface colours are chosen once, in the design system, and checked there; a picker hands that decision to whoever has the settings page open"
    }
  ],
  "stylesheet": "src/styles/color-picker.css",
  "tokens": [
    "--border-focus",
    "--border-strong",
    "--border-subtle",
    "--border-width-default",
    "--border-width-thick",
    "--ion-color-picker-panel",
    "--ion-color-picker-swatch",
    "--ion-color-picker-thumb",
    "--ion-color-picker-thumb-size",
    "--ion-input-icon-size",
    "--ion-shadow-shadow-xs",
    "--radius-2xs",
    "--radius-full",
    "--radius-sm",
    "--radius-xs",
    "--spacing-12",
    "--spacing-2",
    "--spacing-20",
    "--spacing-24",
    "--spacing-40",
    "--spacing-6",
    "--spacing-8",
    "--surface-default",
    "--surface-raised",
    "--text-default"
  ],
  "props": {
    "label": {
      "type": "React.ReactNode",
      "required": false,
      "origin": "own",
      "description": "Field label. Required for a usable control — see `a11y.requires`."
    },
    "aria-label": {
      "type": "string | undefined",
      "required": false,
      "origin": "own",
      "description": "Names the field when there is no visible `label`."
    },
    "description": {
      "type": "React.ReactNode",
      "required": false,
      "origin": "own",
      "description": "Helper text below the field — where the colour is used, and any rule it must meet."
    },
    "errorMessage": {
      "type": "React.ReactNode",
      "required": false,
      "origin": "own",
      "description": "Replaces the helper text while the field is invalid."
    },
    "isInvalid": {
      "type": "boolean | undefined",
      "required": false,
      "origin": "own"
    },
    "isDisabled": {
      "type": "boolean | undefined",
      "required": false,
      "origin": "own"
    },
    "isReadOnly": {
      "type": "boolean | undefined",
      "required": false,
      "origin": "own"
    },
    "isRequired": {
      "type": "boolean | undefined",
      "required": false,
      "origin": "own"
    },
    "size": {
      "type": "ColorPickerSize | undefined",
      "required": false,
      "origin": "own",
      "description": "Matches Input's `Size` variant: Small, Medium, Large.",
      "values": [
        "sm",
        "md",
        "lg"
      ]
    },
    "value": {
      "type": "string | null | undefined",
      "required": false,
      "origin": "own",
      "description": "The colour as `#RRGGBB`. `null` means none is chosen. A malformed value\nthrows rather than rendering an empty field."
    },
    "defaultValue": {
      "type": "string | undefined",
      "required": false,
      "origin": "own",
      "description": "The initial colour, for an uncontrolled picker."
    },
    "onChange": {
      "type": "((value: HexColor | null) => void) | undefined",
      "required": false,
      "origin": "own",
      "description": "Fires with `#RRGGBB` (upper case) on every change — each step of a drag\nincluded — or `null` when the field is cleared."
    },
    "onChangeEnd": {
      "type": "((value: HexColor | null) => void) | undefined",
      "required": false,
      "origin": "own",
      "description": "Fires once a change is finished: a drag let go, a key step, a swatch\npicked, the hex typed and committed. Save or preview here."
    },
    "swatches": {
      "type": "readonly (string | ColorPickerSwatch)[] | undefined",
      "required": false,
      "origin": "own",
      "description": "Preset colours, offered under the picker as one radio group."
    },
    "name": {
      "type": "string | undefined",
      "required": false,
      "origin": "own",
      "description": "Posts the hex under this name, for an uncontrolled form."
    },
    "labels": {
      "type": "ColorPickerLabels | undefined",
      "required": false,
      "origin": "own",
      "description": "The swatch button's name and the presets' group name."
    },
    "className": {
      "type": "string | undefined",
      "required": false,
      "origin": "own"
    },
    "wrapperClassName": {
      "type": "string | undefined",
      "required": false,
      "origin": "own"
    },
    "id": {
      "type": "string | undefined",
      "required": false,
      "origin": "own"
    }
  },
  "propCounts": {
    "own": 19,
    "aria": 0,
    "dom": 0,
    "other": 0
  }
}
