diff --git a/packages/tohuhono/ui/package.json b/packages/tohuhono/ui/package.json
index a5c732af..980615a9 100644
--- a/packages/tohuhono/ui/package.json
+++ b/packages/tohuhono/ui/package.json
@@ -79,6 +79,8 @@
"@tailwindcss/typography": "^0.5.13",
"@tohuhono/dev": "workspace:*",
"@tohuhono/utils": "workspace:*",
+ "@uiw/react-color": "^2.3.0",
+ "@uiw/react-color-chrome": "^2.3.0",
"class-variance-authority": "^0.7.0",
"cmdk": "^1.0.0",
"date-fns": "^3.6.0",
diff --git a/packages/tohuhono/ui/src/theme/color-picker.tsx b/packages/tohuhono/ui/src/theme/color-picker.tsx
new file mode 100644
index 00000000..077b870a
--- /dev/null
+++ b/packages/tohuhono/ui/src/theme/color-picker.tsx
@@ -0,0 +1,42 @@
+"use client"
+
+import { PopoverContent } from "@radix-ui/react-popover"
+import {
+ hslaToHsva,
+ hsvaToHex,
+ type ColorResult,
+ type HslColor,
+} from "@uiw/react-color"
+import ChromeColorPicker from "@uiw/react-color-chrome"
+import { Popover, PopoverTrigger } from "../popover"
+
+export const ColorPicker = ({
+ disabled,
+ color,
+ onColorChange,
+}: {
+ disabled?: boolean
+ color: HslColor
+ onColorChange: (color: ColorResult) => void
+}) => {
+ const hsva = hslaToHsva({ ...color, a: 1 })
+
+ return (
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/packages/tohuhono/ui/src/theme/theme-editor-menu.tsx b/packages/tohuhono/ui/src/theme/theme-editor-menu.tsx
index 43f87c77..8d31683c 100644
--- a/packages/tohuhono/ui/src/theme/theme-editor-menu.tsx
+++ b/packages/tohuhono/ui/src/theme/theme-editor-menu.tsx
@@ -1,20 +1,20 @@
"use client"
import { ClipboardCopyIcon, MoonIcon, SunIcon } from "@radix-ui/react-icons"
-import { useState, ChangeEvent, Fragment } from "react"
import { cn } from "@tohuhono/utils"
-import { Input } from "../input"
-import { Label } from "../label"
+import { hslStringToHsla, type ColorResult } from "@uiw/react-color"
+import { Fragment, useCallback, useState } from "react"
+import { Button } from "../button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuTrigger,
} from "../dropdown-menu"
-import { Button } from "../button"
-import { ApplyTheme, copyToClipboard } from "./theme-editor"
+import { Label } from "../label"
+import { ColorPicker } from "./color-picker"
import { defaultTheme } from "./default-theme"
import { getMode, setMode } from "./mode-toggle"
-
+import { ApplyTheme, copyToClipboard } from "./theme-editor"
export const ThemeEditorMenu = ({ className }: { className?: string }) => {
const [theme, setTheme] = useState(defaultTheme)
@@ -25,17 +25,22 @@ export const ThemeEditorMenu = ({ className }: { className?: string }) => {
setMode(mode)
}
- const onInputChange =
- (id: string, mode: "light" | "dark") =>
- (event: ChangeEvent) =>
- setTheme(
- theme.map((t) => ({
- ...t,
- ...(t.id === id && {
- [mode]: event.currentTarget.value,
- }),
- })),
+ const onInputChange = useCallback(
+ (id: string, mode: "light" | "dark") => (color: ColorResult) => {
+ setTheme((prev) =>
+ prev.map((item) => {
+ if (item.id === id) {
+ return {
+ ...item,
+ [mode]: `${Math.round(color.hsl.h)} ${Math.round(color.hsl.s)}% ${Math.round(color.hsl.l)}%`,
+ }
+ }
+ return item
+ }),
)
+ },
+ [],
+ )
return (
@@ -83,27 +88,22 @@ export const ThemeEditorMenu = ({ className }: { className?: string }) => {
dark
- {theme.map(({ id, light, dark }) => {
- return (
-
-
-
-
-
- )
- })}
+ {theme.map(({ id, light, dark }) => (
+
+
+
+
+
+
+ ))}