Skip to content

Commit 93247db

Browse files
committed
feat: add a color palette tab to the color picker component
1 parent d830004 commit 93247db

8 files changed

+369
-96
lines changed

.stylelintrc.js

+1
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ export default {
152152
"block-size",
153153
"min-block-size",
154154
"max-block-size",
155+
"aspect-ratio",
155156
"margin",
156157
"margin-left",
157158
"margin-right",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
.root {
2+
padding-block: 1rem;
3+
}
4+
5+
.row {
6+
display: flex;
7+
justify-content: center;
8+
}
9+
10+
.cell {
11+
inline-size: 1rem;
12+
block-size: 1rem;
13+
cursor: pointer;
14+
}
15+
16+
.cell:hover {
17+
z-index: 1;
18+
outline: 1px solid white;
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { parseColor } from "@keybr/color";
2+
import * as styles from "./ColorPaletteTab.module.less";
3+
import { palette } from "./palette.ts";
4+
import type { ColorEditorProps } from "./types.ts";
5+
6+
export function ColorPaletteTab({ color, onChange }: ColorEditorProps) {
7+
return (
8+
<div className={styles.root}>
9+
{Object.entries(palette).map(([name, colors]) => (
10+
<div key={name} className={styles.row}>
11+
{Object.entries(colors).map(([saturation, color]) => (
12+
<span
13+
key={saturation}
14+
className={styles.cell}
15+
style={{ backgroundColor: color }}
16+
onClick={() => {
17+
onChange(parseColor(color));
18+
}}
19+
/>
20+
))}
21+
</div>
22+
))}
23+
</div>
24+
);
25+
}
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,13 @@
11
.root {
2-
background-color: #000;
3-
}
4-
5-
.saturation {
6-
position: relative;
7-
box-sizing: content-box;
82
inline-size: 256px;
9-
block-size: 256px;
10-
outline: none;
113
}
124

13-
.hue {
14-
position: relative;
15-
box-sizing: content-box;
16-
inline-size: 256px;
17-
block-size: 2rem;
18-
outline: none;
5+
.tabs {
6+
display: flex;
197
}
208

21-
.channel {
22-
position: relative;
23-
box-sizing: content-box;
24-
inline-size: 256px;
25-
block-size: 2rem;
26-
outline: none;
9+
.tab {
10+
flex: 1;
11+
text-align: center;
12+
cursor: pointer;
2713
}
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,35 @@
1-
import { HsvColor, RgbColor } from "@keybr/color";
2-
import { Spacer } from "@keybr/widget";
3-
import { ColorInput } from "./ColorInput.tsx";
1+
import { Icon } from "@keybr/widget";
2+
import { mdiGrid, mdiPalette } from "@mdi/js";
3+
import { useState } from "react";
4+
import { ColorPaletteTab } from "./ColorPaletteTab.tsx";
45
import * as styles from "./ColorPicker.module.less";
5-
import { Slider } from "./Slider.tsx";
6-
import { Thumb } from "./Thumb.tsx";
6+
import { ColorPickerTab } from "./ColorPickerTab.tsx";
77
import { type ColorEditorProps } from "./types.ts";
88

99
export function ColorPicker({ color, onChange }: ColorEditorProps) {
10-
const { h, s, v } = color.toHsv();
11-
const { r, g, b } = color.toRgb();
12-
const saturationValue = { x: s, y: v };
13-
const hueValue = { x: h, y: 0.5 };
14-
const hueColor = new HsvColor(h, 1, 1);
15-
const rValue = { x: r, y: 0.5 };
16-
const gValue = { x: g, y: 0.5 };
17-
const bValue = { x: b, y: 0.5 };
10+
const [tab, setTab] = useState(0);
1811
return (
1912
<div className={styles.root}>
20-
<Slider
21-
className={styles.saturation}
22-
style={{
23-
backgroundColor: String(hueColor),
24-
backgroundImage: `linear-gradient(0deg,#000,transparent),linear-gradient(90deg,#fff,hsla(0,0%,100%,0))`,
25-
}}
26-
value={saturationValue}
27-
onChange={({ x, y }) => {
28-
onChange(new HsvColor(h, x, y));
29-
}}
30-
>
31-
<Thumb color={color} value={saturationValue} />
32-
</Slider>
33-
<Spacer size={1} />
34-
<Slider
35-
className={styles.hue}
36-
style={{
37-
backgroundImage: `linear-gradient(to right,#f00,#ff0,#0f0,#0ff,#00f,#f0f,#f00)`,
38-
}}
39-
value={hueValue}
40-
onChange={({ x }) => {
41-
onChange(new HsvColor(x, s, v));
42-
}}
43-
>
44-
<Thumb color={hueColor} value={hueValue} />
45-
</Slider>
46-
<Slider
47-
className={styles.channel}
48-
style={{
49-
backgroundImage: `linear-gradient(to right,${new RgbColor(0, g, b)},${new RgbColor(1, g, b)})`,
50-
}}
51-
value={rValue}
52-
onChange={({ x }) => {
53-
onChange(new RgbColor(x, g, b));
54-
}}
55-
>
56-
<Thumb color={color} value={rValue} />
57-
</Slider>
58-
<Slider
59-
className={styles.channel}
60-
style={{
61-
backgroundImage: `linear-gradient(to right,${new RgbColor(r, 0, b)},${new RgbColor(r, 1, b)})`,
62-
}}
63-
value={gValue}
64-
onChange={({ x }) => {
65-
onChange(new RgbColor(r, x, b));
66-
}}
67-
>
68-
<Thumb color={color} value={gValue} />
69-
</Slider>
70-
<Slider
71-
className={styles.channel}
72-
style={{
73-
backgroundImage: `linear-gradient(to right,${new RgbColor(r, g, 0)},${new RgbColor(r, g, 1)})`,
74-
}}
75-
value={gValue}
76-
onChange={({ x }) => {
77-
onChange(new RgbColor(r, g, x));
78-
}}
79-
>
80-
<Thumb color={color} value={bValue} />
81-
</Slider>
82-
<ColorInput color={color} onChange={onChange} />
13+
<div className={styles.tabs}>
14+
<span
15+
className={styles.tab}
16+
onClick={() => {
17+
setTab(0);
18+
}}
19+
>
20+
<Icon shape={mdiPalette} />
21+
</span>
22+
<span
23+
className={styles.tab}
24+
onClick={() => {
25+
setTab(1);
26+
}}
27+
>
28+
<Icon shape={mdiGrid} />
29+
</span>
30+
</div>
31+
{tab === 0 && <ColorPickerTab color={color} onChange={onChange} />}
32+
{tab === 1 && <ColorPaletteTab color={color} onChange={onChange} />}
8333
</div>
8434
);
8535
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
.root {
2+
background-color: #000;
3+
}
4+
5+
.saturation {
6+
position: relative;
7+
aspect-ratio: 1/1;
8+
outline: none;
9+
}
10+
11+
.hue {
12+
position: relative;
13+
block-size: 2rem;
14+
outline: none;
15+
}
16+
17+
.channel {
18+
position: relative;
19+
block-size: 2rem;
20+
outline: none;
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { HsvColor, RgbColor } from "@keybr/color";
2+
import { Spacer } from "@keybr/widget";
3+
import { ColorInput } from "./ColorInput.tsx";
4+
import * as styles from "./ColorPickerTab.module.less";
5+
import { Slider } from "./Slider.tsx";
6+
import { Thumb } from "./Thumb.tsx";
7+
import { type ColorEditorProps } from "./types.ts";
8+
9+
export function ColorPickerTab({ color, onChange }: ColorEditorProps) {
10+
const { h, s, v } = color.toHsv();
11+
const { r, g, b } = color.toRgb();
12+
const saturationValue = { x: s, y: v };
13+
const hueValue = { x: h, y: 0.5 };
14+
const hueColor = new HsvColor(h, 1, 1);
15+
const rValue = { x: r, y: 0.5 };
16+
const gValue = { x: g, y: 0.5 };
17+
const bValue = { x: b, y: 0.5 };
18+
return (
19+
<div className={styles.root}>
20+
<Slider
21+
className={styles.saturation}
22+
style={{
23+
backgroundColor: String(hueColor),
24+
backgroundImage: `linear-gradient(0deg,#000,transparent),linear-gradient(90deg,#fff,hsla(0,0%,100%,0))`,
25+
}}
26+
value={saturationValue}
27+
onChange={({ x, y }) => {
28+
onChange(new HsvColor(h, x, y));
29+
}}
30+
>
31+
<Thumb color={color} value={saturationValue} />
32+
</Slider>
33+
<Spacer size={1} />
34+
<Slider
35+
className={styles.hue}
36+
style={{
37+
backgroundImage: `linear-gradient(to right,#f00,#ff0,#0f0,#0ff,#00f,#f0f,#f00)`,
38+
}}
39+
value={hueValue}
40+
onChange={({ x }) => {
41+
onChange(new HsvColor(x, s, v));
42+
}}
43+
>
44+
<Thumb color={hueColor} value={hueValue} />
45+
</Slider>
46+
<Slider
47+
className={styles.channel}
48+
style={{
49+
backgroundImage: `linear-gradient(to right,${new RgbColor(0, g, b)},${new RgbColor(1, g, b)})`,
50+
}}
51+
value={rValue}
52+
onChange={({ x }) => {
53+
onChange(new RgbColor(x, g, b));
54+
}}
55+
>
56+
<Thumb color={color} value={rValue} />
57+
</Slider>
58+
<Slider
59+
className={styles.channel}
60+
style={{
61+
backgroundImage: `linear-gradient(to right,${new RgbColor(r, 0, b)},${new RgbColor(r, 1, b)})`,
62+
}}
63+
value={gValue}
64+
onChange={({ x }) => {
65+
onChange(new RgbColor(r, x, b));
66+
}}
67+
>
68+
<Thumb color={color} value={gValue} />
69+
</Slider>
70+
<Slider
71+
className={styles.channel}
72+
style={{
73+
backgroundImage: `linear-gradient(to right,${new RgbColor(r, g, 0)},${new RgbColor(r, g, 1)})`,
74+
}}
75+
value={gValue}
76+
onChange={({ x }) => {
77+
onChange(new RgbColor(r, g, x));
78+
}}
79+
>
80+
<Thumb color={color} value={bValue} />
81+
</Slider>
82+
<ColorInput color={color} onChange={onChange} />
83+
</div>
84+
);
85+
}

0 commit comments

Comments
 (0)