Picker.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. import React, { useEffect, useState } from "react";
  2. import { t } from "../../i18n";
  3. import { ExcalidrawElement } from "../../element/types";
  4. import { ShadeList } from "./ShadeList";
  5. import PickerColorList from "./PickerColorList";
  6. import { useAtom } from "jotai";
  7. import { CustomColorList } from "./CustomColorList";
  8. import { colorPickerKeyNavHandler } from "./keyboardNavHandlers";
  9. import PickerHeading from "./PickerHeading";
  10. import {
  11. ColorPickerType,
  12. activeColorPickerSectionAtom,
  13. getColorNameAndShadeFromHex,
  14. getMostUsedCustomColors,
  15. isCustomColor,
  16. } from "./colorPickerUtils";
  17. import {
  18. ColorPaletteCustom,
  19. DEFAULT_ELEMENT_BACKGROUND_COLOR_INDEX,
  20. DEFAULT_ELEMENT_STROKE_COLOR_INDEX,
  21. } from "../../colors";
  22. interface PickerProps {
  23. color: string | null;
  24. onChange: (color: string) => void;
  25. label: string;
  26. type: ColorPickerType;
  27. elements: readonly ExcalidrawElement[];
  28. palette: ColorPaletteCustom;
  29. updateData: (formData?: any) => void;
  30. children?: React.ReactNode;
  31. }
  32. export const Picker = ({
  33. color,
  34. onChange,
  35. label,
  36. type,
  37. elements,
  38. palette,
  39. updateData,
  40. children,
  41. }: PickerProps) => {
  42. const [customColors] = React.useState(() => {
  43. if (type === "canvasBackground") {
  44. return [];
  45. }
  46. return getMostUsedCustomColors(elements, type, palette);
  47. });
  48. const [activeColorPickerSection, setActiveColorPickerSection] = useAtom(
  49. activeColorPickerSectionAtom,
  50. );
  51. const colorObj = getColorNameAndShadeFromHex({
  52. hex: color || "transparent",
  53. palette,
  54. });
  55. useEffect(() => {
  56. if (!activeColorPickerSection) {
  57. const isCustom = isCustomColor({ color, palette });
  58. const isCustomButNotInList =
  59. isCustom && !customColors.includes(color || "");
  60. setActiveColorPickerSection(
  61. isCustomButNotInList
  62. ? "hex"
  63. : isCustom
  64. ? "custom"
  65. : colorObj?.shade != null
  66. ? "shades"
  67. : "baseColors",
  68. );
  69. }
  70. }, [
  71. activeColorPickerSection,
  72. color,
  73. palette,
  74. setActiveColorPickerSection,
  75. colorObj,
  76. customColors,
  77. ]);
  78. const [activeShade, setActiveShade] = useState(
  79. colorObj?.shade ??
  80. (type === "elementBackground"
  81. ? DEFAULT_ELEMENT_BACKGROUND_COLOR_INDEX
  82. : DEFAULT_ELEMENT_STROKE_COLOR_INDEX),
  83. );
  84. useEffect(() => {
  85. if (colorObj?.shade != null) {
  86. setActiveShade(colorObj.shade);
  87. }
  88. }, [colorObj]);
  89. return (
  90. <div role="dialog" aria-modal="true" aria-label={t("labels.colorPicker")}>
  91. <div
  92. onKeyDown={(e) => {
  93. e.preventDefault();
  94. e.stopPropagation();
  95. colorPickerKeyNavHandler({
  96. e,
  97. activeColorPickerSection,
  98. palette,
  99. hex: color,
  100. onChange,
  101. customColors,
  102. setActiveColorPickerSection,
  103. updateData,
  104. activeShade,
  105. });
  106. }}
  107. className="color-picker-content"
  108. // to allow focusing by clicking but not by tabbing
  109. tabIndex={-1}
  110. >
  111. {!!customColors.length && (
  112. <div>
  113. <PickerHeading>
  114. {t("colorPicker.mostUsedCustomColors")}
  115. </PickerHeading>
  116. <CustomColorList
  117. colors={customColors}
  118. color={color}
  119. label={t("colorPicker.mostUsedCustomColors")}
  120. onChange={onChange}
  121. />
  122. </div>
  123. )}
  124. <div>
  125. <PickerHeading>{t("colorPicker.colors")}</PickerHeading>
  126. <PickerColorList
  127. color={color}
  128. label={label}
  129. palette={palette}
  130. onChange={onChange}
  131. activeShade={activeShade}
  132. />
  133. </div>
  134. <div>
  135. <PickerHeading>{t("colorPicker.shades")}</PickerHeading>
  136. <ShadeList hex={color} onChange={onChange} palette={palette} />
  137. </div>
  138. {children}
  139. </div>
  140. </div>
  141. );
  142. };