@avament-pub/color-picker
v0.2.0
Published
Shared HEX color picker for Avament React apps
Readme
@avament-pub/color-picker
Portable HEX color picker for React apps — panel, popover trigger, or sync field. No i18n or app APIs inside; pass labels and handle onSave / onChange in the host.
| | |
|---|---|
| Package | @avament-pub/color-picker |
| React | ≥ 18 (peer) |
| Dependencies | react-colorful, lucide-react |
| License | MIT |
Table of contents
- Install
- What you get
- Minimal usage
- Popover with custom trigger
- Embed the panel
- Props API
- Utilities
- Exports
- Localization
- Use inside Select
- Troubleshooting
1. Install
npm install @avament-pub/color-pickeryarn add @avament-pub/color-picker
pnpm add @avament-pub/color-pickerRequirements
- React 18+ / React DOM 18+
- Client component in Next.js App Router (
"use client")
The package is self-contained (inline styles + CSS variables). No host Tailwind, no surface-theme / border-theme required.
Optional Next.js:
transpilePackages: ["@avament-pub/color-picker"];2. What you get
| Component | Role |
|---|---|
| ColorPickerPopover | Trigger + popover; draft until Save / Cancel |
| ColorPickerField | Like popover, but onChange is sync (local draft / forms) |
| ColorPickerPanel | Controlled panel only (embed in your dialog/popover) |
| ColorSwatch | Small color chip for lists / labels |
| useColorPicker | Draft / save / cancel state hook |
Normalized save value is #rrggbb (lowercase).
3. Minimal usage
Popover (persist on Save)
"use client";
import { useState } from "react";
import { ColorPickerPopover } from "@avament-pub/color-picker";
export function CategoryColor({ initial }: { initial: string }) {
const [color, setColor] = useState(initial);
return (
<ColorPickerPopover
color={color}
onSave={async (next) => {
await fetch(`/api/color`, {
method: "PATCH",
body: JSON.stringify({ color: next }),
});
setColor(next);
}}
/>
);
}Field (immediate / form draft)
"use client";
import { ColorPickerField } from "@avament-pub/color-picker";
export function ColorField({
value,
onChange,
}: {
value: string;
onChange: (color: string) => void;
}) {
return <ColorPickerField value={value} onChange={onChange} />;
}Swatch only
import { ColorSwatch } from "@avament-pub/color-picker";
<ColorSwatch color="#2b60e6" />4. Popover with custom trigger
<ColorPickerPopover
color={color}
onSave={setColor}
renderTrigger={({ open, color, togglePicker, labels }) => (
<button type="button" onClick={togglePicker} aria-label={labels.editAriaLabel}>
<span
style={{
display: "inline-block",
width: 16,
height: 16,
borderRadius: 4,
background: color,
}}
/>
{open ? "Close" : "Pick color"}
</button>
)}
/>5. Embed the panel
"use client";
import { useState } from "react";
import { ColorPickerPanel, useColorPicker } from "@avament-pub/color-picker";
export function InlinePicker({
color,
onSave,
onClose,
}: {
color: string;
onSave: (c: string) => Promise<void>;
onClose: () => void;
}) {
const {
draft,
draftValid,
pending,
handleDraftChange,
handleCancel,
handleSave,
} = useColorPicker({
color,
open: true,
onSave,
onClose,
});
return (
<ColorPickerPanel
color={draft}
onChange={handleDraftChange}
onCancel={handleCancel}
onSave={handleSave}
saving={pending}
disabled={!draftValid}
/>
);
}6. Props API
ColorPickerPopover
| Prop | Type | Description |
|---|---|---|
| color | string | Current saved color |
| onSave | (color: string) => void \| Promise<void> | Called with #rrggbb on Save |
| disabled | boolean | |
| renderTrigger | (props) => ReactNode | Custom trigger |
| labels | ColorPickerLabels | Button / a11y strings |
| onOpenChange | (open: boolean) => void | Notify host (e.g. keep Select open) |
| side | "top" \| "right" \| "bottom" \| "left" | Popover placement (default "left") |
| align | "start" \| "center" \| "end" | |
| zIndexClassName | string | Default z-[200] |
| className / triggerClassName | string | |
Dismiss: Save, Cancel, outside click, or Escape (discards unsaved draft).
ColorPickerField
Same placement / trigger / labels props as popover, but:
| Prop | Type |
|---|---|
| value | string |
| onChange | (color: string) => void | Sync when user saves in the field flow |
ColorPickerPanel
| Prop | Type |
|---|---|
| color | string | Draft hex |
| onChange | (color: string) => void |
| onCancel / onSave | () => void \| Promise<void> |
| disabled / saving | boolean |
| labels | ColorPickerLabels |
ColorPickerLabels
| Key | Default idea |
|---|---|
| editAriaLabel | Change color |
| pickerAriaLabel | Pick a color |
| hexLabel | HEX |
| cancelLabel | Cancel |
| saveLabel | Save |
7. Utilities
import {
COLOR_PICKER_DEFAULT_HEX,
ensureHexPrefix,
hexColorForPicker,
isValidHexColor,
normalizeHexColor,
} from "@avament-pub/color-picker";
normalizeHexColor("#ABC"); // "#aabbcc" or null
isValidHexColor("#2b60e6"); // true
ensureHexPrefix("2b60e6"); // "#2b60e6"8. Exports
import {
ColorPickerPopover,
ColorPickerField,
ColorPickerPanel,
ColorSwatch,
useColorPicker,
normalizeHexColor,
isValidHexColor,
ensureHexPrefix,
hexColorForPicker,
COLOR_PICKER_DEFAULT_HEX,
} from "@avament-pub/color-picker";9. Localization
<ColorPickerPopover
color={color}
onSave={onSave}
labels={{
editAriaLabel: "Farbe ändern",
cancelLabel: "Abbrechen",
saveLabel: "Speichern",
hexLabel: "HEX",
}}
/>10. Use inside Select
When the picker opens from a Select row action, keep the Select open:
<ColorPickerPopover
color={optionColor}
onSave={saveColor}
onOpenChange={(open) => {
// host: prevent Select from closing while open === true
}}
/>11. Troubleshooting
| Issue | Fix |
|---|---|
| Trigger looks wrong | Override --ava-cp-* CSS vars, or use renderTrigger |
| Invalid save | Only valid hex enables Save; use normalizeHexColor |
| Popover behind modal | Raise zIndex (e.g. 400) |
| Select closes when picking | Wire onOpenChange and ignore outside-click while open |
| Next.js SSR | "use client" on the consumer |
Versioning & publish
Bump package.json version before republishing README-only updates.
