Tag Enhancement + bugfixes
This commit is contained in:
171
frontend/src/components/ui/ColorPicker.tsx
Normal file
171
frontend/src/components/ui/ColorPicker.tsx
Normal file
@@ -0,0 +1,171 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import Button from './Button';
|
||||
|
||||
interface ColorPickerProps {
|
||||
value?: string;
|
||||
onChange: (color: string | undefined) => void;
|
||||
disabled?: boolean;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
// Theme-compatible color palette
|
||||
const THEME_COLORS = [
|
||||
// Primary blues
|
||||
{ hex: '#3B82F6', name: 'Theme Blue' },
|
||||
{ hex: '#1D4ED8', name: 'Deep Blue' },
|
||||
{ hex: '#60A5FA', name: 'Light Blue' },
|
||||
|
||||
// Greens
|
||||
{ hex: '#10B981', name: 'Emerald' },
|
||||
{ hex: '#059669', name: 'Forest Green' },
|
||||
{ hex: '#34D399', name: 'Light Green' },
|
||||
|
||||
// Purples
|
||||
{ hex: '#8B5CF6', name: 'Purple' },
|
||||
{ hex: '#7C3AED', name: 'Deep Purple' },
|
||||
{ hex: '#A78BFA', name: 'Light Purple' },
|
||||
|
||||
// Warm tones
|
||||
{ hex: '#F59E0B', name: 'Amber' },
|
||||
{ hex: '#D97706', name: 'Orange' },
|
||||
{ hex: '#F97316', name: 'Bright Orange' },
|
||||
|
||||
// Reds/Pinks
|
||||
{ hex: '#EF4444', name: 'Red' },
|
||||
{ hex: '#F472B6', name: 'Pink' },
|
||||
{ hex: '#EC4899', name: 'Hot Pink' },
|
||||
|
||||
// Neutrals
|
||||
{ hex: '#6B7280', name: 'Gray' },
|
||||
{ hex: '#4B5563', name: 'Dark Gray' },
|
||||
{ hex: '#9CA3AF', name: 'Light Gray' }
|
||||
];
|
||||
|
||||
export default function ColorPicker({ value, onChange, disabled, label }: ColorPickerProps) {
|
||||
const [showCustomPicker, setShowCustomPicker] = useState(false);
|
||||
const [customColor, setCustomColor] = useState(value || '#3B82F6');
|
||||
|
||||
const handleThemeColorSelect = (color: string) => {
|
||||
onChange(color);
|
||||
setShowCustomPicker(false);
|
||||
};
|
||||
|
||||
const handleCustomColorChange = (color: string) => {
|
||||
setCustomColor(color);
|
||||
onChange(color);
|
||||
};
|
||||
|
||||
const handleRemoveColor = () => {
|
||||
onChange(undefined);
|
||||
setShowCustomPicker(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
{label && (
|
||||
<label className="block text-sm font-medium theme-header">
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
|
||||
{/* Current Color Display */}
|
||||
{value && (
|
||||
<div className="flex items-center gap-2 p-2 border theme-border rounded-lg">
|
||||
<div
|
||||
className="w-6 h-6 rounded border border-gray-300 dark:border-gray-600"
|
||||
style={{ backgroundColor: value }}
|
||||
/>
|
||||
<span className="text-sm theme-text font-mono">{value}</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleRemoveColor}
|
||||
disabled={disabled}
|
||||
className="ml-auto text-xs"
|
||||
>
|
||||
Remove
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Theme Color Palette */}
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-sm font-medium theme-header">Theme Colors</h4>
|
||||
<div className="grid grid-cols-6 gap-2 p-3 border theme-border rounded-lg">
|
||||
{THEME_COLORS.map((color) => (
|
||||
<button
|
||||
key={color.hex}
|
||||
type="button"
|
||||
className={`
|
||||
w-8 h-8 rounded-md border-2 transition-all hover:scale-110 focus:outline-none focus:ring-2 focus:ring-theme-accent
|
||||
${value === color.hex ? 'border-gray-800 dark:border-white scale-110' : 'border-gray-300 dark:border-gray-600'}
|
||||
`}
|
||||
style={{ backgroundColor: color.hex }}
|
||||
onClick={() => handleThemeColorSelect(color.hex)}
|
||||
disabled={disabled}
|
||||
title={color.name}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Custom Color Section */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<h4 className="text-sm font-medium theme-header">Custom Color</h4>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setShowCustomPicker(!showCustomPicker)}
|
||||
disabled={disabled}
|
||||
className="text-xs"
|
||||
>
|
||||
{showCustomPicker ? 'Hide' : 'Show'} Custom
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{showCustomPicker && (
|
||||
<div className="p-3 border theme-border rounded-lg space-y-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<input
|
||||
type="color"
|
||||
value={customColor}
|
||||
onChange={(e) => handleCustomColorChange(e.target.value)}
|
||||
disabled={disabled}
|
||||
className="w-12 h-8 rounded border border-gray-300 dark:border-gray-600 cursor-pointer disabled:cursor-not-allowed"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
value={customColor}
|
||||
onChange={(e) => {
|
||||
const color = e.target.value;
|
||||
if (/^#[0-9A-Fa-f]{6}$/.test(color)) {
|
||||
setCustomColor(color);
|
||||
onChange(color);
|
||||
}
|
||||
}}
|
||||
disabled={disabled}
|
||||
className="flex-1 px-3 py-1 text-sm border theme-border rounded font-mono"
|
||||
placeholder="#3B82F6"
|
||||
/>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
onClick={() => onChange(customColor)}
|
||||
disabled={disabled}
|
||||
className="text-xs"
|
||||
>
|
||||
Apply
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-xs theme-text-muted">
|
||||
Enter a hex color code or use the color picker
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user