From f0757dcbaed3f8daae4d903034af5c6da80586a9 Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 10 Feb 2025 12:27:20 -0500 Subject: [PATCH] Layout tweaks, copy buttons --- index.html | 4 +- public/favicon.svg | 3 + public/vite.svg | 1 - src/components/ColorPicker.tsx | 192 +++++++++++++++++------------- src/components/TailwindColors.tsx | 63 +++++----- src/index.css | 4 +- src/utils/colors.ts | 2 +- vite.config.ts | 6 + 8 files changed, 158 insertions(+), 117 deletions(-) create mode 100644 public/favicon.svg delete mode 100644 public/vite.svg diff --git a/index.html b/index.html index e4b78ea..4f1c7ba 100644 --- a/index.html +++ b/index.html @@ -2,9 +2,9 @@ - + - Vite + React + TS + Color Picker
diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..371d9f9 --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/public/vite.svg b/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/components/ColorPicker.tsx b/src/components/ColorPicker.tsx index a5a3b5f..2081a93 100644 --- a/src/components/ColorPicker.tsx +++ b/src/components/ColorPicker.tsx @@ -1,35 +1,15 @@ import { useState, useEffect } from 'react' import { HexColorPicker } from 'react-colorful' -import { SwatchIcon } from '@heroicons/react/24/outline' +import { SwatchIcon, ClipboardIcon, CheckIcon } from '@heroicons/react/24/outline' import { hexToRgb, rgbToHex, rgbToHsl, hslToRgb, rgbToHsb, hsbToRgb, findClosestTailwindColor, rgbToOklch, oklchToRgb, parseOklch, oklchToString } from '../utils/colors' import { TailwindColors } from './TailwindColors' -type RGB = { - r: number - g: number - b: number -} -type HSL = { - h: number - s: number - l: number -} -type HSB = { - h: number - s: number - b: number -} -type OKLCH = { - l: number - c: number - h: number -} type ColorFormat = 'hex' | 'rgb' | 'hsl' | 'hsb' | 'oklch' | 'tailwind' @@ -52,6 +32,7 @@ export function ColorPicker() { oklch: 'oklch(0.585 0.233 277.117)', tailwind: 'indigo-500' }) + const [copiedField, setCopiedField] = useState(null) const [closestTailwind, setClosestTailwind] = useState<{ name: string; hex: string; oklch: string; isExactMatch: boolean }>({ name: 'indigo-500', hex: '#6366F1', @@ -128,6 +109,35 @@ export function ColorPicker() { })) } + const handleCopyToClipboard = async (value: string, fieldId: string) => { + try { + await navigator.clipboard.writeText(value) + setCopiedField(fieldId) + setTimeout(() => { + setCopiedField(null) + }, 500) + } catch (err) { + console.error('Failed to copy to clipboard:', err) + } + } + + const CopyButton = ({ value, fieldId }: { value: string, fieldId: string }) => { + const isCopied = copiedField === fieldId + return ( + + ) + } + return (
@@ -135,10 +145,10 @@ export function ColorPicker() {

Find and convert colors in different formats

-
+
{/* Left Column: Color Picker and Input */} -
-
+
+
{/* Color Preview */}
@@ -170,78 +180,96 @@ export function ColorPicker() { - handleInputChange(e.target.value, 'hex')} - className="block w-full rounded-md border-0 py-1.5 px-3 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" - /> +
+ handleInputChange(e.target.value, 'hex')} + className="block w-full rounded-md border-0 py-1.5 px-3 pr-10 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" + /> + +
- handleInputChange(e.target.value, 'rgb')} - className="block w-full rounded-md border-0 py-1.5 px-3 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" - /> +
+ handleInputChange(e.target.value, 'rgb')} + className="block w-full rounded-md border-0 py-1.5 px-3 pr-10 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" + /> + +
- handleInputChange(e.target.value, 'hsl')} - className="block w-full rounded-md border-0 py-1.5 px-3 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" - /> +
+ handleInputChange(e.target.value, 'hsl')} + className="block w-full rounded-md border-0 py-1.5 px-3 pr-10 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" + /> + +
- handleInputChange(e.target.value, 'hsb')} - className="block w-full rounded-md border-0 py-1.5 px-3 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" - /> +
+ handleInputChange(e.target.value, 'hsb')} + className="block w-full rounded-md border-0 py-1.5 px-3 pr-10 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" + /> + +
- handleInputChange(e.target.value, 'oklch')} - className="block w-full rounded-md border-0 py-1.5 px-3 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" - /> +
+ handleInputChange(e.target.value, 'oklch')} + className="block w-full rounded-md border-0 py-1.5 px-3 pr-10 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" + /> + +
- +
+ + +
@@ -249,22 +277,24 @@ export function ColorPicker() {
{/* Right Column: Tailwind Colors */} -
- { - // Always set the hex color for the color picker - if (selectedColor.startsWith('oklch')) { - const oklch = parseOklch(selectedColor) - if (oklch) { - const rgb = oklchToRgb(oklch.l, oklch.c, oklch.h) - setColor(rgbToHex(rgb.r, rgb.g, rgb.b)) +
+
+ { + // Always set the hex color for the color picker + if (selectedColor.startsWith('oklch')) { + const oklch = parseOklch(selectedColor) + if (oklch) { + const rgb = oklchToRgb(oklch.l, oklch.c, oklch.h) + setColor(rgbToHex(rgb.r, rgb.g, rgb.b)) + } + } else { + setColor(selectedColor) } - } else { - setColor(selectedColor) - } - }} - selectedColor={color} - /> + }} + selectedColor={color} + /> +
diff --git a/src/components/TailwindColors.tsx b/src/components/TailwindColors.tsx index 2b9e9bd..2343104 100644 --- a/src/components/TailwindColors.tsx +++ b/src/components/TailwindColors.tsx @@ -46,36 +46,41 @@ export function TailwindColors({ onColorSelect, selectedColor }: TailwindColorsP } return ( -
-
- {/* Color rows */} - {orderedColors.map(colorName => ( - -
- {colorName} -
- {shades.map(shade => { - const values = TAILWIND_COLORS[colorName][shade] - const hexColor = typeof values === 'string' ? values : values.hex - const textColorClass = getTextColor(hexColor) +
+
+ Tailwind Color Reference +
+
+
+ {/* Color rows */} + {orderedColors.map(colorName => ( + +
+ {colorName} +
+ {shades.map(shade => { + const values = TAILWIND_COLORS[colorName][shade] + const hexColor = typeof values === 'string' ? values : values.hex + const textColorClass = getTextColor(hexColor) - return ( - - ) - })} -
- ))} + return ( + + ) + })} + + ))} +
) diff --git a/src/index.css b/src/index.css index 8ca46a6..6cf94be 100644 --- a/src/index.css +++ b/src/index.css @@ -54,9 +54,7 @@ button { cursor: pointer; transition: border-color 0.25s; } -button:hover { - border-color: #646cff; -} + button:focus, button:focus-visible { outline: 4px auto -webkit-focus-ring-color; diff --git a/src/utils/colors.ts b/src/utils/colors.ts index 2ab4ae3..8e5c92c 100644 --- a/src/utils/colors.ts +++ b/src/utils/colors.ts @@ -261,7 +261,7 @@ export function oklchToString(l: number, c: number, h: number): string { // Function to find the closest Tailwind color using CIELAB color space export function findClosestTailwindColor(color: string): { name: string; hex: string; oklch: string; isExactMatch: boolean } { const rgb1 = color.startsWith('oklch') - ? oklchToRgb(...Object.values(parseOklch(color) || { l: 0, c: 0, h: 0 })) + ? oklchToRgb(parseOklch(color)?.l || 0, parseOklch(color)?.c || 0, parseOklch(color)?.h || 0) : hexToRgb(color) if (!rgb1) return { name: 'Invalid color', hex: color, oklch: '', isExactMatch: false } diff --git a/vite.config.ts b/vite.config.ts index cb34d31..c38d6a1 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -11,4 +11,10 @@ export default defineConfig({ server: { port: 5555, }, + base: '/color-picker/', + build: { + outDir: 'dist', + assetsDir: 'assets', + emptyOutDir: true, + }, })