HextaUI

BêtaConstruit sur l’UI SDK 0.1.0-beta.1 de Vercel. Son API peut changer.

Color picker

Un sélecteur de couleur sur l’UI SDK de Vercel, avec une zone de saturation, des curseurs de teinte et d’alpha, des couleurs CSS saisies, des préréglages et une pipette.

Dépend de
  • @tabler/icons-react
  • @ui-sdk/core
  • @ui-sdk/react
"use client"

import * as React from "react"

import {
  ColorPicker,
  ColorPickerArea,
  ColorPickerContent,
  ColorPickerEyeDropper,
  ColorPickerInput,
  ColorPickerPopover,
  ColorPickerSlider,
  ColorPickerSwatch,
  ColorPickerSwatches,
  ColorPickerSwatchItem,
  ColorPickerTrigger,
} from "@/components/ui/ui-sdk-color-picker"

const presets = [
  "#0f172a",
  "#ef4444",
  "#f97316",
  "#eab308",
  "#22c55e",
  "#14b8a6",
  "#0ea5e9",
  "#6366f1",
  "#a855f7",
  "#ec4899",
]

function UiSdkColorPickerDemo() {
  const [value, setValue] = React.useState("#6366f1")

  return (
    <ColorPicker
      aria-label="Accent color"
      value={value}
      onValueChange={setValue}
    >
      <ColorPickerPopover>
        <ColorPickerTrigger aria-label={`Accent color, ${value}`}>
          <ColorPickerSwatch />
          <span className="font-mono text-xs uppercase tabular-nums">
            {value}
          </span>
        </ColorPickerTrigger>
        <ColorPickerContent>
          <ColorPickerArea />
          <ColorPickerSlider channel="hue" />
          <div className="flex items-center gap-2">
            <ColorPickerInput aria-label="Hex" />
            <ColorPickerEyeDropper aria-label="Pick a color from the screen" />
          </div>
          <ColorPickerSwatches aria-label="Presets">
            {presets.map((preset) => (
              <ColorPickerSwatchItem key={preset} value={preset} />
            ))}
          </ColorPickerSwatches>
        </ColorPickerContent>
      </ColorPickerPopover>
    </ColorPicker>
  )
}

export { UiSdkColorPickerDemo }
pnpm dlx shadcn@latest add https://hextaui.com/r/ui-sdk-color-picker.json

Ajoute le composant, les tokens de thème HextaUI et les composants HextaUI dont il dépend.

UI SDK ajoute un peu de CSS dans une couche de cascade nommée ui-sdk. Déclarez cette couche en haut de votre feuille de style globale, au-dessus de l’import Tailwind, pour que vos classes l’emportent.

@layer ui-sdk;

@import "tailwindcss";
import {
  ColorPicker,
  ColorPickerArea,
  ColorPickerInput,
  ColorPickerSlider,
  ColorPickerSwatch,
} from "@/components/ui/ui-sdk-color-picker"
<ColorPicker aria-label="Brand color" defaultValue="#0ea5e9">
  <ColorPickerArea />
  <ColorPickerSlider channel="hue" />
  <ColorPickerSwatch />
  <ColorPickerInput aria-label="Hex" />
</ColorPicker>

La valeur est une chaîne de couleur CSS. Toutes les parties modifient la même couleur, utilisez donc seulement celles dont vous avez besoin. ColorPickerArea et ColorPickerSlider affichent leur curseur pour vous. Nommez le sélecteur avec aria-label ou avec le libellé d’un champ qui l’entoure.

ColorPicker
├── ColorPickerArea
│   ├── ColorPickerAreaCanvas
│   └── ColorPickerAreaThumb
├── ColorPickerSlider
│   └── ColorPickerSliderThumb
├── ColorPickerSwatch
├── ColorPickerInput
├── ColorPickerChannelInput
├── ColorPickerEyeDropper
├── ColorPickerSwatches
│   └── ColorPickerSwatchItem
└── ColorPickerPopover
    ├── ColorPickerTrigger
    └── ColorPickerContent

Affichez les parties sur place, sans popover, pour une page de réglages ou une barre latérale.

import {
  ColorPicker,
  ColorPickerArea,
  ColorPickerInput,
  ColorPickerSlider,
  ColorPickerSwatch,
} from "@/components/ui/ui-sdk-color-picker"

function UiSdkColorPickerInline() {
  return (
    <ColorPicker aria-label="Brand color" defaultValue="#0ea5e9">
      <ColorPickerArea />
      <ColorPickerSlider channel="hue" />
      <div className="flex items-center gap-2">
        <ColorPickerSwatch />
        <ColorPickerInput aria-label="Hex" />
      </div>
    </ColorPicker>
  )
}

export { UiSdkColorPickerInline }

Un curseur avec channel="alpha" modifie l’opacité. Sa piste et l’échantillon dessinent un damier sous la couleur, pour qu’une couleur transparente paraisse transparente.

import {
  ColorPicker,
  ColorPickerArea,
  ColorPickerInput,
  ColorPickerSlider,
  ColorPickerSwatch,
} from "@/components/ui/ui-sdk-color-picker"

function UiSdkColorPickerAlpha() {
  return (
    <ColorPicker
      aria-label="Overlay color"
      space="hsl"
      defaultValue="hsl(262 83% 58% / 0.6)"
    >
      <ColorPickerArea />
      <ColorPickerSlider channel="hue" />
      <ColorPickerSlider channel="alpha" />
      <div className="flex items-center gap-2">
        <ColorPickerSwatch />
        <ColorPickerInput aria-label="HSL" />
      </div>
    </ColorPicker>
  )
}

export { UiSdkColorPickerAlpha }

Avec space="oklch", ajoutez ColorPickerAreaCanvas pour peindre la zone. Les couleurs hors du gamut de l’écran restent vides, et un curseur glissé là se cale sur la couleur la plus proche à l’intérieur quand vous lâchez.

import {
  ColorPicker,
  ColorPickerArea,
  ColorPickerAreaCanvas,
  ColorPickerAreaThumb,
  ColorPickerInput,
  ColorPickerSlider,
  ColorPickerSwatch,
} from "@/components/ui/ui-sdk-color-picker"

function UiSdkColorPickerOklch() {
  return (
    <ColorPicker
      aria-label="Accent"
      space="oklch"
      defaultValue="oklch(0.62 0.19 259)"
    >
      <ColorPickerArea>
        <ColorPickerAreaCanvas />
        <ColorPickerAreaThumb />
      </ColorPickerArea>
      <ColorPickerSlider channel="hue" />
      <div className="flex items-center gap-2">
        <ColorPickerSwatch />
        <ColorPickerInput aria-label="OKLCH" />
      </div>
    </ColorPicker>
  )
}

export { UiSdkColorPickerOklch }

ColorPickerSwatches est un seul arrêt de tabulation avec les flèches entre les préréglages, et un préréglage reste sélectionné quelle que soit l’écriture de la valeur. ColorPickerChannelInput modifie un canal sous forme de nombre. La pipette se masque dans les navigateurs qui ne peuvent pas prélever à l’écran.

import {
  ColorPicker,
  ColorPickerChannelInput,
  ColorPickerEyeDropper,
  ColorPickerSwatch,
  ColorPickerSwatches,
  ColorPickerSwatchItem,
} from "@/components/ui/ui-sdk-color-picker"

const presets = [
  "#ef4444",
  "#f97316",
  "#eab308",
  "#22c55e",
  "#0ea5e9",
  "#6366f1",
  "#a855f7",
  "#ec4899",
]

const channels = [
  { channel: "red", label: "R" },
  { channel: "green", label: "G" },
  { channel: "blue", label: "B" },
] as const

function UiSdkColorPickerChannels() {
  return (
    <ColorPicker
      aria-label="Label color"
      space="srgb"
      format="hex"
      defaultValue="#0ea5e9"
    >
      <ColorPickerSwatches aria-label="Presets">
        {presets.map((preset) => (
          <ColorPickerSwatchItem key={preset} value={preset} />
        ))}
      </ColorPickerSwatches>
      <div className="flex items-end gap-2">
        <ColorPickerSwatch />
        {channels.map(({ channel, label }) => (
          <label
            key={channel}
            className="flex min-w-0 flex-1 flex-col gap-1 text-xs text-muted-foreground"
          >
            {label}
            <ColorPickerChannelInput channel={channel} />
          </label>
        ))}
        <ColorPickerEyeDropper aria-label="Pick a color from the screen" />
      </div>
    </ColorPicker>
  )
}

export { UiSdkColorPickerChannels }

Mettez orientation="vertical" sur un curseur pour le placer à côté de la zone.

import {
  ColorPicker,
  ColorPickerArea,
  ColorPickerSlider,
} from "@/components/ui/ui-sdk-color-picker"

function UiSdkColorPickerVertical() {
  return (
    <ColorPicker aria-label="Highlight" space="hsl" defaultValue="#f97316">
      <div className="flex gap-3">
        <div className="min-w-0 flex-1">
          <ColorPickerArea />
        </div>
        <ColorPickerSlider channel="hue" orientation="vertical" />
        <ColorPickerSlider channel="alpha" orientation="vertical" />
      </div>
    </ColorPicker>
  )
}

export { UiSdkColorPickerVertical }

onValueChange s’exécute à chaque pas d’un glissement et onValueCommit une fois à la fin, enregistrez donc là. Un contrôleur issu de createColorPickerController() règle la couleur depuis n’importe où.

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import {
  ColorPicker,
  ColorPickerArea,
  ColorPickerInput,
  ColorPickerSlider,
  createColorPickerController,
} from "@/components/ui/ui-sdk-color-picker"

function UiSdkColorPickerControlled() {
  const [picker] = React.useState(() => createColorPickerController())
  const [value, setValue] = React.useState("#0ea5e9")
  const [saved, setSaved] = React.useState("#0ea5e9")

  return (
    <div className="flex w-full max-w-64 flex-col gap-3">
      <ColorPicker
        aria-label="Theme color"
        value={value}
        onValueChange={setValue}
        onValueCommit={setSaved}
        controller={picker}
      >
        <ColorPickerArea />
        <ColorPickerSlider channel="hue" />
        <ColorPickerInput aria-label="Hex" />
      </ColorPicker>
      <div className="flex items-center justify-between gap-2 text-sm">
        <p className="text-muted-foreground">
          Saved <span className="font-mono text-foreground">{saved}</span>
        </p>
        <Button
          variant="outline"
          size="sm"
          onClick={() => picker.setValue("#663399")}
        >
          Use purple
        </Button>
      </div>
    </div>
  )
}

export { UiSdkColorPickerControlled }

Un input caché envoie la valeur sous name, et la réinitialisation du formulaire revient à defaultValue.

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import {
  ColorPicker,
  ColorPickerArea,
  ColorPickerSlider,
  ColorPickerSwatch,
} from "@/components/ui/ui-sdk-color-picker"

function UiSdkColorPickerForm() {
  const [submitted, setSubmitted] = React.useState<string | null>(null)

  return (
    <form
      className="flex w-full max-w-64 flex-col gap-3"
      onSubmit={(event) => {
        event.preventDefault()
        setSubmitted(String(new FormData(event.currentTarget).get("brand")))
      }}
      onReset={() => setSubmitted(null)}
    >
      <ColorPicker aria-label="Brand color" name="brand" defaultValue="#f97316">
        <ColorPickerArea />
        <div className="flex items-center gap-2">
          <ColorPickerSwatch />
          <div className="min-w-0 flex-1">
            <ColorPickerSlider channel="hue" />
          </div>
        </div>
      </ColorPicker>
      <div className="flex gap-2">
        <Button type="submit" size="sm">
          Save
        </Button>
        <Button type="reset" variant="outline" size="sm">
          Reset
        </Button>
      </div>
      <p role="status" className="text-sm text-muted-foreground">
        {submitted ? (
          <>
            Submitted brand=
            <span className="font-mono text-foreground">{submitted}</span>
          </>
        ) : (
          "Nothing submitted yet."
        )}
      </p>
    </form>
  )
}

export { UiSdkColorPickerForm }

Un sélecteur désactivé ne prend pas le focus, ne change pas et n’envoie rien.

import {
  ColorPicker,
  ColorPickerArea,
  ColorPickerInput,
  ColorPickerSlider,
} from "@/components/ui/ui-sdk-color-picker"

function UiSdkColorPickerDisabled() {
  return (
    <ColorPicker aria-label="Brand color" defaultValue="#0ea5e9" disabled>
      <ColorPickerArea />
      <ColorPickerSlider channel="hue" />
      <ColorPickerInput aria-label="Hex" />
    </ColorPicker>
  )
}

export { UiSdkColorPickerDisabled }

Avec readOnly, les curseurs restent focalisables et la valeur est envoyée, mais glisser, les touches et la saisie ne touchent pas à la couleur.

import {
  ColorPicker,
  ColorPickerArea,
  ColorPickerInput,
  ColorPickerSlider,
  ColorPickerSwatch,
} from "@/components/ui/ui-sdk-color-picker"

function UiSdkColorPickerReadOnly() {
  return (
    <ColorPicker aria-label="Theme color" defaultValue="#22c55e" readOnly>
      <ColorPickerArea />
      <ColorPickerSlider channel="hue" />
      <div className="flex items-center gap-2">
        <ColorPickerSwatch />
        <ColorPickerInput aria-label="Hex" />
      </div>
    </ColorPicker>
  )
}

export { UiSdkColorPickerReadOnly }

UI SDK lit la direction de la page, donc la zone, les curseurs et les flèches sont inversés. Les inputs choisissent leur propre direction, ainsi une valeur hex se lit toujours de gauche à droite.

import {
  ColorPicker,
  ColorPickerArea,
  ColorPickerInput,
  ColorPickerSlider,
  ColorPickerSwatch,
} from "@/components/ui/ui-sdk-color-picker"

function UiSdkColorPickerRtl() {
  return (
    <div dir="rtl" className="flex w-full justify-center">
      <ColorPicker aria-label="لون العلامة" defaultValue="#ec4899">
        <ColorPickerArea />
        <ColorPickerSlider channel="hue" />
        <div className="flex items-center gap-2">
          <ColorPickerSwatch />
          <ColorPickerInput aria-label="سداسي عشري" />
        </div>
      </ColorPicker>
    </div>
  )
}

export { UiSdkColorPickerRtl }
ToucheAction
←→↑↓Déplace un curseur d’un pas. Sur la zone, gauche et droite déplacent en largeur, haut et bas en hauteur.
Shift + arrowDéplace de dix pas.
Page UpPage DownDéplace d’un dixième de la plage, vers le haut de la zone ou le long d’un curseur.
HomeEndVa au début ou à la fin.
EnterLit le texte de l’input, ou choisit le préréglage qui a le focus.
EscRemet le texte de l’input à la valeur, ou ferme le popover.

Aucun motif WAI-ARIA ne couvre un sélecteur de couleur, donc UI SDK le construit à partir de ceux qui existent. Le sélecteur est un groupe. Le curseur de la zone et chaque curseur de slider sont des sliders, et celui de la zone nomme ses deux canaux, comme « Saturation 76 %, Luminosité 96 % ». Les préréglages forment une listbox, et chaque préréglage et l’échantillon sont nommés par leur couleur, comme « bleu vif ».

Le déclencheur de l’exemple avec popover donne la couleur actuelle dans son nom. Les curseurs grossissent pendant le glissement seulement si l’animation est autorisée, et le damier, les anneaux et les contours de focus passent aux couleurs système en mode couleurs forcées.

Chaque partie enveloppe la partie UI SDK du même nom et accepte toutes ses props, y compris render et un className qui peut être une fonction de son état.

PropTypePar défaut
value
string–
defaultValue
string"black"
onValueChangeS’exécute à chaque pas d’un glissement, chaque touche, couleur saisie ou préréglage.
(value: string, details: ChangeDetails) => void–
onValueCommitS’exécute une fois à la fin d’un glissement, d’une touche ou d’une couleur saisie.
(value: string, details: ChangeDetails) => void–
spaceLes canaux que modifient les parties.
"hsv" | "hsl" | "srgb" | "oklch""hsv"
formatComment la valeur est écrite. Hex pour hsv, sinon la syntaxe CSS propre à l’espace.
ColorFormat–
gamut
"srgb" | "p3" | false"srgb"
disabled
booleanfalse
readOnly
booleanfalse
required
booleanfalse
name
string–
form
string–
controllerIssu de createColorPickerController().
ColorPickerController–
render
ReactElement | (props, state) => ReactElement<div>
AttributDescription
data-slot="color-picker"Ciblez la racine en CSS.
data-disabledPrésent lorsque l’élément est désactivé.
data-readonlyPrésent en lecture seule.
data-requiredPrésent lorsqu’elle est requise.
data-out-of-gamutPrésent sur chaque partie tant qu’un glissement tient une couleur hors gamut.
--color-picker-colorLa couleur en CSS. Les curseurs et échantillons en sont remplis.
PropTypePar défaut
xLe canal en largeur. Saturation, rouge ou chroma, selon l’espace.
ColorChannel–
yLe canal en hauteur. Luminosité, clarté ou vert, selon l’espace.
ColorChannel–
children
ReactNode<ColorPickerAreaThumb />
render
ReactElement | (props, state) => ReactElement<div>
AttributDescription
data-slot="color-picker-area"Pour cibler la zone en CSS.
--color-area-backgroundLes dégradés qui dessinent la zone. Non défini quand un canvas la peint.
--color-picker-radiusLe rayon des coins de la zone. Défini par ColorPickerContent pour des coins concentriques.
AttributDescription
data-slot="color-picker-area-thumb"Pour cibler le bouton du curseur en CSS.
--area-xLa position du curseur en largeur dans la zone, de 0 à 1.
--area-yLa position du curseur en hauteur dans la zone, de 0 à 1.
PropTypePar défaut
channelPar défaut la teinte, ou le bleu en sRGB.
ColorChannel–
orientation
"horizontal" | "vertical""horizontal"
children
ReactNode<ColorPickerSliderThumb />
render
ReactElement | (props, state) => ReactElement<div>
AttributDescription
data-slot="color-picker-slider"Pour cibler le curseur en CSS.
data-channelLe canal modifié, comme la teinte ou l’alpha.
data-orientationhorizontal ou vertical.
--color-slider-backgroundUn dégradé le long du canal.
PropTypePar défaut
channelLe canal à modifier, dans les unités où on le lit.
ColorChannel–
render
ReactElement | (props, state) => ReactElement<input>
PropTypePar défaut
valueRequis. La couleur du préréglage.
string–
render
ReactElement | (props, state) => ReactElement<button>
AttributDescription
data-slot="color-picker-swatch-item"Pour cibler les préréglages en CSS.
data-selectedPrésent tant que la valeur est la couleur de ce préréglage.
PropTypePar défaut
children
ReactNode<IconColorPicker />
render
ReactElement | (props, state) => ReactElement<button>
AttributDescription
data-unsupportedPrésent quand le navigateur n’a pas d’EyeDropper. Le bouton est alors masqué.

ColorPickerPopover, ColorPickerTrigger et ColorPickerContent enveloppent le popover d’UI SDK. Le contenu s’ouvre dans la top layer du navigateur, il n’a donc pas besoin de portail.

PropTypePar défaut
side
"top" | "bottom" | "left" | "right" | "start" | "end""bottom"
align
"start" | "center" | "end""start"
sideOffset
number6
AttributDescription
data-slot="popover-content"Posé par UI SDK sur le popup. Ciblez-le en CSS.
data-starting-stylePrésent pendant l'animation d'entrée de la popup.
data-ending-stylePrésent pendant l'animation de sortie de la popup.