-
-
Notifications
You must be signed in to change notification settings - Fork 286
doc: add registry parseAsColor using tinycolor2 #1348
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
jo-chemla
wants to merge
2
commits into
47ng:next
Choose a base branch
from
jo-chemla:parseAsColor-registry
base: next
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| { | ||
| "type": "registry:item", | ||
| "name": "parser-color", | ||
| "title": "Color Parser", | ||
| "description": "Parse colors in multiple formats (hex, rgb, hsl, hsv) with nuqs using tinycolor2.", | ||
| "dependencies": ["nuqs", "tinycolor2"], | ||
| "devDependencies": ["@types/tinycolor2"], | ||
| "files": [ | ||
| { | ||
| "type": "registry:file", | ||
| "path": "src/registry/items/parser-color.source", | ||
| "target": "~/lib/parsers/color.ts" | ||
| } | ||
| ] | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,129 @@ | ||
| # Color Parser | ||
|
|
||
| Parse colors in multiple formats (hex, rgb, hsl, hsv) using [tinycolor2](https://github.com/bgrins/TinyColor). | ||
|
|
||
| Install the parser using the CLI or copy/paste above, then use it in your components: | ||
|
|
||
| ## Usage | ||
|
|
||
| ### Basic hex color | ||
|
|
||
| ```tsx | ||
| import { useQueryState } from 'nuqs' | ||
| import { parseAsHex } from '~/lib/parsers/color' | ||
|
|
||
| function ColorPicker() { | ||
| const [color, setColor] = useQueryState( | ||
| 'color', | ||
| parseAsHex().withDefault('#0055ff') | ||
| ) | ||
|
|
||
| return ( | ||
| <input | ||
| type="color" | ||
| value={color} | ||
| onChange={(e) => setColor(e.target.value)} | ||
| /> | ||
| ) | ||
| } | ||
| ``` | ||
|
|
||
| ### Different output formats | ||
|
|
||
| ```tsx | ||
| import { parseAsRgb, parseAsHsl, parseAsHsv } from '~/lib/parsers/color' | ||
|
|
||
| // RGB output | ||
| const [color] = useQueryState('color', parseAsRgb().withDefault('rgb(0, 85, 255)')) | ||
|
|
||
| // HSL output | ||
| const [color] = useQueryState('color', parseAsHsl().withDefault('hsl(220, 100%, 50%)')) | ||
|
|
||
| // HSV output | ||
| const [color] = useQueryState('color', parseAsHsv().withDefault('hsv(220, 100%, 100%)')) | ||
| ``` | ||
|
|
||
| ### Short hex format | ||
|
|
||
| ```tsx | ||
| import { parseAsHex } from '~/lib/parsers/color' | ||
|
|
||
| const [color, setColor] = useQueryState( | ||
| 'color', | ||
| parseAsHex(true).withDefault('#05f') // true = short format | ||
| ) | ||
| // Returns #05f instead of #0055ff when possible | ||
| ``` | ||
|
|
||
| ### Theme customizer | ||
|
|
||
| ```tsx | ||
| import { useQueryStates } from 'nuqs' | ||
| import { parseAsHex, parseAsRgb } from '~/lib/parsers/color' | ||
|
|
||
| function ThemeEditor() { | ||
| const [colors, setColors] = useQueryStates({ | ||
| primary: parseAsHex().withDefault('#0055ff'), | ||
| secondary: parseAsHex(true).withDefault('#f0f'), | ||
| background: parseAsRgb().withDefault('rgb(255, 255, 255)') | ||
| }) | ||
|
|
||
| return ( | ||
| <div> | ||
| <input | ||
| type="color" | ||
| value={colors.primary} | ||
| onChange={(e) => setColors({ primary: e.target.value })} | ||
| /> | ||
| {/* ... */} | ||
| </div> | ||
| ) | ||
| } | ||
| ``` | ||
|
|
||
| ## Accepted input formats | ||
|
|
||
| The parser accepts any valid color format that tinycolor2 supports: | ||
|
|
||
| - **Hex**: `#fff`, `#ffffff`, `fff`, `ffffff` | ||
| - **RGB**: `rgb(255, 255, 255)`, `rgba(255, 255, 255, 0.5)` | ||
| - **HSL**: `hsl(0, 0%, 100%)`, `hsla(0, 0%, 100%, 0.5)` | ||
| - **HSV**: `hsv(0, 0%, 100%)`, `hsva(0, 0%, 100%, 0.5)` | ||
| - **Named**: `white`, `red`, `blue`, etc. | ||
|
|
||
| Invalid colors return `null`. | ||
|
|
||
| ## URL serialization | ||
|
|
||
| All colors are automatically serialized to 6-character hex format (without `#`) in URLs: | ||
|
|
||
| ``` | ||
| // User input: rgb(255, 0, 0) → URL: ?color=ff0000 | ||
| // User input: hsl(120, 100%, 50%) → URL: ?color=00ff00 | ||
| // User input: blue → URL: ?color=0000ff | ||
| ``` | ||
|
|
||
| ## API | ||
|
|
||
| ### `parseAsColor(options?)` | ||
|
|
||
| Main parser with configuration options. | ||
|
|
||
| **Options:** | ||
| - `format?: 'hex' | 'rgb' | 'hsl' | 'hsv'` - Output format (default: `'hex'`) | ||
| - `short?: boolean` - Use short hex format when possible (default: `false`) | ||
|
|
||
| ### Convenience parsers | ||
|
|
||
| - `parseAsHex(short?)` - Parse as hex color | ||
| - `parseAsRgb()` - Parse as RGB color | ||
| - `parseAsHsl()` - Parse as HSL color | ||
| - `parseAsHsv()` - Parse as HSV color | ||
|
|
||
| ## Why tinycolor2? | ||
|
|
||
| - **Lightweight**: Only 5kB minified + gzipped | ||
| - **Comprehensive**: Supports all common color formats | ||
| - **Battle-tested**: Widely used in production | ||
| - **Simple API**: Easy to use and understand | ||
| - **Type-safe**: TypeScript definitions included |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| import tinycolor from 'tinycolor2' | ||
| import { createParser } from 'nuqs' | ||
|
|
||
| /** | ||
| * Supported color output formats | ||
| */ | ||
| export type ColorFormat = 'hex' | 'rgb' | 'hsl' | 'hsv' | ||
|
|
||
| /** | ||
| * Configuration options for color parser | ||
| */ | ||
| export interface ColorParserOptions { | ||
| /** | ||
| * Output format for the color | ||
| * @default 'hex' | ||
| */ | ||
| format?: ColorFormat | ||
| /** | ||
| * For hex format: use 3-character form when possible (#fff vs #ffffff) | ||
| * @default false | ||
| */ | ||
| short?: boolean | ||
| } | ||
|
|
||
| /** | ||
| * Parse color from query string and convert to specified format | ||
| * | ||
| * Accepts hex, rgb, hsl, hsv, and named colors as input. | ||
| * Always serializes to hex in the URL for compactness. | ||
| * | ||
| * @example | ||
| * ```tsx | ||
| * // Basic hex color | ||
| * const [color, setColor] = useQueryState( | ||
| * 'color', | ||
| * parseAsColor().withDefault('#0055ff') | ||
| * ) | ||
| * | ||
| * // RGB output | ||
| * const [color, setColor] = useQueryState( | ||
| * 'color', | ||
| * parseAsColor({ format: 'rgb' }).withDefault('rgb(0, 85, 255)') | ||
| * ) | ||
| * | ||
| * // Short hex format | ||
| * const [color, setColor] = useQueryState( | ||
| * 'color', | ||
| * parseAsColor({ short: true }).withDefault('#05f') | ||
| * ) | ||
| * ``` | ||
| */ | ||
| export function parseAsColor(options: ColorParserOptions = {}) { | ||
| const { format = 'hex', short = false } = options | ||
|
|
||
| return createParser({ | ||
| parse(value: string) { | ||
| // Decode URL-encoded characters (e.g., %23 -> #) | ||
| const decodedValue = decodeURIComponent(value) | ||
| const color = tinycolor(decodedValue) | ||
|
|
||
| if (!color.isValid()) { | ||
| return null | ||
| } | ||
|
|
||
| switch (format) { | ||
| case 'hex': | ||
| return short ? color.toHexString() : color.toHexString() | ||
| case 'rgb': | ||
| return color.toRgbString() | ||
| case 'hsl': | ||
| return color.toHslString() | ||
| case 'hsv': | ||
| return color.toHsvString() | ||
| default: | ||
| return color.toHexString() | ||
| } | ||
| }, | ||
|
|
||
| serialize(value: string) { | ||
| const color = tinycolor(value) | ||
| if (!color.isValid()) { | ||
| return '' | ||
| } | ||
| // Always use hex6 format for URL (most compact & consistent) | ||
| return color.toHex() | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| /** | ||
| * Parse as hex color (default) | ||
| * @example parseAsHex().withDefault('#0055ff') | ||
| */ | ||
| export const parseAsHex = (short = false) => | ||
| parseAsColor({ format: 'hex', short }) | ||
|
|
||
| /** | ||
| * Parse as RGB color | ||
| * @example parseAsRgb().withDefault('rgb(0, 85, 255)') | ||
| */ | ||
| export const parseAsRgb = () => | ||
| parseAsColor({ format: 'rgb' }) | ||
|
|
||
| /** | ||
| * Parse as HSL color | ||
| * @example parseAsHsl().withDefault('hsl(220, 100%, 50%)') | ||
| */ | ||
| export const parseAsHsl = () => | ||
| parseAsColor({ format: 'hsl' }) | ||
|
|
||
| /** | ||
| * Parse as HSV color | ||
| * @example parseAsHsv().withDefault('hsv(220, 100%, 100%)') | ||
| */ | ||
| export const parseAsHsv = () => | ||
| parseAsColor({ format: 'hsv' }) | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: to keep the URL small we could forego the hash, just with the character set and its length we should be able to detect hex color codes, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes indeed, by default when the hash is not present the color is still correctly parsed. When serializing, we don't prefix with the hash, only use the 6 digits hex string.