Skip to content

Commit

Permalink
feat: add a color palette tab to the color picker component
Browse files Browse the repository at this point in the history
  • Loading branch information
aradzie committed Nov 22, 2024
1 parent d830004 commit 0893185
Show file tree
Hide file tree
Showing 9 changed files with 370 additions and 97 deletions.
1 change: 1 addition & 0 deletions .stylelintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ export default {
"block-size",
"min-block-size",
"max-block-size",
"aspect-ratio",
"margin",
"margin-left",
"margin-right",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { tryParseColor } from "@keybr/color";
import { TextField, useHotkeysHandler } from "@keybr/widget";
import { useEffect, useRef, useState } from "react";
import type { ColorEditorProps } from "./types.ts";
import { type ColorEditorProps } from "./types.ts";

export function ColorInput({ color, onChange }: ColorEditorProps) {
const focus = useRef(false);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.root {
padding-block: 1rem;
}

.row {
display: flex;
justify-content: center;
}

.cell {
inline-size: 1rem;
block-size: 1rem;
cursor: pointer;
}

.cell:hover {
z-index: 1;
outline: 1px solid white;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { parseColor } from "@keybr/color";
import * as styles from "./ColorPaletteTab.module.less";
import { palette } from "./palette.ts";
import { type ColorEditorProps } from "./types.ts";

export function ColorPaletteTab({ color, onChange }: ColorEditorProps) {
return (
<div className={styles.root}>
{Object.entries(palette).map(([name, colors]) => (
<div key={name} className={styles.row}>
{Object.entries(colors).map(([saturation, color]) => (
<span
key={saturation}
className={styles.cell}
style={{ backgroundColor: color }}
onClick={() => {
onChange(parseColor(color));
}}
/>
))}
</div>
))}
</div>
);
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,13 @@
.root {
background-color: #000;
}

.saturation {
position: relative;
box-sizing: content-box;
inline-size: 256px;
block-size: 256px;
outline: none;
}

.hue {
position: relative;
box-sizing: content-box;
inline-size: 256px;
block-size: 2rem;
outline: none;
.tabs {
display: flex;
}

.channel {
position: relative;
box-sizing: content-box;
inline-size: 256px;
block-size: 2rem;
outline: none;
.tab {
flex: 1;
text-align: center;
cursor: pointer;
}
102 changes: 26 additions & 76 deletions packages/keybr-theme-designer/lib/design/input/color/ColorPicker.tsx
Original file line number Diff line number Diff line change
@@ -1,85 +1,35 @@
import { HsvColor, RgbColor } from "@keybr/color";
import { Spacer } from "@keybr/widget";
import { ColorInput } from "./ColorInput.tsx";
import { Icon } from "@keybr/widget";
import { mdiGrid, mdiPalette } from "@mdi/js";
import { useState } from "react";
import { ColorPaletteTab } from "./ColorPaletteTab.tsx";
import * as styles from "./ColorPicker.module.less";
import { Slider } from "./Slider.tsx";
import { Thumb } from "./Thumb.tsx";
import { ColorPickerTab } from "./ColorPickerTab.tsx";
import { type ColorEditorProps } from "./types.ts";

export function ColorPicker({ color, onChange }: ColorEditorProps) {
const { h, s, v } = color.toHsv();
const { r, g, b } = color.toRgb();
const saturationValue = { x: s, y: v };
const hueValue = { x: h, y: 0.5 };
const hueColor = new HsvColor(h, 1, 1);
const rValue = { x: r, y: 0.5 };
const gValue = { x: g, y: 0.5 };
const bValue = { x: b, y: 0.5 };
const [tab, setTab] = useState(0);
return (
<div className={styles.root}>
<Slider
className={styles.saturation}
style={{
backgroundColor: String(hueColor),
backgroundImage: `linear-gradient(0deg,#000,transparent),linear-gradient(90deg,#fff,hsla(0,0%,100%,0))`,
}}
value={saturationValue}
onChange={({ x, y }) => {
onChange(new HsvColor(h, x, y));
}}
>
<Thumb color={color} value={saturationValue} />
</Slider>
<Spacer size={1} />
<Slider
className={styles.hue}
style={{
backgroundImage: `linear-gradient(to right,#f00,#ff0,#0f0,#0ff,#00f,#f0f,#f00)`,
}}
value={hueValue}
onChange={({ x }) => {
onChange(new HsvColor(x, s, v));
}}
>
<Thumb color={hueColor} value={hueValue} />
</Slider>
<Slider
className={styles.channel}
style={{
backgroundImage: `linear-gradient(to right,${new RgbColor(0, g, b)},${new RgbColor(1, g, b)})`,
}}
value={rValue}
onChange={({ x }) => {
onChange(new RgbColor(x, g, b));
}}
>
<Thumb color={color} value={rValue} />
</Slider>
<Slider
className={styles.channel}
style={{
backgroundImage: `linear-gradient(to right,${new RgbColor(r, 0, b)},${new RgbColor(r, 1, b)})`,
}}
value={gValue}
onChange={({ x }) => {
onChange(new RgbColor(r, x, b));
}}
>
<Thumb color={color} value={gValue} />
</Slider>
<Slider
className={styles.channel}
style={{
backgroundImage: `linear-gradient(to right,${new RgbColor(r, g, 0)},${new RgbColor(r, g, 1)})`,
}}
value={gValue}
onChange={({ x }) => {
onChange(new RgbColor(r, g, x));
}}
>
<Thumb color={color} value={bValue} />
</Slider>
<ColorInput color={color} onChange={onChange} />
<div className={styles.tabs}>
<span
className={styles.tab}
onClick={() => {
setTab(0);
}}
>
<Icon shape={mdiPalette} />
</span>
<span
className={styles.tab}
onClick={() => {
setTab(1);
}}
>
<Icon shape={mdiGrid} />
</span>
</div>
{tab === 0 && <ColorPickerTab color={color} onChange={onChange} />}
{tab === 1 && <ColorPaletteTab color={color} onChange={onChange} />}
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
.root {
background-color: #000;
}

.saturation {
position: relative;
aspect-ratio: 1/1;
outline: none;
}

.hue {
position: relative;
block-size: 2rem;
outline: none;
}

.channel {
position: relative;
block-size: 2rem;
outline: none;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { HsvColor, RgbColor } from "@keybr/color";
import { Spacer } from "@keybr/widget";
import { ColorInput } from "./ColorInput.tsx";
import * as styles from "./ColorPickerTab.module.less";
import { Slider } from "./Slider.tsx";
import { Thumb } from "./Thumb.tsx";
import { type ColorEditorProps } from "./types.ts";

export function ColorPickerTab({ color, onChange }: ColorEditorProps) {
const { h, s, v } = color.toHsv();
const { r, g, b } = color.toRgb();
const saturationValue = { x: s, y: v };
const hueValue = { x: h, y: 0.5 };
const hueColor = new HsvColor(h, 1, 1);
const rValue = { x: r, y: 0.5 };
const gValue = { x: g, y: 0.5 };
const bValue = { x: b, y: 0.5 };
return (
<div className={styles.root}>
<Slider
className={styles.saturation}
style={{
backgroundColor: String(hueColor),
backgroundImage: `linear-gradient(0deg,#000,transparent),linear-gradient(90deg,#fff,hsla(0,0%,100%,0))`,
}}
value={saturationValue}
onChange={({ x, y }) => {
onChange(new HsvColor(h, x, y));
}}
>
<Thumb color={color} value={saturationValue} />
</Slider>
<Spacer size={1} />
<Slider
className={styles.hue}
style={{
backgroundImage: `linear-gradient(to right,#f00,#ff0,#0f0,#0ff,#00f,#f0f,#f00)`,
}}
value={hueValue}
onChange={({ x }) => {
onChange(new HsvColor(x, s, v));
}}
>
<Thumb color={hueColor} value={hueValue} />
</Slider>
<Slider
className={styles.channel}
style={{
backgroundImage: `linear-gradient(to right,${new RgbColor(0, g, b)},${new RgbColor(1, g, b)})`,
}}
value={rValue}
onChange={({ x }) => {
onChange(new RgbColor(x, g, b));
}}
>
<Thumb color={color} value={rValue} />
</Slider>
<Slider
className={styles.channel}
style={{
backgroundImage: `linear-gradient(to right,${new RgbColor(r, 0, b)},${new RgbColor(r, 1, b)})`,
}}
value={gValue}
onChange={({ x }) => {
onChange(new RgbColor(r, x, b));
}}
>
<Thumb color={color} value={gValue} />
</Slider>
<Slider
className={styles.channel}
style={{
backgroundImage: `linear-gradient(to right,${new RgbColor(r, g, 0)},${new RgbColor(r, g, 1)})`,
}}
value={gValue}
onChange={({ x }) => {
onChange(new RgbColor(r, g, x));
}}
>
<Thumb color={color} value={bValue} />
</Slider>
<ColorInput color={color} onChange={onChange} />
</div>
);
}
Loading

0 comments on commit 0893185

Please sign in to comment.