mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
Enhance ColorPicker component with RGB support and improved styling
- Added RGB value updates on color input changes and preset selections. - Refactored RGB sliders to always be visible, enhancing user experience across devices. - Improved range input styles for better accessibility and visual feedback. - Updated number input fields for RGB values to hide spinners and ensure a cleaner look.
This commit is contained in:
@@ -87,6 +87,7 @@ export function ColorPicker({ value, onChange, label, description, disabled }: C
|
|||||||
const handleColorInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleColorInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const newColor = e.target.value
|
const newColor = e.target.value
|
||||||
setLocalValue(newColor)
|
setLocalValue(newColor)
|
||||||
|
setRgb(hexToRgb(newColor))
|
||||||
onChange(newColor)
|
onChange(newColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,8 +103,9 @@ export function ColorPicker({ value, onChange, label, description, disabled }: C
|
|||||||
if (newValue === '' || newValue.match(/^#[0-9A-Fa-f]{0,6}$/)) {
|
if (newValue === '' || newValue.match(/^#[0-9A-Fa-f]{0,6}$/)) {
|
||||||
setLocalValue(newValue)
|
setLocalValue(newValue)
|
||||||
|
|
||||||
// Only trigger onChange for valid complete hex
|
// Only trigger onChange and update RGB for valid complete hex
|
||||||
if (newValue.match(/^#[0-9A-Fa-f]{6}$/)) {
|
if (newValue.match(/^#[0-9A-Fa-f]{6}$/)) {
|
||||||
|
setRgb(hexToRgb(newValue))
|
||||||
onChange(newValue)
|
onChange(newValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,6 +113,7 @@ export function ColorPicker({ value, onChange, label, description, disabled }: C
|
|||||||
|
|
||||||
const handlePresetClick = (color: string) => {
|
const handlePresetClick = (color: string) => {
|
||||||
setLocalValue(color)
|
setLocalValue(color)
|
||||||
|
setRgb(hexToRgb(color))
|
||||||
onChange(color)
|
onChange(color)
|
||||||
setIsOpen(false)
|
setIsOpen(false)
|
||||||
}
|
}
|
||||||
@@ -178,71 +181,90 @@ export function ColorPicker({ value, onChange, label, description, disabled }: C
|
|||||||
|
|
||||||
{/* Dropdown with presets and RGB sliders */}
|
{/* Dropdown with presets and RGB sliders */}
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<div className="absolute z-50 mt-2 p-3 bg-dark-800 rounded-xl border border-dark-700 shadow-xl animate-fade-in w-72">
|
<div className="absolute z-50 mt-2 p-3 sm:p-4 bg-dark-800 rounded-xl border border-dark-700 shadow-xl animate-fade-in w-[calc(100vw-2rem)] sm:w-80 max-w-sm left-0 sm:left-auto">
|
||||||
{/* RGB Sliders - shown in Telegram instead of native picker */}
|
{/* RGB Sliders - always visible on all devices */}
|
||||||
{isTelegram && (
|
<div className="mb-3 pb-3 border-b border-dark-700">
|
||||||
<div className="mb-3 pb-3 border-b border-dark-700">
|
<div className="text-xs text-dark-400 mb-3">RGB</div>
|
||||||
<div className="text-xs text-dark-400 mb-2">RGB</div>
|
<div className="space-y-3 sm:space-y-2">
|
||||||
<div className="space-y-2">
|
{/* Red */}
|
||||||
{/* Red */}
|
<div className="flex items-center gap-2 sm:gap-3">
|
||||||
<div className="flex items-center gap-2">
|
<span className="text-xs font-medium text-red-400 w-4 flex-shrink-0">R</span>
|
||||||
<span className="text-xs text-red-400 w-4">R</span>
|
<input
|
||||||
<input
|
type="range"
|
||||||
type="range"
|
min="0"
|
||||||
min="0"
|
max="255"
|
||||||
max="255"
|
value={rgb.r}
|
||||||
value={rgb.r}
|
onChange={(e) => handleRgbChange('r', parseInt(e.target.value))}
|
||||||
onChange={(e) => handleRgbChange('r', parseInt(e.target.value))}
|
className="flex-1 h-3 sm:h-2 rounded-full appearance-none cursor-pointer touch-pan-x"
|
||||||
className="flex-1 h-2 rounded-full appearance-none cursor-pointer"
|
style={{
|
||||||
style={{
|
background: `linear-gradient(to right, rgb(0,${rgb.g},${rgb.b}), rgb(255,${rgb.g},${rgb.b}))`,
|
||||||
background: `linear-gradient(to right, rgb(0,${rgb.g},${rgb.b}), rgb(255,${rgb.g},${rgb.b}))`,
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
<input
|
||||||
<span className="text-xs text-dark-400 w-8 text-right">{rgb.r}</span>
|
type="number"
|
||||||
</div>
|
min="0"
|
||||||
{/* Green */}
|
max="255"
|
||||||
<div className="flex items-center gap-2">
|
value={rgb.r}
|
||||||
<span className="text-xs text-green-400 w-4">G</span>
|
onChange={(e) => handleRgbChange('r', Math.min(255, Math.max(0, parseInt(e.target.value) || 0)))}
|
||||||
<input
|
className="w-14 sm:w-12 text-xs text-center bg-dark-700 border border-dark-600 rounded px-1 py-1 text-dark-200"
|
||||||
type="range"
|
/>
|
||||||
min="0"
|
</div>
|
||||||
max="255"
|
{/* Green */}
|
||||||
value={rgb.g}
|
<div className="flex items-center gap-2 sm:gap-3">
|
||||||
onChange={(e) => handleRgbChange('g', parseInt(e.target.value))}
|
<span className="text-xs font-medium text-green-400 w-4 flex-shrink-0">G</span>
|
||||||
className="flex-1 h-2 rounded-full appearance-none cursor-pointer"
|
<input
|
||||||
style={{
|
type="range"
|
||||||
background: `linear-gradient(to right, rgb(${rgb.r},0,${rgb.b}), rgb(${rgb.r},255,${rgb.b}))`,
|
min="0"
|
||||||
}}
|
max="255"
|
||||||
/>
|
value={rgb.g}
|
||||||
<span className="text-xs text-dark-400 w-8 text-right">{rgb.g}</span>
|
onChange={(e) => handleRgbChange('g', parseInt(e.target.value))}
|
||||||
</div>
|
className="flex-1 h-3 sm:h-2 rounded-full appearance-none cursor-pointer touch-pan-x"
|
||||||
{/* Blue */}
|
style={{
|
||||||
<div className="flex items-center gap-2">
|
background: `linear-gradient(to right, rgb(${rgb.r},0,${rgb.b}), rgb(${rgb.r},255,${rgb.b}))`,
|
||||||
<span className="text-xs text-blue-400 w-4">B</span>
|
}}
|
||||||
<input
|
/>
|
||||||
type="range"
|
<input
|
||||||
min="0"
|
type="number"
|
||||||
max="255"
|
min="0"
|
||||||
value={rgb.b}
|
max="255"
|
||||||
onChange={(e) => handleRgbChange('b', parseInt(e.target.value))}
|
value={rgb.g}
|
||||||
className="flex-1 h-2 rounded-full appearance-none cursor-pointer"
|
onChange={(e) => handleRgbChange('g', Math.min(255, Math.max(0, parseInt(e.target.value) || 0)))}
|
||||||
style={{
|
className="w-14 sm:w-12 text-xs text-center bg-dark-700 border border-dark-600 rounded px-1 py-1 text-dark-200"
|
||||||
background: `linear-gradient(to right, rgb(${rgb.r},${rgb.g},0), rgb(${rgb.r},${rgb.g},255))`,
|
/>
|
||||||
}}
|
</div>
|
||||||
/>
|
{/* Blue */}
|
||||||
<span className="text-xs text-dark-400 w-8 text-right">{rgb.b}</span>
|
<div className="flex items-center gap-2 sm:gap-3">
|
||||||
</div>
|
<span className="text-xs font-medium text-blue-400 w-4 flex-shrink-0">B</span>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="0"
|
||||||
|
max="255"
|
||||||
|
value={rgb.b}
|
||||||
|
onChange={(e) => handleRgbChange('b', parseInt(e.target.value))}
|
||||||
|
className="flex-1 h-3 sm:h-2 rounded-full appearance-none cursor-pointer touch-pan-x"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(to right, rgb(${rgb.r},${rgb.g},0), rgb(${rgb.r},${rgb.g},255))`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
max="255"
|
||||||
|
value={rgb.b}
|
||||||
|
onChange={(e) => handleRgbChange('b', Math.min(255, Math.max(0, parseInt(e.target.value) || 0)))}
|
||||||
|
className="w-14 sm:w-12 text-xs text-center bg-dark-700 border border-dark-600 rounded px-1 py-1 text-dark-200"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
|
|
||||||
<div className="text-xs text-dark-400 mb-2">Preset colors</div>
|
<div className="text-xs text-dark-400 mb-2">Preset colors</div>
|
||||||
<div className="grid grid-cols-4 gap-1">
|
<div className="grid grid-cols-4 gap-2 sm:gap-1">
|
||||||
{PRESET_COLORS.map((preset) => (
|
{PRESET_COLORS.map((preset) => (
|
||||||
<button
|
<button
|
||||||
key={preset}
|
key={preset}
|
||||||
onClick={() => handlePresetClick(preset)}
|
onClick={() => handlePresetClick(preset)}
|
||||||
className={`min-w-[44px] min-h-[44px] w-full aspect-square rounded-lg border-2 transition-all hover:scale-105 ${
|
className={`min-w-[40px] min-h-[40px] sm:min-w-[44px] sm:min-h-[44px] w-full aspect-square rounded-lg border-2 transition-all hover:scale-105 active:scale-95 ${
|
||||||
localValue.toLowerCase() === preset.toLowerCase() ? 'border-white ring-2 ring-white/30' : 'border-dark-600 hover:border-dark-500'
|
localValue.toLowerCase() === preset.toLowerCase() ? 'border-white ring-2 ring-white/30' : 'border-dark-600 hover:border-dark-500'
|
||||||
}`}
|
}`}
|
||||||
style={{ backgroundColor: preset }}
|
style={{ backgroundColor: preset }}
|
||||||
|
|||||||
@@ -962,31 +962,83 @@
|
|||||||
input[type="range"] {
|
input[type="range"] {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
appearance: none;
|
appearance: none;
|
||||||
height: 8px;
|
height: 12px;
|
||||||
border-radius: 4px;
|
border-radius: 6px;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
touch-action: pan-x;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Desktop: smaller track */
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
input[type="range"] {
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="range"]::-webkit-slider-thumb {
|
input[type="range"]::-webkit-slider-thumb {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
appearance: none;
|
appearance: none;
|
||||||
width: 18px;
|
width: 24px;
|
||||||
height: 18px;
|
height: 24px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: white;
|
background: white;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: 2px solid rgba(0, 0, 0, 0.2);
|
border: 2px solid rgba(0, 0, 0, 0.15);
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(255, 255, 255, 0.1);
|
||||||
|
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"]::-webkit-slider-thumb:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.4), 0 0 0 2px rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"]::-webkit-slider-thumb:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Desktop: smaller thumb */
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
input[type="range"]::-webkit-slider-thumb {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="range"]::-moz-range-thumb {
|
input[type="range"]::-moz-range-thumb {
|
||||||
width: 18px;
|
width: 24px;
|
||||||
height: 18px;
|
height: 24px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: white;
|
background: white;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: 2px solid rgba(0, 0, 0, 0.2);
|
border: 2px solid rgba(0, 0, 0, 0.15);
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(255, 255, 255, 0.1);
|
||||||
|
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"]::-moz-range-thumb:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.4), 0 0 0 2px rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Desktop: smaller thumb */
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
input[type="range"]::-moz-range-thumb {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide number input spinners for cleaner look */
|
||||||
|
input[type="number"]::-webkit-inner-spin-button,
|
||||||
|
input[type="number"]::-webkit-outer-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="number"] {
|
||||||
|
-moz-appearance: textfield;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Selection color */
|
/* Selection color */
|
||||||
|
|||||||
Reference in New Issue
Block a user