mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-29 18:13:47 +00:00
refactor: unify ColorPicker — remove native input, fix scroll closing
- Remove native <input type="color">, eyedropper button, colorInputRef, isTelegram check and isInTelegramWebApp import from ColorPicker - Remove scroll event listener so picker only closes on click/tap outside - Replace native color input in AdminWheel InlinePrizeForm with ColorPicker
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
import { useState, useRef, useEffect, useMemo, useCallback } from 'react';
|
import { useState, useRef, useEffect, useCallback } from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { isInTelegramWebApp } from '@/hooks/useTelegramSDK';
|
|
||||||
|
|
||||||
interface ColorPickerProps {
|
interface ColorPickerProps {
|
||||||
value: string;
|
value: string;
|
||||||
@@ -111,9 +110,6 @@ export function ColorPicker({ value, onChange, label, description, disabled }: C
|
|||||||
|
|
||||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||||
const pickerRef = useRef<HTMLDivElement>(null);
|
const pickerRef = useRef<HTMLDivElement>(null);
|
||||||
const colorInputRef = useRef<HTMLInputElement>(null);
|
|
||||||
|
|
||||||
const isTelegram = useMemo(() => isInTelegramWebApp(), []);
|
|
||||||
|
|
||||||
// Sync with external value
|
// Sync with external value
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -177,18 +173,15 @@ export function ColorPicker({ value, onChange, label, description, disabled }: C
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleScroll = () => handleClose();
|
|
||||||
const handleResize = () => updatePosition();
|
const handleResize = () => updatePosition();
|
||||||
|
|
||||||
document.addEventListener('mousedown', handleClickOutside);
|
document.addEventListener('mousedown', handleClickOutside);
|
||||||
document.addEventListener('touchstart', handleClickOutside as EventListener);
|
document.addEventListener('touchstart', handleClickOutside as EventListener);
|
||||||
window.addEventListener('scroll', handleScroll, true);
|
|
||||||
window.addEventListener('resize', handleResize);
|
window.addEventListener('resize', handleResize);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('mousedown', handleClickOutside);
|
document.removeEventListener('mousedown', handleClickOutside);
|
||||||
document.removeEventListener('touchstart', handleClickOutside as EventListener);
|
document.removeEventListener('touchstart', handleClickOutside as EventListener);
|
||||||
window.removeEventListener('scroll', handleScroll, true);
|
|
||||||
window.removeEventListener('resize', handleResize);
|
window.removeEventListener('resize', handleResize);
|
||||||
};
|
};
|
||||||
}, [isOpen, handleClose, updatePosition]);
|
}, [isOpen, handleClose, updatePosition]);
|
||||||
@@ -229,18 +222,6 @@ export function ColorPicker({ value, onChange, label, description, disabled }: C
|
|||||||
[hsl, updateColorFromHsl],
|
[hsl, updateColorFromHsl],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Handle native color input
|
|
||||||
const handleColorInputChange = useCallback(
|
|
||||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const newColor = e.target.value;
|
|
||||||
setLocalValue(newColor);
|
|
||||||
const rgb = hexToRgb(newColor);
|
|
||||||
setHsl(rgbToHsl(rgb.r, rgb.g, rgb.b));
|
|
||||||
onChange(newColor);
|
|
||||||
},
|
|
||||||
[onChange],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Handle hex input
|
// Handle hex input
|
||||||
const handleHexInputChange = useCallback(
|
const handleHexInputChange = useCallback(
|
||||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
@@ -409,41 +390,6 @@ export function ColorPicker({ value, onChange, label, description, disabled }: C
|
|||||||
placeholder="#000000"
|
placeholder="#000000"
|
||||||
maxLength={7}
|
maxLength={7}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Native color picker button (hidden in Telegram) */}
|
|
||||||
{!isTelegram && (
|
|
||||||
<>
|
|
||||||
<input
|
|
||||||
ref={colorInputRef}
|
|
||||||
type="color"
|
|
||||||
value={localValue || '#000000'}
|
|
||||||
onChange={handleColorInputChange}
|
|
||||||
disabled={disabled}
|
|
||||||
className="sr-only"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => colorInputRef.current?.click()}
|
|
||||||
disabled={disabled}
|
|
||||||
className="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-xl border border-dark-700 bg-dark-800 text-dark-400 transition-colors hover:bg-dark-700 hover:text-dark-200 disabled:opacity-50"
|
|
||||||
title="System color picker"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
className="h-5 w-5"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth={1.5}
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
d="M4.098 19.902a3.75 3.75 0 005.304 0l6.401-6.402M6.75 21A3.75 3.75 0 013 17.25V4.125C3 3.504 3.504 3 4.125 3h5.25c.621 0 1.125.504 1.125 1.125v4.072M6.75 21a3.75 3.75 0 003.75-3.75V8.197M6.75 21h13.125c.621 0 1.125-.504 1.125-1.125v-5.25c0-.621-.504-1.125-1.125-1.125h-4.072M10.5 8.197l2.88-2.88c.438-.439 1.15-.439 1.59 0l3.712 3.713c.44.44.44 1.152 0 1.59l-2.879 2.88M6.75 17.25h.008v.008H6.75v-.008z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Render picker in portal */}
|
{/* Render picker in portal */}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import { CSS } from '@dnd-kit/utilities';
|
|||||||
import { adminWheelApi, type WheelPrizeAdmin, type CreateWheelPrizeData } from '../api/wheel';
|
import { adminWheelApi, type WheelPrizeAdmin, type CreateWheelPrizeData } from '../api/wheel';
|
||||||
import { useDestructiveConfirm } from '@/platform';
|
import { useDestructiveConfirm } from '@/platform';
|
||||||
import FortuneWheel from '../components/wheel/FortuneWheel';
|
import FortuneWheel from '../components/wheel/FortuneWheel';
|
||||||
|
import { ColorPicker } from '@/components/ColorPicker';
|
||||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||||
|
|
||||||
@@ -1146,26 +1147,11 @@ function InlinePrizeForm({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Color */}
|
{/* Color */}
|
||||||
<div>
|
<ColorPicker
|
||||||
<label className="mb-1 block text-sm font-medium text-dark-300">
|
label={t('admin.wheel.prizes.fields.color')}
|
||||||
{t('admin.wheel.prizes.fields.color')}
|
value={formData.color}
|
||||||
</label>
|
onChange={(color) => setFormData({ ...formData, color })}
|
||||||
<div className="flex gap-2">
|
/>
|
||||||
<input
|
|
||||||
type="color"
|
|
||||||
value={formData.color}
|
|
||||||
onChange={(e) => setFormData({ ...formData, color: e.target.value })}
|
|
||||||
className="h-10 w-12 cursor-pointer rounded"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={formData.color}
|
|
||||||
onChange={(e) => setFormData({ ...formData, color: e.target.value })}
|
|
||||||
className="input flex-1"
|
|
||||||
pattern="^#[0-9A-Fa-f]{6}$"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Active toggle */}
|
{/* Active toggle */}
|
||||||
|
|||||||
Reference in New Issue
Block a user