Skip to content

feat(color-picker): export formatColor utility#823

Open
paanSinghCoder wants to merge 1 commit into
mainfrom
feat/color-to-hex-util
Open

feat(color-picker): export formatColor utility#823
paanSinghCoder wants to merge 1 commit into
mainfrom
feat/color-to-hex-util

Conversation

@paanSinghCoder
Copy link
Copy Markdown
Contributor

@paanSinghCoder paanSinghCoder commented Jun 1, 2026

Summary

Exports a formatColor utility from @raystack/apsara for consumers that need any format from apsara's OKLCH-authored tokens — hex for chart libraries, rgb/hsl for legacy APIs, or normalizing arbitrary input back to the OKLCH design-system format.

import { formatColor } from '@raystack/apsara';

formatColor('oklch(0.5438 0.191 267.01)', 'hex')   // '#3E63DD'
formatColor('oklch(0.7 0.32 30)', 'hex')           // '#FF5843'  (gamut-mapped)
formatColor('red', 'rgb')                          // 'rgb(255, 0, 0)'
formatColor('rgba(255, 0, 0, 0.5)', 'hsl')         // 'hsla(0, 100%, 50%, 0.5)'
formatColor('#FF0000', 'oklch')                    // 'oklch(0.628 0.2577 29.23)'
formatColor('not a color', 'hex')                  // null

Contract

  • Accepts any CSS color string (oklch, rgb, hsl, hex, named, transparent)
  • format is 'hex' | 'rgb' | 'hsl' | 'oklch' — same set the picker supports
  • sRGB-bound outputs (hex/rgb/hsl): wide-gamut OKLCH is gamut-mapped via culori's toGamut — chroma reduced, lightness/hue preserved. No per-channel-clip hue distortion.
  • OKLCH output: full color preserved; matches the design system's token format (4-decimal L/C, 2-decimal H, hue pinned to 0 when achromatic).
  • Hex is uppercase; 8-digit when alpha < 1. Translucent rgb/hsl/oklch produce rgba() / hsla() / oklch(... / A).
  • Returns null for unparseable input rather than throwing.

Test plan

  • 9 new unit tests in utils.test.ts covering all four formats + alpha + gamut-map + null fallback
  • pnpm test components/color-picker — 47/47 passing
  • tsc --noEmit clean on raystack and apps/www
  • biome check clean on touched files
  • Smoke-imported from built dist/index.cjs — all four formats return expected values, no stale colorToHex export

Docs added in the Utilities section of the Color Picker page.

🤖 Generated with Claude Code

@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 1, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
apsara Ready Ready Preview, Comment Jun 1, 2026 5:44am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 1, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a new exported utility formatColor(value, format) that parses CSS color strings, returns null for unparseable input, serializes OKLCH directly, and for hex/rgb/hsl gamut-maps wide-gamut OKLCH into sRGB before formatting. Hex output is uppercased and uses 8-digit hex when alpha < 1; translucent outputs use rgba/hsla or an okLCH alpha tail. Changes include implementation in utils.ts, re-export in index.ts, unit tests, and docs.

Suggested reviewers

  • rohanchkrabrty
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The PR title accurately describes the main change: exporting a formatColor utility from the color-picker component. It is concise, clear, and directly reflects the primary objective of the changeset.
Description check ✅ Passed The PR description is comprehensive and directly related to the changeset. It explains the formatColor utility's purpose, provides clear usage examples, documents the contract and behavior (including gamut-mapping), and references the test plan and validation steps performed.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
packages/raystack/components/color-picker/utils.ts (1)

31-37: 💤 Low value

colorToHex looks correct. Parse guard, alpha-aware 6/8-digit formatting (?? 1 correctly handles the no-alpha case from parse), gamut-mapping, and null fallback are all sound. culori's toGamut short-circuits already-in-gamut inputs, so opaque sRGB/named-color cases stay exact.

Optional: the alpha === 1 ? formatHex : formatHex8 + toUpperCase() tail duplicates the hex branch of getColorString (Lines 113-116). A tiny shared helper would keep the two hex paths from drifting (note the guard there is clipped.alpha === 1 vs (safe.alpha ?? 1) === 1 here).

♻️ Optional shared helper
+const toUpperHex = (color: { alpha?: number }) =>
+  ((color.alpha ?? 1) === 1 ? formatHex(color) : formatHex8(color)).toUpperCase();
+
 export const colorToHex = (value: string): string | null => {
   const parsed = parse(value);
   if (!parsed) return null;
-  const safe = toSrgb(parsed);
-  const hex = (safe.alpha ?? 1) === 1 ? formatHex(safe) : formatHex8(safe);
-  return hex.toUpperCase();
+  return toUpperHex(toSrgb(parsed));
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/raystack/components/color-picker/utils.ts` around lines 31 - 37,
There’s duplicated logic for choosing 6- vs 8-digit hex and uppercasing in
colorToHex and getColorString; extract that into a small shared helper (e.g.,
formatHexWithAlpha or formatHexForColor) that accepts a culori color object (or
its clipped/safe variant), checks alpha using the same guard ((color.alpha ?? 1)
or clipped.alpha), returns formatHex or formatHex8 and uppercases the result,
then replace the hex branch in colorToHex and the corresponding branch in
getColorString to call this helper.
apps/www/src/content/docs/components/color-picker/index.mdx (1)

87-114: 💤 Low value

Docs match colorToHex; wide-gamut example values are illustrative.

colorToHex follows the documented contract: culori.parse + toGamut mapping, uppercase #RRGGBB for alpha===1 and #RRGGBBAA otherwise, and null for unparseable input.

Keep in mind the exact wide-gamut example outputs (#3E63DD, #FF5843) aren’t pinned by tests—oklch(0.7 0.32 30) is only asserted as a valid 6-digit hex and not '#FF0000'. With culori pinned to 4.0.2, this should only matter if the dependency is upgraded.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/www/src/content/docs/components/color-picker/index.mdx` around lines 87
- 114, The docs/examples assert specific outputs for colorToHex but the
implementation may produce different gamut-mapped results depending on culori
version; update the documentation to clarify that the provided wide-gamut
example hex values (e.g., '`#3E63DD`', '`#FF5843`') are illustrative and not
guaranteed by tests; reference the colorToHex implementation that uses
culori.parse and toGamut and explicitly state that colorToHex returns uppercase
'`#RRGGBB`' for alpha===1, '`#RRGGBBAA`' otherwise, and null for unparseable input,
noting that exact gamut-mapped outputs can vary with culori versions (e.g.,
4.0.2) so tests only assert format/validity rather than exact hex.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@apps/www/src/content/docs/components/color-picker/index.mdx`:
- Around line 87-114: The docs/examples assert specific outputs for colorToHex
but the implementation may produce different gamut-mapped results depending on
culori version; update the documentation to clarify that the provided wide-gamut
example hex values (e.g., '`#3E63DD`', '`#FF5843`') are illustrative and not
guaranteed by tests; reference the colorToHex implementation that uses
culori.parse and toGamut and explicitly state that colorToHex returns uppercase
'`#RRGGBB`' for alpha===1, '`#RRGGBBAA`' otherwise, and null for unparseable input,
noting that exact gamut-mapped outputs can vary with culori versions (e.g.,
4.0.2) so tests only assert format/validity rather than exact hex.

In `@packages/raystack/components/color-picker/utils.ts`:
- Around line 31-37: There’s duplicated logic for choosing 6- vs 8-digit hex and
uppercasing in colorToHex and getColorString; extract that into a small shared
helper (e.g., formatHexWithAlpha or formatHexForColor) that accepts a culori
color object (or its clipped/safe variant), checks alpha using the same guard
((color.alpha ?? 1) or clipped.alpha), returns formatHex or formatHex8 and
uppercases the result, then replace the hex branch in colorToHex and the
corresponding branch in getColorString to call this helper.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7f91001c-ad4a-450c-badd-5910f145223a

📥 Commits

Reviewing files that changed from the base of the PR and between 1cfaeab and 7e1f477.

📒 Files selected for processing (4)
  • apps/www/src/content/docs/components/color-picker/index.mdx
  • packages/raystack/components/color-picker/__tests__/utils.test.ts
  • packages/raystack/components/color-picker/index.ts
  • packages/raystack/components/color-picker/utils.ts

Exposes a small public helper for consumers that need to convert any CSS
color string (oklch, rgb, hsl, hex, named) to a string in another format —
useful when feeding apsara's OKLCH tokens into APIs that expect hex/rgb/hsl
(charts, SVG attrs, canvas, design exports), or normalizing arbitrary input
back to the OKLCH design-system format.

For sRGB-bound outputs (hex/rgb/hsl), wide-gamut OKLCH inputs are
gamut-mapped via culori's `toGamut` (chroma reduced, lightness/hue
preserved) so the returned value is the closest sRGB representation,
not a per-channel-clipped one that would distort hue. OKLCH output
preserves the full color and matches the design system's token format.
Returns null for unparseable input.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@paanSinghCoder paanSinghCoder force-pushed the feat/color-to-hex-util branch from 7e1f477 to 04fbf04 Compare June 1, 2026 05:41
@paanSinghCoder paanSinghCoder changed the title feat(color-picker): export colorToHex utility feat(color-picker): export formatColor utility Jun 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant