ColorPicker.tsx 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. import React from "react";
  2. import { Popover } from "./Popover";
  3. import "./ColorPicker.css";
  4. // This is a narrow reimplementation of the awesome react-color Twitter component
  5. // https://github.com/casesandberg/react-color/blob/master/src/components/twitter/Twitter.js
  6. const Picker = function({
  7. colors,
  8. color,
  9. onChange,
  10. label,
  11. }: {
  12. colors: string[];
  13. color: string | null;
  14. onChange: (color: string) => void;
  15. label: string;
  16. }) {
  17. return (
  18. <div className="color-picker">
  19. <div className="color-picker-triangle-shadow"></div>
  20. <div className="color-picker-triangle"></div>
  21. <div className="color-picker-content">
  22. <div className="colors-gallery">
  23. {colors.map(color => (
  24. <button
  25. className="color-picker-swatch"
  26. onClick={() => {
  27. onChange(color);
  28. }}
  29. title={color}
  30. tabIndex={0}
  31. style={{ backgroundColor: color }}
  32. key={color}
  33. >
  34. {color === "transparent" ? (
  35. <div className="color-picker-transparent"></div>
  36. ) : (
  37. undefined
  38. )}
  39. </button>
  40. ))}
  41. </div>
  42. <ColorInput
  43. color={color}
  44. label={label}
  45. onChange={color => {
  46. onChange(color);
  47. }}
  48. />
  49. </div>
  50. </div>
  51. );
  52. };
  53. function ColorInput({
  54. color,
  55. onChange,
  56. label,
  57. }: {
  58. color: string | null;
  59. onChange: (color: string) => void;
  60. label: string;
  61. }) {
  62. const colorRegex = /^([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8}|transparent)$/;
  63. const [innerValue, setInnerValue] = React.useState(color);
  64. React.useEffect(() => {
  65. setInnerValue(color);
  66. }, [color]);
  67. return (
  68. <div className="color-input-container">
  69. <div className="color-picker-hash">#</div>
  70. <input
  71. spellCheck={false}
  72. className="color-picker-input"
  73. aria-label={label}
  74. onChange={e => {
  75. const value = e.target.value;
  76. if (value.match(colorRegex)) {
  77. onChange(value === "transparent" ? "transparent" : "#" + value);
  78. }
  79. setInnerValue(value);
  80. }}
  81. value={(innerValue || "").replace(/^#/, "")}
  82. onPaste={e => onChange(e.clipboardData.getData("text"))}
  83. onBlur={() => setInnerValue(color)}
  84. />
  85. </div>
  86. );
  87. }
  88. export function ColorPicker({
  89. type,
  90. color,
  91. onChange,
  92. label,
  93. }: {
  94. type: "canvasBackground" | "elementBackground" | "elementStroke";
  95. color: string | null;
  96. onChange: (color: string) => void;
  97. label: string;
  98. }) {
  99. const [isActive, setActive] = React.useState(false);
  100. return (
  101. <div>
  102. <div className="color-picker-control-container">
  103. <button
  104. className="color-picker-label-swatch"
  105. aria-label={label}
  106. style={color ? { backgroundColor: color } : undefined}
  107. onClick={() => setActive(!isActive)}
  108. />
  109. <ColorInput
  110. color={color}
  111. label={label}
  112. onChange={color => {
  113. onChange(color);
  114. }}
  115. />
  116. </div>
  117. <React.Suspense fallback="">
  118. {isActive ? (
  119. <Popover onCloseRequest={() => setActive(false)}>
  120. <Picker
  121. colors={colors[type]}
  122. color={color || null}
  123. onChange={changedColor => {
  124. onChange(changedColor);
  125. }}
  126. label={label}
  127. />
  128. </Popover>
  129. ) : null}
  130. </React.Suspense>
  131. </div>
  132. );
  133. }
  134. // https://yeun.github.io/open-color/
  135. const colors = {
  136. // Shade 0
  137. canvasBackground: [
  138. "#ffffff",
  139. "#f8f9fa",
  140. "#f1f3f5",
  141. "#fff5f5",
  142. "#fff0f6",
  143. "#f8f0fc",
  144. "#f3f0ff",
  145. "#edf2ff",
  146. "#e7f5ff",
  147. "#e3fafc",
  148. "#e6fcf5",
  149. "#ebfbee",
  150. "#f4fce3",
  151. "#fff9db",
  152. "#fff4e6",
  153. ],
  154. // Shade 6
  155. elementBackground: [
  156. "transparent",
  157. "#ced4da",
  158. "#868e96",
  159. "#fa5252",
  160. "#e64980",
  161. "#be4bdb",
  162. "#7950f2",
  163. "#4c6ef5",
  164. "#228be6",
  165. "#15aabf",
  166. "#12b886",
  167. "#40c057",
  168. "#82c91e",
  169. "#fab005",
  170. "#fd7e14",
  171. ],
  172. // Shade 9
  173. elementStroke: [
  174. "#000000",
  175. "#343a40",
  176. "#495057",
  177. "#c92a2a",
  178. "#a61e4d",
  179. "#862e9c",
  180. "#5f3dc4",
  181. "#364fc7",
  182. "#1864ab",
  183. "#0b7285",
  184. "#087f5b",
  185. "#2b8a3e",
  186. "#5c940d",
  187. "#e67700",
  188. "#d9480f",
  189. ],
  190. };