Skip to content

Commit

Permalink
feat: add Alpha slider
Browse files Browse the repository at this point in the history
  • Loading branch information
tigranpetrossian committed Aug 4, 2024
1 parent ddf7d59 commit 0bcb270
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 9 deletions.
55 changes: 55 additions & 0 deletions figma-kit/src/components/color-picker/alpha.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { round } from 'remeda';
import type { CSSProperties } from 'react';
import { Slider } from '@components/slider';
import { useColorPickerContext } from '@components/color-picker/color-picker';
import { blendWithWhite, rgbaToCssString, rgbaToP3String } from '@lib/color';

type AlphaProps = React.ComponentPropsWithoutRef<'div'>;

const Alpha = (props: AlphaProps) => {
const { className, style } = props;
const { colorSpace, color, onColorChange } = useColorPickerContext('Hue');
const trackBg = {
srgb: {
transparent: rgbaToCssString({ ...color, a: 0 }),
opaque: rgbaToCssString({ ...color, a: 1 }),
},
p3: {
transparent: rgbaToP3String({ ...color, a: 0 }),
opaque: rgbaToP3String({ ...color, a: 1 }),
},
};
const thumbBg = {
srgb: rgbaToCssString(blendWithWhite(color)),
p3: rgbaToP3String(blendWithWhite(color)),
};

const handleValueChange = (value: number[]) => {
onColorChange({ ...color, a: round(value[0] / 100, 2) });
};

return (
<Slider
min={0}
max={100}
range={false}
value={[color.a * 100]}
onValueChange={handleValueChange}
className={`fp-ColorPickerAlphaSlider fp-color-space-${colorSpace} ${className}`}
style={
{
'--track-bg-transparent-srgb': trackBg.srgb.transparent,
'--track-bg-opaque-srgb': trackBg.srgb.opaque,
'--track-bg-transparent-p3': trackBg.p3.transparent,
'--track-bg-opaque-p3': trackBg.p3.opaque,
'--thumb-bg-srgb': thumbBg.srgb,
'--thumb-bg-p3': thumbBg.p3,
...style,
} as CSSProperties
}
/>
);
};

export type { AlphaProps };
export { Alpha };
34 changes: 28 additions & 6 deletions figma-kit/src/components/color-picker/color-picker.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
.fp-ColorPickerHueSlider {
}

.fp-ColorPickerHueSlider:where(.fp-color-model-hsv, .fp-color-model-hsl) {
--slider-track-bg: linear-gradient(
90deg,
Expand Down Expand Up @@ -39,9 +36,9 @@
);
}

@supports (color: color(display-p3 1 1 1)) {
@media (color-gamut: p3) {
.fp-ColorPickerHueSlider:where(.fp-color-model-hsv, .fp-color-model-hsl):where(.fp-color-space-display-p3) {
.fp-ColorPickerHueSlider:where(.fp-color-model-hsv, .fp-color-model-hsl):where(.fp-color-space-display-p3) {
@supports (color: color(display-p3 1 1 1)) {
@media (color-gamut: p3) {
--slider-track-bg: linear-gradient(
90deg,
color(display-p3 1 0 0) calc(var(--slider-track-size) / 2),
Expand Down Expand Up @@ -80,3 +77,28 @@
}
}
}

.fp-ColorPickerAlphaSlider {
--track-bg-step-1: var(--track-bg-transparent-srgb);
--track-bg-step-2: var(--track-bg-opaque-srgb);
--slider-thumb-bg: var(--thumb-bg-srgb);

--slider-track-bg: linear-gradient(
to right,
var(--track-bg-step-1) 0 calc(var(--slider-track-size) / 2),
var(--track-bg-step-2) calc(100% - calc(var(--slider-track-size) / 2)) 100%
),
url('data:image/svg+xml;utf8,%3Csvg%20width%3D%222%22%20height%3D%222%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%3E%3Cpath%20d%3D%22M0%200h1v2h1V1H0%22%20fill-rule%3D%22nonzero%22%20fill%3D%22%23e1e1e1%22/%3E%3C/svg%3E')
0 0 / auto 66.66%,
#fff;
}

.fp-ColorPickerAlphaSlider:where(.fp-color-space-display-p3) {
@supports (color: color(display-p3 1 1 1)) {
@media (color-gamut: p3) {
--slider-thumb-bg: var(--thumb-bg-p3);
--track-bg-step-1: var(--track-bg-transparent-p3);
--track-bg-step-2: var(--track-bg-opaque-p3);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@ const meta: Meta<typeof Picker> = {
export default meta;

export const Story = () => {
const [color, setColor] = useState({ r: 0, g: 1, b: 0, a: 1 });
const [color, setColor] = useState({ r: 0.48235294, g: 0.81176471, b: 0.48235294, a: 1 });

return (
<ColorPicker.Root colorModel="hsv" colorSpace="srgb" color={color} onColorChange={setColor}>
<Flex style={{ width: 240 }}>
<ColorPicker.Root colorModel="hsv" colorSpace="display-p3" color={color} onColorChange={setColor}>
<Flex direction="column" gap="1" style={{ width: 240 }}>
<ColorPicker.Hue />
<ColorPicker.Alpha />
</Flex>
</ColorPicker.Root>
);
Expand Down
1 change: 1 addition & 0 deletions figma-kit/src/components/color-picker/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './color-picker';
export * from './hue';
export * from './alpha';
3 changes: 3 additions & 0 deletions figma-kit/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,6 @@ export { Dialog };
export { AlertDialog };
export { ValueField };
export { Collapsible };
export { blendWithWhite } from '@lib/color';
export { rgbaToCssString } from '@lib/color';
export { rgbaToP3String } from '@lib/color';
31 changes: 31 additions & 0 deletions figma-kit/src/lib/color.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { round } from 'remeda';

export type RGBA = { r: number; g: number; b: number; a: number };
export type HSVA = { h: number; s: number; v: number; a: number };
export type HSLA = { h: number; s: number; l: number; a: number };
export type P3String = `color(display-p3 ${number} ${number} ${number}${` / ${number}` | ''})`;
export type RGBString = `rgb(${number} ${number} ${number}${` / ${number}` | ''})`;

export const rgbaToHsva = ({ r, g, b, a }: RGBA): HSVA => {
const max = Math.max(r, g, b);
Expand Down Expand Up @@ -64,3 +68,30 @@ export const hslaToRgba = (hsla: HSLA): RGBA => {
export const rgbaToHsla = (rgba: RGBA): HSLA => {
return hsvaToHsla(rgbaToHsva(rgba));
};

export function rgbaToP3String(color: RGBA): P3String {
const r = round(color.r, 4);
const g = round(color.g, 4);
const b = round(color.b, 4);
const a = round(color.a, 2);
return a < 1 ? `color(display-p3 ${r} ${g} ${b} / ${a})` : `color(display-p3 ${r} ${g} ${b})`;
}

export function rgbaToCssString(color: RGBA): RGBString {
const r = Math.round(color.r * 255);
const g = Math.round(color.g * 255);
const b = Math.round(color.b * 255);
const a = color.a;

return a < 1 ? `rgb(${r} ${g} ${b} / ${a})` : `rgb(${r} ${g} ${b})`;
}

export function blendWithWhite(color: RGBA): RGBA {
const { r, g, b, a } = color;
return {
r: r + (1 - r) * (1 - a),
g: g + (1 - g) * (1 - a),
b: b + (1 - b) * (1 - a),
a: 1,
};
}

0 comments on commit 0bcb270

Please sign in to comment.