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.
 
 
 

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