Design system.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

94 lines
2.5 KiB

  1. import * as React from 'react';
  2. import clsx from 'clsx';
  3. import Color from 'color';
  4. import * as convert from 'color-convert';
  5. export type SwatchDerivedElement = HTMLInputElement;
  6. type ColorValue = ConstructorParameters<typeof Color>[0];
  7. type ColorMode = keyof typeof convert;
  8. export interface SwatchProps extends Omit<React.HTMLProps<SwatchDerivedElement>, 'color'> {
  9. color: NonNullable<ColorValue>;
  10. mode?: ColorMode;
  11. }
  12. export const useSwatchControls = () => {
  13. const id = React.useId();
  14. const copyColor: React.ReactEventHandler<SwatchDerivedElement> = React.useCallback(async (e) => {
  15. const { value } = e.currentTarget;
  16. await window.navigator.clipboard.writeText(value);
  17. }, []);
  18. return React.useMemo(() => ({
  19. id,
  20. copyColor,
  21. }), [id, copyColor]);
  22. };
  23. export const Swatch = React.forwardRef<SwatchDerivedElement, SwatchProps>(({
  24. // todo unify color and mode into one "value" attribute
  25. color,
  26. mode = 'rgb',
  27. className,
  28. style,
  29. ...etcProps
  30. }, forwardedRef) => {
  31. const { id, copyColor } = useSwatchControls();
  32. const colorInternal = React.useMemo(() => new Color(color, mode), [color, mode]);
  33. const colorValue = colorInternal.hex();
  34. return (
  35. <span
  36. className={clsx(
  37. 'inline-block align-middle',
  38. className,
  39. )}
  40. style={style}
  41. >
  42. <input
  43. {...etcProps}
  44. ref={forwardedRef}
  45. type="text"
  46. value={colorInternal.toString()}
  47. className="sr-only select-all peer"
  48. readOnly
  49. id={id}
  50. onSelect={copyColor}
  51. />
  52. <label
  53. className={clsx(
  54. 'relative rounded ring-secondary/50 whitespace-nowrap inline-block align-top leading-none cursor-pointer', // todo eyedropper cursor
  55. 'peer-focus:outline-0 peer-focus:ring-4',
  56. 'peer-active:ring-tertiary/50',
  57. 'peer-disabled:opacity-50 peer-disabled:cursor-not-allowed',
  58. )}
  59. title={colorValue}
  60. htmlFor={id}
  61. >
  62. {/* todo border primary */}
  63. <span
  64. className="inline-block w-5 h-5 align-middle border border-[#ffffff]"
  65. >
  66. <span
  67. className="block w-full h-full border border-[#000000]"
  68. style={{
  69. backgroundColor: colorInternal.hex(),
  70. }}
  71. />
  72. </span>
  73. <span className="tabular-nums text-xs sr-only">
  74. {colorInternal.toString()}
  75. </span>
  76. </label>
  77. </span>
  78. );
  79. });
  80. Swatch.displayName = 'Swatch';
  81. Swatch.defaultProps = {
  82. mode: 'rgb',
  83. };