All files / packages/design-system-2/.storybook/addons/theme-builder/src/components ColorPicker.jsx

0% Statements 0/47
0% Branches 0/44
0% Functions 0/13
0% Lines 0/44

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133                                                                                                                                                                                                                                                                         
import React, { useEffect, useMemo, useState, useCallback, useRef } from 'react';
 
// Debounce utility function
const useDebounce = (callback, delay) => {
  const timeoutRef = useRef(null);
 
  useEffect(() => {
    // Cleanup on unmount
    return () => {
      if (timeoutRef.current) {
        clearTimeout(timeoutRef.current);
      }
    };
  }, []);
 
  return useCallback(
    (...args) => {
      if (timeoutRef.current) {
        clearTimeout(timeoutRef.current);
      }
 
      timeoutRef.current = setTimeout(() => {
        callback(...args);
      }, delay);
    },
    [callback, delay],
  );
};
 
// Simple hex utilities
const toHashHex = (hex) => {
  if (!hex) return null;
  const v = hex.trim();
  const withHash = v.startsWith('#') ? v : `#${v}`;
  return /^#([0-9a-fA-F]{6})$/.test(withHash) ? withHash.toUpperCase() : null;
};
 
const fromColorInput = (value) => {
  // input type=color always yields #rrggbb (lowercase). Normalize to uppercase.
  if (typeof value !== 'string') return null;
  return /^#[0-9a-f]{6}$/.test(value) ? value.toUpperCase() : toHashHex(value);
};
 
// SLDS-styled ColorPicker with native input type="color" and a synced hex input
const ColorPicker = ({ id, value, onChange, label = 'Color', className = '' }) => {
  const initial = toHashHex(value) || '#5679C0';
  const [colorHex, setColorHex] = useState(initial);
  const [hexText, setHexText] = useState(initial);
 
  // Sync internal state when parent value changes
  useEffect(() => {
    const norm = toHashHex(value);
    if (norm && norm !== colorHex) {
      setColorHex(norm);
      setHexText(norm);
    }
  }, [value]);
 
  const hasError = useMemo(() => !!hexText && !toHashHex(hexText), [hexText]);
 
  // Create debounced version of onChange
  const debouncedOnChange = useDebounce((value) => {
    onChange && onChange(value);
  }, 500);
 
  const handleColorChange = (e) => {
    const next = fromColorInput(e.target.value) || '#000000';
    setColorHex(next);
    setHexText(next);
    debouncedOnChange(next);
  };
 
  const handleHexInputChange = (e) => {
    const nextText = e.target.value;
    setHexText(nextText);
    const normalized = toHashHex(nextText);
    if (normalized) {
      setColorHex(normalized);
      debouncedOnChange(normalized);
    }
  };
 
  return (
    <div className={`slds-color-picker ${className}`.trim()}>
      <div className="slds-grid slds-align_absolute-center">
        <div className="slds-col">
          <div className="slds-form-element">
            <label className="slds-form-element__label" htmlFor={id ? `${id}-color` : 'color-input'}>
              {label}
            </label>
            <div className="slds-grid slds-gutters_x-small slds-align_absolute-center">
              <div className="slds-form-element__control slds-m-right_x-small">
                <input
                  id={id ? `${id}-color` : 'color-input'}
                  type="color"
                  className="slds-p-horizontal_xx-small"
                  value={colorHex}
                  onChange={handleColorChange}
                  aria-label="Choose color"
                  style={{
                    width: 'var(--slds-g-sizing-10)',
                    minWidth: 'var(--slds-g-sizing-10)',
                    height: 'var(--slds-g-sizing-9)',
                    borderRadius: 'var(--slds-g-radius-border-1)',
                  }}
                />
              </div>
              <div className="slds-form-element__control">
                <input
                  id={id ? `${id}-hex` : 'hex-input'}
                  type="text"
                  className="slds-input"
                  value={hexText}
                  onChange={handleHexInputChange}
                  placeholder="#RRGGBB"
                  aria-describedby={hasError ? (id ? `${id}-hex-error` : 'hex-error') : undefined}
                />
              </div>
              {hasError ? (
                <div className="slds-form-element__help" id={id ? `${id}-hex-error` : 'hex-error'}>
                  Enter a valid hex color like #FFAA00
                </div>
              ) : null}
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};
 
export default ColorPicker;