Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 170 additions & 0 deletions apps/www/src/app/examples/color-picker/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
'use client';

import { Button, ColorPicker, Flex, Popover, Text } from '@raystack/apsara';
import { useState } from 'react';

const cardStyle = {
width: 280,
padding: 16,
borderRadius: 8,
background: 'var(--rs-color-background-base-primary)',
border: '1px solid var(--rs-color-border-base-primary)'
} as const;

export default function ColorPickerExamplesPage() {
const [controlledValue, setControlledValue] = useState(
'oklch(0.5438 0.191 267.01)'
);
const [controlledMode, setControlledMode] = useState<
'hex' | 'rgb' | 'hsl' | 'oklch'
>('oklch');
const [popoverColor, setPopoverColor] = useState('#DA2929');

return (
<Flex
direction='column'
gap={9}
style={{
padding: 32,
background: 'var(--rs-color-background-neutral-secondary)',
minHeight: '100vh'
}}
>
<Flex direction='column' gap={2}>
<Text size='large' weight='medium'>
ColorPicker
</Text>
<Text size='small' variant='secondary'>
The picker edits internally in OKLCH for perceptual uniformity. The
area pad shows a chroma × lightness cross-section at the selected hue;
the <code>mode</code> prop selects the output format (hex / rgb / hsl
/ oklch).
</Text>
</Flex>

<Flex gap={6} wrap='wrap'>
<Flex direction='column' gap={3} style={cardStyle}>
<Text size='small' weight='medium'>
Default (hex)
</Text>
<ColorPicker defaultValue='#DA2929'>
<ColorPicker.Area />
<ColorPicker.Hue />
<ColorPicker.Alpha />
<Flex direction='row' gap={2}>
<ColorPicker.Mode />
<ColorPicker.Input copyable />
</Flex>
</ColorPicker>
</Flex>

<Flex direction='column' gap={3} style={cardStyle}>
<Text size='small' weight='medium'>
OKLCH mode
</Text>
<Text size='micro' variant='secondary'>
<code>defaultValue=&apos;oklch(0.5438 0.191 267.01)&apos;</code>{' '}
with <code>defaultMode=&apos;oklch&apos;</code>.
</Text>
<ColorPicker
defaultValue='oklch(0.5438 0.191 267.01)'
defaultMode='oklch'
>
<ColorPicker.Area />
<ColorPicker.Hue />
<ColorPicker.Alpha />
<Flex direction='row' gap={2}>
<ColorPicker.Mode />
<ColorPicker.Input copyable />
</Flex>
</ColorPicker>
</Flex>

<Flex direction='column' gap={3} style={cardStyle}>
<Text size='small' weight='medium'>
Controlled — emits live value
</Text>
<ColorPicker
value={controlledValue}
mode={controlledMode}
onValueChange={(value, mode) => {
setControlledValue(value);
setControlledMode(mode as typeof controlledMode);
}}
onModeChange={mode =>
setControlledMode(mode as typeof controlledMode)
}
>
<ColorPicker.Area />
<ColorPicker.Hue />
<ColorPicker.Alpha />
<Flex direction='row' gap={2}>
<ColorPicker.Mode />
<ColorPicker.Input copyable />
</Flex>
</ColorPicker>
<Flex direction='column' gap={1}>
<Text size='micro' variant='secondary'>
value:
</Text>
<Text
size='small'
style={{
fontFamily: 'monospace',
wordBreak: 'break-all'
}}
>
{controlledValue}
</Text>
<Text size='micro' variant='secondary' style={{ marginTop: 4 }}>
mode: <code>{controlledMode}</code>
</Text>
</Flex>
</Flex>

<Flex direction='column' gap={3} style={cardStyle}>
<Text size='small' weight='medium'>
Popover trigger
</Text>
<Popover>
<Popover.Trigger
render={
<Button
style={{
width: 60,
height: 60,
background: popoverColor
}}
/>
}
/>
<Popover.Content>
<ColorPicker
value={popoverColor}
onValueChange={setPopoverColor}
style={{ width: 240, height: 320 }}
>
<ColorPicker.Area />
<ColorPicker.Hue />
<ColorPicker.Alpha />
<Flex direction='row' gap={2}>
<ColorPicker.Mode />
<ColorPicker.Input copyable />
</Flex>
</ColorPicker>
</Popover.Content>
</Popover>
<Text
size='small'
style={{
fontFamily: 'monospace',
wordBreak: 'break-all'
}}
>
{popoverColor}
</Text>
</Flex>
</Flex>
</Flex>
);
}
43 changes: 41 additions & 2 deletions apps/www/src/content/docs/components/color-picker/demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ style={{
<ColorPicker.Alpha />
<Flex direction="row" gap={2}>
<ColorPicker.Mode />
<ColorPicker.Input />
<ColorPicker.Input copyable />
</Flex>
</ColorPicker>`
};
Expand All @@ -36,6 +36,45 @@ export const basicDemo = {
`
};

export const copyableDemo = {
type: 'code',
code: `<ColorPicker defaultValue='#5B8DEF' style={{
width: '240px',
height: '300px',
padding: 12,
background: 'white'
}}>
<ColorPicker.Area />
<ColorPicker.Hue />
<Flex direction='row' gap={2}>
<ColorPicker.Mode />
<ColorPicker.Input copyable />
</Flex>
</ColorPicker>`
};

export const oklchDemo = {
type: 'code',
code: `<ColorPicker
defaultValue='oklch(0.5438 0.191 267.01)'
defaultMode='oklch'
style={{
width: '240px',
Copy link
Copy Markdown
Contributor

@Shreyag02 Shreyag02 May 26, 2026

Choose a reason for hiding this comment

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

Moving only the slider values leaves the first component of the OKLCH value unchanged, making it look like the value isn't updating. Since the cursor also resets upon updating the slider, consider increasing the card's width to resolve this.

height: '320px',
padding: 12,
background: 'white'
}}
>
<ColorPicker.Area />
<ColorPicker.Hue />
<ColorPicker.Alpha />
<Flex direction="row" gap={2}>
<ColorPicker.Mode />
<ColorPicker.Input copyable />
</Flex>
</ColorPicker>`
};

export const popoverDemo = {
type: 'code',
previewCode: false,
Expand Down Expand Up @@ -68,7 +107,7 @@ export const popoverDemo = {
<ColorPicker.Alpha />
<Flex direction='row' gap={2}>
<ColorPicker.Mode />
<ColorPicker.Input />
<ColorPicker.Input copyable />
</Flex>
</ColorPicker>
</Popover.Content>
Expand Down
22 changes: 18 additions & 4 deletions apps/www/src/content/docs/components/color-picker/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ source: packages/raystack/components/color-picker
import {
preview,
basicDemo,
copyableDemo,
oklchDemo,
popoverDemo,
} from "./demo.ts";

Expand All @@ -24,7 +26,7 @@ import { ColorPicker } from '@raystack/apsara'
<ColorPicker.Hue />
<ColorPicker.Alpha />
<ColorPicker.Mode />
<ColorPicker.Input />
<ColorPicker.Input copyable />
</ColorPicker>
```

Expand All @@ -38,7 +40,7 @@ The `ColorPicker` is composed of several subcomponents, each responsible for a s

### Area

Enables users to select a color from a palette. Typically used for choosing saturation and brightness.
Enables users to select a color from a 2D palette. The surface adapts to the active mode: in `hex`, `rgb`, and `hsl` modes it renders the classic saturation × brightness gradient square; in `oklch` mode it renders a chroma × lightness plane covering the full P3 gamut.

### Hue

Expand All @@ -50,20 +52,32 @@ Provides a slider for selecting the alpha value of the color.

### Mode

Lets users switch between different color models (e.g., HEX, RGB, HSL) via a dropdown menu.
Lets users switch between different color models (HEX, RGB, HSL, OKLCH) via a dropdown menu.

<auto-type-table path="./props.ts" name="ColorPickerModeProps" />

### Input

Displays the current color value in the selected color model and allows direct text input.
Displays the current color value in the selected color model as a read-only string. The value is updated automatically as the user interacts with the area, hue, or alpha controls. Pass `copyable` to render a copy-to-clipboard button in the input's trailing slot.

## Examples

### Basic Usage

<Demo data={basicDemo} />

### Copy to Clipboard

Pass the `copyable` prop on `ColorPicker.Input` to render a copy-to-clipboard button in the input's trailing slot. The button copies the formatted color string in the active mode (e.g. `#FF0000`, `rgb(...)`, or `oklch(...)`) and shows a brief confirmation icon after a successful copy.

<Demo data={copyableDemo} />

### OKLCH Mode

The picker stores color internally in OKLCH. In `hex`, `rgb`, and `hsl` modes the area pad is the familiar HSL saturation × brightness square and the hue slider runs in HSL hue — so the picker behaves like a traditional color picker and only emits colors representable in the chosen format. Pass an `oklch(...)` string as `defaultValue` and set `defaultMode='oklch'` to switch to a chroma × lightness plane covering the full P3 gamut; the hue slider then runs in OKLCH hue, where equal steps correspond to equal perceptual changes.

<Demo data={oklchDemo} />

### Popover Integration

The `ColorPicker` can be embedded within a `Popover` component to create a more interactive and space-efficient color selection experience.
Expand Down
12 changes: 6 additions & 6 deletions apps/www/src/content/docs/components/color-picker/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/
export interface ColorPickerProps {
/**
* The controlled color value. Accepts hex, rgb, hsl, etc.
* The controlled color value. Accepts hex, rgb, hsl, oklch, etc.
*/
value?: string;
/**
Expand All @@ -16,14 +16,14 @@ export interface ColorPickerProps {
*/
onValueChange?: (value: string, mode: string) => void;
/**
* The initial color mode (hex, rgb, hsl).
* The initial color mode (hex, rgb, hsl, oklch).
* @default 'hex'
*/
defaultMode?: 'hex' | 'rgb' | 'hsl';
defaultMode?: 'hex' | 'rgb' | 'hsl' | 'oklch';
/**
* The controlled color mode.
*/
mode?: 'hex' | 'rgb' | 'hsl';
mode?: 'hex' | 'rgb' | 'hsl' | 'oklch';
/**
* Callback fired when the color mode changes.
*/
Expand All @@ -36,7 +36,7 @@ export interface ColorPickerProps {
export interface ColorPickerModeProps {
/**
* Supported color modes for the picker.
* @default ['hex', 'rgb', 'hsl']
* @default ['hex', 'rgb', 'hsl', 'oklch']
*/
options?: Array<'hex' | 'rgb' | 'hsl'>;
options?: Array<'hex' | 'rgb' | 'hsl' | 'oklch'>;
}
Loading
Loading