CustomColorPicker.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. import React, { useState } from "react";
  2. import {
  3. ColorPicker,
  4. Popover,
  5. Dropdown,
  6. Button,
  7. Space,
  8. Tooltip,
  9. InputNumber,
  10. Input,
  11. } from "antd";
  12. import type { ColorPickerProps } from "antd";
  13. import noColorImg from "@/assets/icon/no-color.png";
  14. import {
  15. ArrowRightOutlined,
  16. CaretDownOutlined,
  17. CheckOutlined,
  18. SwapOutlined,
  19. } from "@ant-design/icons";
  20. import morePng from "@/assets/icon/more-color.png";
  21. import insertCss from "insert-css";
  22. import { useModel } from "umi";
  23. // 系统色
  24. const presetColors1 = [
  25. "#EFECEB",
  26. "#F2F2F2",
  27. "#E7EBED",
  28. "#FADCDB",
  29. "#FBEADA",
  30. "#FCF9EA",
  31. "#E5F6DA",
  32. "#DBF5F5",
  33. "#D2D6F9",
  34. "#FADDED",
  35. "#DED9D7",
  36. "#D9D9D9",
  37. "#E0E0E0",
  38. "#F5B9B7",
  39. "#F8D5B5",
  40. "#F6EDC1",
  41. "#CAEDB4",
  42. "#B7EAEB",
  43. "#A6AEF3",
  44. "#F6BBDB",
  45. "#BEB3AF",
  46. "#BFBFBF",
  47. "#9E9E9E",
  48. "#F19594",
  49. "#F4C18F",
  50. "#F1E4A2",
  51. "#B0E38F",
  52. "#94E0E1",
  53. "#7985EC",
  54. "#F199C8",
  55. "#9D8C88",
  56. "#A6A6A6",
  57. "#616161",
  58. "#EC7270",
  59. "#F1AC6A",
  60. "#E9D66F",
  61. "#95DA69",
  62. "#70D5D7",
  63. "#5B79E8",
  64. "#ED77B6",
  65. "#5C4038",
  66. "#7F7F7F",
  67. "#262626",
  68. "#A23735",
  69. "#A66A30",
  70. "#A7932C",
  71. "#569230",
  72. "#358E90",
  73. "#314AA4",
  74. "#A23C73",
  75. ];
  76. // 莫兰迪
  77. const presetColors2 = [
  78. "#F1F1E8",
  79. "#ECECED",
  80. "#F3FAF9",
  81. "#F4F0EA",
  82. "#F9FAEE",
  83. "#F8F9F5",
  84. "#F5E9ED",
  85. "#FCE7EB",
  86. "#F9EEE1",
  87. "#FEF5EF",
  88. "#E3E2D1",
  89. "#D9DADB",
  90. "#E8F5F4",
  91. "#E9E1D5",
  92. "#F3F6DD",
  93. "#F0F3EC",
  94. "#EBD2DC",
  95. "#F9CFD7",
  96. "#F3DDC3",
  97. "#FDEBDF",
  98. "#D6D4BA",
  99. "#C5C7CA",
  100. "#DCEFEE",
  101. "#DDD1C1",
  102. "#EEF1CB",
  103. "#E9ECE2",
  104. "#E0BCCA",
  105. "#F6B6C4",
  106. "#EDCCA5",
  107. "#FCE2CF",
  108. "#C8C5A3",
  109. "#B2B5B8",
  110. "#D1EAE9",
  111. "#D2C2AC",
  112. "#E8EDBA",
  113. "#E1E6D9",
  114. "#D6A5B9",
  115. "#F39EB0",
  116. "#E7BB87",
  117. "#FBD8BF",
  118. "#959270",
  119. "#7F8285",
  120. "#9EB7B6",
  121. "#9F8F79",
  122. "#B5BA87",
  123. "#AEB3A6",
  124. "#A37286",
  125. "#C06B7D",
  126. "#B48854",
  127. "#C8A58C",
  128. ];
  129. // 中国风
  130. const presetColors3 = [
  131. "#866B50",
  132. "#705138",
  133. "#5A5645",
  134. "#5C3719",
  135. "#775550",
  136. "#5A1216",
  137. "#B0913E",
  138. "#964D22",
  139. "#E28342",
  140. "#C37940",
  141. "#2C2305",
  142. "#645822",
  143. "#6B5E4C",
  144. "#556980",
  145. "#70887D",
  146. "#5B8483",
  147. "#975F42",
  148. "#A2825E",
  149. "#DDAB4C",
  150. "#91A45A",
  151. "#4D5255",
  152. "#587D8C",
  153. "#737C7B",
  154. "#B1AD94",
  155. "#ADB4A9",
  156. "#467E7D",
  157. "#94B68E",
  158. "#894276",
  159. "#C36077",
  160. "#D59482",
  161. "#144A74",
  162. "#495C69",
  163. "#314656",
  164. "#134857",
  165. "#7E8489",
  166. "#8F927F",
  167. "#B2BBBE",
  168. "#67907C",
  169. "#539271",
  170. "#D2B116",
  171. "#322F3B",
  172. "#525288",
  173. "#8076A3",
  174. "#1A94BC",
  175. "#5D3131",
  176. "#314A43",
  177. "#C1651A",
  178. "#DE9E44",
  179. "#D2B116",
  180. "#D2D97A",
  181. ];
  182. // 潘通色
  183. const presetColors4 = [
  184. "#E3F0E1",
  185. "#EEE7F1",
  186. "#E4D5D8",
  187. "#FDF8DC",
  188. "#DFE3F5",
  189. "#FFF1D5",
  190. "#DFE5DB",
  191. "#E2EFF5",
  192. "#DEE6E6",
  193. "#F9DDD6",
  194. "#C7E1C3",
  195. "#DED0E4",
  196. "#CAAAB1",
  197. "#FBF1B8",
  198. "#BFC7EA",
  199. "#FEE2AB",
  200. "#BFCAB6",
  201. "#C6DFEA",
  202. "#BDCECD",
  203. "#F3BBAD",
  204. "#ABD1A6",
  205. "#CDB8D6",
  206. "#AF808A",
  207. "#F8EB95",
  208. "#9EAAE0",
  209. "#FED480",
  210. "#A0B092",
  211. "#A9CEE0",
  212. "#9DB5B5",
  213. "#ED9984",
  214. "#8FC288",
  215. "#BDA1C9",
  216. "#955563",
  217. "#F6E471",
  218. "#7E8ED5",
  219. "#FDC556",
  220. "#80956D",
  221. "#8DBED5",
  222. "#7C9D9C",
  223. "#E7775B",
  224. "#5C8F55",
  225. "#8A6E96",
  226. "#622230",
  227. "#C3B13E",
  228. "#4B5BA2",
  229. "#CA9223",
  230. "#4D623A",
  231. "#5A8BA2",
  232. "#496A69",
  233. "#B44428",
  234. ];
  235. const baseColor = [
  236. "#FFFFFF",
  237. "#7F8B98",
  238. "#000000",
  239. "#E74F4C",
  240. "#ED9745",
  241. "#E0C431",
  242. "#7BD144",
  243. "#4CCBCD",
  244. "#4669EA",
  245. "#E855A4",
  246. ];
  247. const colorTxts = ["系统色", "莫兰迪", "中国风", "潘通色"];
  248. const colors = [presetColors1, presetColors2, presetColors3, presetColors4];
  249. export default function CustomColorPicker(props: {
  250. onChange?: (color: string) => void;
  251. color?: string;
  252. pickerOption?: ColorPickerProps;
  253. children?: React.ReactNode;
  254. disabled?: boolean;
  255. otherPopoverAttr?: Partial<Parameters<typeof Popover>[0]>;
  256. }) {
  257. const { color } = props;
  258. const [colorSystem, setColorSystem] = useState(0);
  259. const [colorType, setColorType] = useState<"rgb" | "hex">("rgb");
  260. const [open, setOpen] = useState(false);
  261. const { historyColor, addHistoryCoolor } = useModel('appModel');
  262. // 修改值
  263. const onChange = (value: string) => {
  264. props.onChange?.(value);
  265. handleOpenChange(false);
  266. addHistoryCoolor(value);
  267. };
  268. insertCss(`
  269. .rgb .shalu-input-number-input {
  270. padding: 0 !important;
  271. }
  272. `);
  273. const handleSystemAbsorption = () => {
  274. // @ts-ignore 系统拾色
  275. const dropper = new EyeDropper();
  276. dropper.open().then((result: { sRGBHex: string }) => {
  277. onChange(result.sRGBHex);
  278. });
  279. };
  280. const handleChangeColorType = () => {
  281. setColorType(colorType === "rgb" ? "hex" : "rgb");
  282. };
  283. // hex转rgb
  284. const hexToRGB = (hex?: string) => {
  285. const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex || "");
  286. return result
  287. ? {
  288. r: parseInt(result[1], 16),
  289. g: parseInt(result[2], 16),
  290. b: parseInt(result[3], 16),
  291. }
  292. : {
  293. r: 255,
  294. g: 255,
  295. b: 255,
  296. };
  297. };
  298. const handleChangeRGB = (value: number | null, type: "r" | "g" | "b") => {
  299. const rgb = hexToRGB(color);
  300. const val = value?.toString(16);
  301. onChange(
  302. `${type === "r" ? val : rgb.r}${type === "g" ? val : rgb.g}${type === "b" ? val : rgb.b}`
  303. );
  304. };
  305. const handleOpenChange = (newOpen: boolean) => {
  306. setOpen(newOpen && !props.disabled);
  307. };
  308. const PopoverContent = () => {
  309. return (
  310. <div className="w-200px">
  311. <Dropdown
  312. menu={{
  313. items: colorTxts.map((item, index) => {
  314. return {
  315. key: index,
  316. label: (
  317. <span onClick={() => setColorSystem(index)}>
  318. <span className="inline-block w-24px">
  319. {colorSystem === index && <CheckOutlined />}
  320. </span>
  321. {item}
  322. </span>
  323. ),
  324. };
  325. }),
  326. }}
  327. >
  328. <div>
  329. {colorTxts[colorSystem]}
  330. <CaretDownOutlined />
  331. </div>
  332. </Dropdown>
  333. <Button
  334. size="small"
  335. className="block w-full my-8px"
  336. onClick={() => onChange("transparent")}
  337. >
  338. 无填充
  339. </Button>
  340. <Space wrap size={4} className="justify-between">
  341. {[...colors[colorSystem], ...baseColor].map((item, index) => {
  342. return (
  343. <Tooltip title={item} key={index}>
  344. <div
  345. className="w-14px h-14px border border-solid border-#00000000 border-radius-2px shadow hover:shadow-[0_0_4px_0_rgba(0,0,0,0.3)]"
  346. style={{ background: item }}
  347. onClick={() => onChange(item)}
  348. />
  349. </Tooltip>
  350. );
  351. })}
  352. </Space>
  353. <div className="text-12px text-#666666 my-8px">最近使用</div>
  354. <Space wrap size={4} className="justify-between">
  355. {[...historyColor].map((item, index) => {
  356. return (
  357. <Tooltip title={item} key={index}>
  358. <div
  359. className="w-14px h-14px border border-solid border-#00000000 border-radius-2px shadow hover:shadow-[0_0_4px_0_rgba(0,0,0,0.3)]"
  360. style={{ background: item }}
  361. onClick={() => onChange(item)}
  362. />
  363. </Tooltip>
  364. );
  365. })}
  366. </Space>
  367. <div className="flex items-center justify-center mt-8px">
  368. <Button
  369. type="text"
  370. size="small"
  371. icon={<i className="iconfont icon-xise" />}
  372. onClick={handleSystemAbsorption}
  373. />
  374. <Button type="text" size="small" onClick={handleChangeColorType}>
  375. {colorType === "rgb" ? "RGB" : "HEX"}
  376. <SwapOutlined />
  377. </Button>
  378. {colorType === "rgb" ? (
  379. <div className="flex rgb">
  380. <InputNumber
  381. size="small"
  382. max={255}
  383. min={0}
  384. precision={0}
  385. className="w-30px px-0"
  386. controls={false}
  387. value={hexToRGB(color).r}
  388. onChange={(value) => handleChangeRGB(value, "r")}
  389. />
  390. <InputNumber
  391. size="small"
  392. max={255}
  393. min={0}
  394. precision={0}
  395. className="w-30px"
  396. controls={false}
  397. value={hexToRGB(color).g}
  398. onChange={(value) => handleChangeRGB(value, "g")}
  399. />
  400. <InputNumber
  401. size="small"
  402. max={255}
  403. min={0}
  404. precision={0}
  405. className="w-30px"
  406. controls={false}
  407. value={hexToRGB(color).b}
  408. onChange={(value) => handleChangeRGB(value, "b")}
  409. />
  410. </div>
  411. ) : (
  412. <Input maxLength={6} pattern="[0-9a-fA-F]{6}" />
  413. )}
  414. <Tooltip title="当前颜色">
  415. <div
  416. className="ml-4px w-14px h-14px border border-solid border-#00000010 border-radius-2px"
  417. style={{ background: color || "#ffffff" }}
  418. />
  419. </Tooltip>
  420. </div>
  421. <div className="my-8px border border-transparent border-1px border-solid border-b-#e9edf2" />
  422. <ColorPicker
  423. value={color}
  424. disabledAlpha
  425. format="hex"
  426. onChange={(value) => onChange(value.toHexString())}
  427. className="w-0 h-0"
  428. >
  429. <Button
  430. type="text"
  431. className="w-full flex justify-between items-center"
  432. >
  433. <span className="flex-important items-center">
  434. <img className="w-16px h-16px mr-8px" src={morePng} alt="" />
  435. <span className="text-size-12px">更多颜色</span>
  436. </span>
  437. <ArrowRightOutlined />
  438. </Button>
  439. </ColorPicker>
  440. </div>
  441. );
  442. };
  443. const backgroundStyle =
  444. color === "transparent"
  445. ? { background: `url(${noColorImg})`, backgroundSize: "cover" }
  446. : { background: color };
  447. const popoverAttrs = props.otherPopoverAttr || {};
  448. return (
  449. <div className="inline-block">
  450. <Popover
  451. autoAdjustOverflow
  452. destroyTooltipOnHide
  453. content={<PopoverContent />}
  454. trigger="click"
  455. placement="bottom"
  456. arrow={false}
  457. open={open}
  458. onOpenChange={handleOpenChange}
  459. {...popoverAttrs}
  460. >
  461. {props.children ? (
  462. props.children
  463. ) : (
  464. <div
  465. className="w-30px h-24px border border-solid border-#435f7329 border-radius-2px"
  466. style={{
  467. ...backgroundStyle,
  468. opacity: props.disabled ? 0.5 : 1
  469. }}
  470. />
  471. )}
  472. </Popover>
  473. </div>
  474. );
  475. }