Pick a colour
Brand or label colour from swatches or a picker.
Built with
- Button
- ColorInput
- ColorPicker
- Dialog
- Swatch
- SwatchPicker
Team colours
Used on the calendar, org chart and reports.
- Engineering42 people
- Design11 people
- People Ops8 people
pick-a-colour.tsx
"use client";
import { useState } from "react";
import { LuPalette } from "react-icons/lu";
import { Button, ColorInput, ColorPicker, Dialog, Swatch, SwatchPicker } from "@nexera-ui/react";
// Label colours are user data, so they are stored as hex values rather than theme tokens.
const PALETTE: [name: string, hex: string][] = [
["Emerald 600", "#04855D"],
["Blue 600", "#2563EB"],
["Amber 600", "#D97706"],
["Violet 600", "#7C3AED"],
["Cyan 700", "#0E7490"],
["Pink 600", "#DB2777"],
];
const PRESETS = PALETTE.map(([name, hex]) => ({ name, color: hex }));
const TEAMS = [
{ id: "engineering", name: "Engineering", hex: "#2563EB", people: 42 },
{ id: "design", name: "Design", hex: "#7C3AED", people: 11 },
{ id: "people-ops", name: "People Ops", hex: "#04855D", people: 8 },
];
/** Pick a colour: change a team's label colour from preset swatches or a custom picker, in a dialog. */
export default function PickAColour() {
const [teams, setTeams] = useState(TEAMS);
const [editing, setEditing] = useState<(typeof TEAMS)[number] | null>(null);
const [draft, setDraft] = useState("#2563EB");
return (
<div className="@container bg-page px-4 py-8 @3xl:px-8">
<section
aria-labelledby="labels-title"
className="mx-auto grid max-w-xl gap-5 rounded-xl border border-default bg-surface p-5 shadow-sm @3xl:p-6"
>
<header className="grid gap-1">
<h2 id="labels-title" className="text-heading-h3 text-primary">
Team colours
</h2>
<p className="text-body-default text-secondary">
Used on the calendar, org chart and reports.
</p>
</header>
<ul className="grid divide-y divide-subtle">
{teams.map((team) => (
<li key={team.id} className="flex flex-wrap items-center gap-3 py-3">
<Swatch color={team.hex} colorName={`${team.name} colour`} size="md" />
<div className="grid min-w-0 flex-1">
<span className="text-body-strong text-primary">{team.name}</span>
<span className="text-body-small text-secondary">{team.people} people</span>
</div>
<Button
variant="ghost"
leadingIcon={<LuPalette />}
aria-label={`Change colour of ${team.name}`}
onPress={() => {
setDraft(team.hex);
setEditing(team);
}}
>
Change
</Button>
</li>
))}
</ul>
</section>
<Dialog
title={`Colour for ${editing?.name ?? "team"}`}
body="Pick a preset, or enter a custom colour with enough contrast against white text."
isOpen={editing !== null}
onOpenChange={(open) => {
if (!open) setEditing(null);
}}
primaryLabel="Save colour"
onPrimaryAction={() => {
setTeams((list) => list.map((t) => (t.id === editing?.id ? { ...t, hex: draft } : t)));
}}
secondaryLabel="Cancel"
>
<div className="grid gap-4">
<SwatchPicker
aria-label="Preset colours"
value={draft}
onChange={(c) => {
setDraft(c.toString("hex"));
}}
>
{PALETTE.map(([name, hex]) => (
<Swatch key={hex} color={hex} colorName={name} size="md" />
))}
</SwatchPicker>
<ColorInput
label="Custom colour"
value={draft}
onChange={(c) => {
if (c) setDraft(c.toString("hex"));
}}
placeholder="#04855D"
picker={
<ColorPicker
aria-label="Custom colour"
presets={PRESETS}
presetsLabel="Northwind palette"
/>
}
/>
</div>
</Dialog>
</div>
);
}
More forms and data entry blocks
- Contact formName, email, topic and message with validation.
- Add a personCreate a record in a modal.
- Multi-step formLong forms split into steps you can go back to.
- Address and phoneCountry, address lookup and phone with dialling code.
- File uploadDrag files in and watch each one upload.
- Date range pickerChoose a start and end date on a calendar.
- Search with suggestionsType ahead and pick from matching results.
- Terms and consentScrollable terms with a required checkbox.
- Choose an optionPick one option from a short list in a dialog.