@tendaui/components 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (270) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +176 -176
  3. package/alert/Alert.tsx +3 -2
  4. package/button/_example/base.tsx +10 -0
  5. package/button/_example/icon.tsx +20 -0
  6. package/color-picker/ColorPickPanel.tsx +9 -0
  7. package/color-picker/ColorPicker.tsx +67 -0
  8. package/color-picker/components/panel/alpha.tsx +32 -0
  9. package/color-picker/components/panel/format/index.tsx +47 -0
  10. package/color-picker/components/panel/format/inputs.tsx +119 -0
  11. package/color-picker/components/panel/header.tsx +37 -0
  12. package/color-picker/components/panel/hue.tsx +20 -0
  13. package/color-picker/components/panel/index.tsx +191 -0
  14. package/color-picker/components/panel/saturation.tsx +81 -0
  15. package/color-picker/components/panel/slider.tsx +76 -0
  16. package/color-picker/components/panel/swatches.tsx +84 -0
  17. package/color-picker/components/trigger.tsx +49 -0
  18. package/color-picker/defaultProps.ts +7 -0
  19. package/color-picker/helpers.ts +53 -0
  20. package/color-picker/hooks/useClassNames.ts +9 -0
  21. package/color-picker/hooks/useStyles.ts +39 -0
  22. package/color-picker/index.ts +12 -0
  23. package/color-picker/style/css.js +1 -0
  24. package/color-picker/style/index.js +1 -0
  25. package/color-picker/type.ts +143 -0
  26. package/color-picker/utils/color-picker/cmyk.ts +89 -0
  27. package/color-picker/utils/color-picker/color.ts +467 -0
  28. package/color-picker/utils/color-picker/constants.ts +187 -0
  29. package/color-picker/utils/color-picker/draggable.ts +100 -0
  30. package/color-picker/utils/color-picker/format.ts +95 -0
  31. package/color-picker/utils/color-picker/gradient.ts +243 -0
  32. package/color-picker/utils/color-picker/index.ts +7 -0
  33. package/color-picker/utils/color-picker/types.ts +33 -0
  34. package/common/observe.ts +33 -0
  35. package/common.ts +20 -0
  36. package/config-provider/ConfigContext.tsx +4 -1
  37. package/config-provider/index.ts +1 -1
  38. package/dialog/DialogCard.tsx +4 -6
  39. package/dialog/hooks/useDialogPosition.ts +1 -2
  40. package/dialog/plugin.tsx +3 -2
  41. package/drawer/Drawer.tsx +264 -0
  42. package/drawer/defaultProps.ts +19 -0
  43. package/drawer/hooks/useDrag.ts +98 -0
  44. package/drawer/hooks/useLockStyle.ts +36 -0
  45. package/drawer/index.ts +5 -0
  46. package/drawer/style/css.js +1 -0
  47. package/drawer/style/index.js +1 -0
  48. package/drawer/type.ts +193 -0
  49. package/drawer/utils/index.ts +76 -0
  50. package/fireworks/Fireworks.tsx +138 -0
  51. package/fireworks/index.ts +10 -0
  52. package/fireworks/style/css.js +0 -0
  53. package/fireworks/style/index.js +0 -0
  54. package/fireworks/type.ts +72 -0
  55. package/form/FormItem.tsx +5 -5
  56. package/form/easing.ts +10 -0
  57. package/form/scroll.ts +124 -0
  58. package/form/type.ts +519 -519
  59. package/global-config/default-config.ts +95 -0
  60. package/global-config/locale/ar_KW.ts +270 -0
  61. package/global-config/locale/en_US.ts +280 -0
  62. package/global-config/locale/it_IT.ts +287 -0
  63. package/global-config/locale/ja_JP.ts +279 -0
  64. package/global-config/locale/ko_KR.ts +279 -0
  65. package/global-config/locale/ru_RU.ts +288 -0
  66. package/global-config/locale/zh_CN.ts +279 -0
  67. package/global-config/locale/zh_TW.ts +279 -0
  68. package/global-config/mobile/default-config.ts +6 -0
  69. package/global-config/mobile/locale/ar_KW.ts +113 -0
  70. package/global-config/mobile/locale/en_US.ts +114 -0
  71. package/global-config/mobile/locale/it_IT.ts +114 -0
  72. package/global-config/mobile/locale/ja_JP.ts +101 -0
  73. package/global-config/mobile/locale/ko_KR.ts +101 -0
  74. package/global-config/mobile/locale/ru_RU.ts +113 -0
  75. package/global-config/mobile/locale/zh_CN.ts +101 -0
  76. package/global-config/mobile/locale/zh_TW.ts +101 -0
  77. package/global-config/t.ts +111 -0
  78. package/hooks/useControlled.ts +3 -3
  79. package/hooks/useDeepEffect.ts +32 -0
  80. package/hooks/useGlobalIcon.ts +10 -3
  81. package/hooks/useLastest.ts +2 -6
  82. package/hooks/useResizeObserve.ts +36 -0
  83. package/index.ts +10 -7
  84. package/input/Input.tsx +4 -1
  85. package/input/defaultProps.ts +0 -2
  86. package/input/type.ts +1 -6
  87. package/input-number/InputNumber.tsx +124 -0
  88. package/input-number/defaultProps.ts +17 -0
  89. package/input-number/index.ts +9 -0
  90. package/input-number/style/css.js +1 -0
  91. package/input-number/style/index.js +1 -0
  92. package/input-number/type.ts +147 -0
  93. package/input-number/useInputNumber.tsx +270 -0
  94. package/ip-input/IPInput.tsx +516 -0
  95. package/ip-input/defaultProps.ts +11 -0
  96. package/ip-input/index.ts +3 -0
  97. package/ip-input/style/css.js +1 -0
  98. package/ip-input/style/index.js +1 -0
  99. package/ip-input/type.ts +115 -0
  100. package/ip-input/utils.ts +112 -0
  101. package/layout/Aside.tsx +38 -0
  102. package/layout/Layout.tsx +104 -0
  103. package/layout/defaultProps.ts +9 -0
  104. package/layout/index.ts +9 -0
  105. package/layout/style/css.js +1 -0
  106. package/layout/style/index.js +1 -0
  107. package/layout/type.ts +43 -0
  108. package/list/List.tsx +144 -0
  109. package/list/ListItem.tsx +36 -0
  110. package/list/ListItemMeta.tsx +40 -0
  111. package/list/defaultProps.ts +11 -0
  112. package/list/hooks/useListVirtualScroll.ts +82 -0
  113. package/list/index.ts +11 -0
  114. package/list/style/css.js +1 -0
  115. package/list/style/index.js +1 -0
  116. package/list/type.ts +93 -0
  117. package/locale/LocalReceiver.ts +55 -0
  118. package/locale/ar_KW.ts +7 -0
  119. package/locale/en_US.ts +7 -0
  120. package/locale/it_IT.ts +6 -0
  121. package/locale/ja_JP.ts +6 -0
  122. package/locale/ko_KR.ts +6 -0
  123. package/locale/ru_RU.ts +6 -0
  124. package/locale/zh_CN.ts +5 -0
  125. package/locale/zh_TW.ts +7 -0
  126. package/notification/NotifyContainer.tsx +2 -2
  127. package/notification/NotifyContext.tsx +1 -0
  128. package/package.json +6 -3
  129. package/popup/Popup.tsx +34 -10
  130. package/radio/Radio.tsx +24 -0
  131. package/radio/RadioGroup.tsx +159 -0
  132. package/radio/defaultProps.ts +18 -0
  133. package/radio/index.ts +12 -0
  134. package/radio/style/css.js +0 -0
  135. package/radio/style/index.js +1 -0
  136. package/radio/type.ts +115 -0
  137. package/radio/useKeyboard.ts +36 -0
  138. package/select/hooks/useOptions.ts +10 -7
  139. package/select/hooks/usePanelVirtualScroll.ts +1 -1
  140. package/select/type.ts +382 -382
  141. package/select-input/type.ts +280 -280
  142. package/slider/Slider.tsx +270 -0
  143. package/slider/SliderHandleButton.tsx +50 -0
  144. package/slider/defaultProps.ts +15 -0
  145. package/slider/index.ts +9 -0
  146. package/slider/style/css.js +1 -0
  147. package/slider/style/index.js +1 -0
  148. package/slider/type.ts +77 -0
  149. package/style/all.js +26 -0
  150. package/styles/_global.scss +39 -39
  151. package/styles/_vars.scss +358 -386
  152. package/styles/components/alert/_index.scss +175 -175
  153. package/styles/components/alert/_vars.scss +39 -39
  154. package/styles/components/badge/_index.scss +70 -70
  155. package/styles/components/badge/_vars.scss +25 -25
  156. package/styles/components/button/_index.scss +499 -511
  157. package/styles/components/button/_mixins.scss +39 -39
  158. package/styles/components/button/_vars.scss +120 -122
  159. package/styles/components/checkbox/_index.scss +158 -158
  160. package/styles/components/checkbox/_var.scss +60 -60
  161. package/styles/components/color-picker/_index.scss +586 -0
  162. package/styles/components/color-picker/_mixins.scss +0 -0
  163. package/styles/components/color-picker/_vars.scss +84 -0
  164. package/styles/components/dialog/_animate.scss +135 -135
  165. package/styles/components/dialog/_index.scss +311 -311
  166. package/styles/components/dialog/_vars.scss +59 -59
  167. package/styles/components/drawer/_index.scss +205 -0
  168. package/styles/components/drawer/_mixins.scss +1 -0
  169. package/styles/components/drawer/_var.scss +53 -0
  170. package/styles/components/fireworks/_index.scss +86 -0
  171. package/styles/components/fireworks/_vars.scss +4 -0
  172. package/styles/components/form/_index.scss +174 -174
  173. package/styles/components/form/_mixins.scss +76 -76
  174. package/styles/components/form/_vars.scss +100 -100
  175. package/styles/components/input/_index.scss +349 -349
  176. package/styles/components/input/_mixins.scss +116 -116
  177. package/styles/components/input/_vars.scss +134 -134
  178. package/styles/components/input-number/_index.scss +353 -0
  179. package/styles/components/input-number/_mixins.scss +0 -0
  180. package/styles/components/input-number/_vars.scss +65 -0
  181. package/styles/components/ip-input/_index.scss +280 -0
  182. package/styles/components/layout/_index.scss +47 -0
  183. package/styles/components/layout/_mixin.scss +0 -0
  184. package/styles/components/layout/_vars.scss +18 -0
  185. package/styles/components/layout/doc.scss +74 -0
  186. package/styles/components/list/_index.scss +172 -0
  187. package/styles/components/list/_mixins.scss +0 -0
  188. package/styles/components/list/_vars.scss +41 -0
  189. package/styles/components/loading/_index.scss +112 -112
  190. package/styles/components/loading/_vars.scss +39 -39
  191. package/styles/components/notification/_index.scss +160 -160
  192. package/styles/components/notification/_mixins.scss +12 -12
  193. package/styles/components/notification/_vars.scss +59 -59
  194. package/styles/components/popup/_index.scss +82 -82
  195. package/styles/components/popup/_mixin.scss +149 -149
  196. package/styles/components/popup/_var.scss +31 -31
  197. package/styles/components/radio/_index.scss +376 -0
  198. package/styles/components/radio/_mixins.scss +0 -0
  199. package/styles/components/radio/_var.scss +92 -0
  200. package/styles/components/select/_index.scss +290 -290
  201. package/styles/components/select/_var.scss +65 -65
  202. package/styles/components/select-input/_index.scss +5 -5
  203. package/styles/components/select-input/_var.scss +3 -3
  204. package/styles/components/slider/_index.scss +241 -0
  205. package/styles/components/slider/_mixins.scss +0 -0
  206. package/styles/components/slider/_vars.scss +50 -0
  207. package/styles/components/switch/_index.scss +279 -279
  208. package/styles/components/switch/_vars.scss +61 -61
  209. package/styles/components/table/_index.scss +193 -0
  210. package/styles/components/table/_var.scss +52 -0
  211. package/styles/components/tabs/_index.scss +165 -0
  212. package/styles/components/tabs/_mixins.scss +11 -0
  213. package/styles/components/tabs/_vars.scss +71 -0
  214. package/styles/components/tag/_index.scss +316 -316
  215. package/styles/components/tag/_var.scss +85 -85
  216. package/styles/components/tag-input/_index.scss +163 -163
  217. package/styles/components/tag-input/_vars.scss +16 -16
  218. package/styles/globals.css +250 -250
  219. package/styles/mixins/_focus.scss +7 -7
  220. package/styles/mixins/_layout.scss +32 -32
  221. package/styles/mixins/_reset.scss +10 -10
  222. package/styles/mixins/_scrollbar.scss +31 -31
  223. package/styles/mixins/_text.scss +48 -48
  224. package/styles/rillple.css +16 -16
  225. package/styles/scrollbar.css +41 -41
  226. package/styles/themes/_dark.scss +191 -191
  227. package/styles/themes/_font.scss +69 -79
  228. package/styles/themes/_index.scss +5 -5
  229. package/styles/themes/_light.scss +190 -190
  230. package/styles/themes/_radius.scss +9 -9
  231. package/styles/themes/_size.scss +68 -68
  232. package/styles/themes.css +66 -66
  233. package/styles/utilities/_animation.scss +57 -57
  234. package/styles/utilities/_tips.scss +9 -9
  235. package/tab/TabBar.tsx +85 -0
  236. package/tab/TabNav.tsx +103 -0
  237. package/tab/TabNavItem.tsx +80 -0
  238. package/tab/TabPanel.tsx +42 -0
  239. package/tab/Tabs.tsx +71 -0
  240. package/tab/defaultProps.ts +19 -0
  241. package/tab/index.ts +7 -0
  242. package/tab/style/index.js +1 -0
  243. package/tab/type.ts +125 -0
  244. package/tab/useTabClass.ts +20 -0
  245. package/table/Cell.tsx +109 -0
  246. package/table/TBody.tsx +77 -0
  247. package/table/THead.tsx +63 -0
  248. package/table/TR.tsx +78 -0
  249. package/table/Table.tsx +73 -0
  250. package/table/defaultProps.ts +14 -0
  251. package/table/hooks/index.ts +4 -0
  252. package/table/hooks/useTableClassName.ts +63 -0
  253. package/table/hooks/useTableStyle.ts +93 -0
  254. package/table/index.ts +7 -0
  255. package/table/style/css.js +1 -0
  256. package/table/style/index.js +1 -0
  257. package/table/type.ts +192 -0
  258. package/tag/Tag.tsx +1 -1
  259. package/tag-input/hooks/useTagList.tsx +1 -1
  260. package/utils/dom.ts +4 -0
  261. package/utils/forwardRefWithStatics.ts +1 -4
  262. package/utils/input-number/large-number.ts +423 -0
  263. package/utils/input-number/number.ts +257 -0
  264. package/utils/isFragment.ts +6 -6
  265. package/utils/log/index.ts +3 -0
  266. package/utils/log/log.ts +30 -0
  267. package/utils/log/types.ts +12 -0
  268. package/utils/number.ts +21 -0
  269. package/utils/scroll.ts +26 -0
  270. package/utils/style.ts +2 -4
@@ -0,0 +1,119 @@
1
+ import React, { useRef, useEffect } from "react";
2
+ import { throttle } from "lodash-es";
3
+ import { Color, getColorFormatInputs, getColorFormatMap } from "../../../utils/color-picker";
4
+ import type { TdColorFormatProps } from ".";
5
+ import Input from "../../../../input";
6
+ import InputNumber from "../../../../input-number";
7
+
8
+ const FormatInputs = (props: TdColorFormatProps) => {
9
+ const { format, enableAlpha, inputProps, disabled, onInputChange, color } = props;
10
+ const modelValueRef = useRef<Record<string, number | string>>({});
11
+ const lastModelValue = useRef<Record<string, number | string>>({});
12
+ const inputKey = useRef<number>(0);
13
+
14
+ const updateModelValue = () => {
15
+ const value = getColorFormatMap(color, "encode")[format];
16
+ if (!value || typeof value === "string") return;
17
+
18
+ const valueObj = value as Record<string, number | string>;
19
+ if (enableAlpha && "a" in valueObj) {
20
+ valueObj.a = Math.round(color.alpha * 100);
21
+ }
22
+
23
+ const changedFormatValue: Record<string, number | string> = {};
24
+ Object.keys(valueObj).forEach((key) => {
25
+ if (valueObj[key] !== modelValueRef.current[key]) {
26
+ changedFormatValue[key] = valueObj[key];
27
+ }
28
+ lastModelValue.current[key] = valueObj[key];
29
+ });
30
+
31
+ if (Object.keys(changedFormatValue).length > 0) {
32
+ modelValueRef.current = valueObj;
33
+ }
34
+ };
35
+
36
+ const handleInputChange = (key: string, v: number | string, max: number) => {
37
+ inputKey.current = performance.now();
38
+
39
+ if (v.toString().trim() === "") {
40
+ const lastValue = lastModelValue.current[key];
41
+ color.update(lastValue as string);
42
+ onInputChange();
43
+ return;
44
+ }
45
+
46
+ if (!v || v === lastModelValue.current[key] || Number(v) < 0 || Number(v) > max) return;
47
+ lastModelValue.current[key] = v;
48
+
49
+ const newFormatValue = {
50
+ ...modelValueRef.current,
51
+ [key]: v
52
+ };
53
+ modelValueRef.current = newFormatValue;
54
+ if (key === "a") {
55
+ color.alpha = (v as number) / 100;
56
+ } else if (key === "hex" || key === "css") {
57
+ color.update(v as string);
58
+ } else {
59
+ color.update(Color.object2color(newFormatValue, format));
60
+ }
61
+ onInputChange();
62
+ };
63
+
64
+ updateModelValue();
65
+ useEffect(() => {
66
+ const throttleUpdate = throttle(updateModelValue, 100);
67
+ throttleUpdate();
68
+ return () => throttleUpdate.cancel();
69
+ }, [color.saturation, color.hue, color.value, color.alpha, format]);
70
+
71
+ return (
72
+ <div className="input-group">
73
+ {getColorFormatInputs(format, enableAlpha).map((config) => {
74
+ const currentValue = modelValueRef.current[config.key];
75
+ const commonProps = {
76
+ ...inputProps,
77
+ disabled,
78
+ title: currentValue,
79
+ align: "center" as const,
80
+ size: "small" as const,
81
+ onBlur: (v: string) => handleInputChange(config.key, v, config.max),
82
+ onEnter: (v: string) => handleInputChange(config.key, v, config.max)
83
+ };
84
+
85
+ return (
86
+ <div
87
+ className="input-group__item"
88
+ key={config.key}
89
+ style={{
90
+ flex: config.flex || 1
91
+ }}
92
+ >
93
+ {config.type === "input" ? (
94
+ <Input
95
+ {...commonProps}
96
+ defaultValue={currentValue as string}
97
+ key={`${inputKey.current}-${currentValue}`}
98
+ maxlength={format === "HEX" ? 9 : undefined}
99
+ />
100
+ ) : (
101
+ <InputNumber
102
+ {...commonProps}
103
+ min={config.min}
104
+ max={config.max}
105
+ format={config.format as (value: number) => string}
106
+ step={1}
107
+ value={currentValue as number}
108
+ onChange={(v) => handleInputChange(config.key, v ?? (config.min as number), config.max)}
109
+ theme="normal"
110
+ />
111
+ )}
112
+ </div>
113
+ );
114
+ })}
115
+ </div>
116
+ );
117
+ };
118
+
119
+ export default React.memo(FormatInputs);
@@ -0,0 +1,37 @@
1
+ import React from "react";
2
+ import { COLOR_MODES } from "../../utils/color-picker";
3
+ import Radio, { RadioValue } from "../../../radio";
4
+ import { TdColorModes, TdColorPickerProps } from "../../type";
5
+
6
+ export interface ColorPanelHeaderProps extends TdColorPickerProps {
7
+ mode?: TdColorModes;
8
+ colorModes?: Array<TdColorModes>;
9
+ onModeChange?: (value: RadioValue, context: { e: React.ChangeEvent<HTMLInputElement> }) => void;
10
+ baseClassName?: string;
11
+ }
12
+
13
+ const Header = (props: ColorPanelHeaderProps) => {
14
+ const { baseClassName = "", mode = "monochrome", colorModes, onModeChange } = props;
15
+
16
+ const isSingleMode = colorModes?.length === 1;
17
+
18
+ if (isSingleMode) {
19
+ return null;
20
+ }
21
+
22
+ return (
23
+ <div className={`${baseClassName}__head`}>
24
+ <div className={`${baseClassName}__mode`}>
25
+ <Radio.Group variant="default-filled" size="small" value={mode} onChange={onModeChange}>
26
+ {Object.keys(COLOR_MODES).map((key) => (
27
+ <Radio.Button key={key} value={key}>
28
+ {COLOR_MODES[key as keyof typeof COLOR_MODES]}
29
+ </Radio.Button>
30
+ ))}
31
+ </Radio.Group>
32
+ </div>
33
+ </div>
34
+ );
35
+ };
36
+
37
+ export default React.memo(Header);
@@ -0,0 +1,20 @@
1
+ import React from "react";
2
+ import type { TdColorBaseProps } from "../../type";
3
+ import ColorSlider from "./slider";
4
+
5
+ const HueSlider = (props: TdColorBaseProps) => {
6
+ const { color, baseClassName, disabled, onChange } = props;
7
+ return (
8
+ <ColorSlider
9
+ disabled={disabled}
10
+ baseClassName={baseClassName}
11
+ className={`${baseClassName}__hue`}
12
+ color={color}
13
+ value={color.hue}
14
+ type="hue"
15
+ onChange={onChange}
16
+ />
17
+ );
18
+ };
19
+
20
+ export default React.memo(HueSlider);
@@ -0,0 +1,191 @@
1
+ import React, { forwardRef, useCallback, useEffect, useRef, useState } from "react";
2
+ import classNames from "classnames";
3
+ import {
4
+ Color,
5
+ DEFAULT_COLOR,
6
+ DEFAULT_SYSTEM_SWATCH_COLORS,
7
+ getColorObject,
8
+ initColorFormat,
9
+ type ColorFormat
10
+ } from "../../utils/color-picker/index";
11
+ import useCommonClassName from "../../../hooks/useCommonClassName";
12
+ import useControlled from "../../../hooks/useControlled";
13
+ import useDefaultProps from "../../../hooks/useDefaultProps";
14
+ import { useLocaleReceiver } from "../../../locale/LocalReceiver";
15
+ import { colorPickerDefaultProps } from "../../defaultProps";
16
+ import useClassName from "../../hooks/useClassNames";
17
+ import type { ColorPickerProps, TdColorSaturationData } from "../../type";
18
+ import type { ColorPickerChangeTrigger } from "../../type";
19
+ import AlphaSlider from "./alpha";
20
+ import HueSlider from "./hue";
21
+ import SaturationPanel from "./saturation";
22
+ import SwatchesPanel from "./swatches";
23
+
24
+ const Panel = forwardRef<HTMLDivElement, ColorPickerProps>((props, ref) => {
25
+ const baseClassName = useClassName();
26
+ const { STATUS } = useCommonClassName();
27
+ const [local, t] = useLocaleReceiver("colorPicker");
28
+ const {
29
+ className,
30
+ disabled,
31
+ enableAlpha,
32
+ format,
33
+ style,
34
+ swatchColors,
35
+ showPrimaryColorPreview,
36
+ onChange,
37
+ onPaletteBarChange
38
+ } = useDefaultProps(props, colorPickerDefaultProps);
39
+ const [innerValue, setInnerValue] = useControlled(props, "value", onChange);
40
+
41
+ const [, setUpdateId] = useState(0); // 确保 UI 同步更新
42
+
43
+ const defaultEmptyColor = DEFAULT_COLOR;
44
+
45
+ const colorInstanceRef = useRef<Color>(new Color(innerValue || defaultEmptyColor));
46
+ const formatRef = useRef<ColorFormat>(initColorFormat(format, enableAlpha));
47
+
48
+ const baseProps = {
49
+ color: colorInstanceRef.current,
50
+ disabled,
51
+ baseClassName
52
+ };
53
+
54
+ const updateColor = (value: string) => {
55
+ colorInstanceRef.current.update(value);
56
+ setUpdateId(window.performance.now());
57
+ };
58
+
59
+ const emitColorChange = useCallback(
60
+ (trigger?: ColorPickerChangeTrigger) => {
61
+ const value = colorInstanceRef.current.getFormattedColor(formatRef.current, enableAlpha);
62
+ setInnerValue(value, {
63
+ color: getColorObject(colorInstanceRef.current),
64
+ trigger: trigger || "palette-saturation-brightness"
65
+ });
66
+ setUpdateId(window.performance.now());
67
+ },
68
+ [enableAlpha, setInnerValue]
69
+ );
70
+
71
+ useEffect(() => {
72
+ const currentColor = colorInstanceRef.current.getFormattedColor(formatRef.current, enableAlpha);
73
+ if (innerValue === currentColor) return;
74
+ // 根据颜色自动切换模式
75
+ updateColor(innerValue);
76
+ }, [innerValue]); // eslint-disable-line react-hooks/exhaustive-deps
77
+
78
+ const [previewBackground, setPreviewBackground] = useState("");
79
+
80
+ useEffect(() => {
81
+ const inst = colorInstanceRef.current;
82
+ const next = inst.rgba;
83
+ setPreviewBackground(next);
84
+ }, [innerValue]);
85
+
86
+ /**
87
+ * 饱和度亮度变化
88
+ */
89
+ const handleSatAndValueChange = ({ saturation, value }: TdColorSaturationData) => {
90
+ const { saturation: sat, value: val } = colorInstanceRef.current;
91
+ let changeTrigger: ColorPickerChangeTrigger = "palette-saturation-brightness";
92
+ if (value !== val && saturation !== sat) {
93
+ changeTrigger = "palette-saturation-brightness";
94
+ colorInstanceRef.current.saturation = saturation;
95
+ colorInstanceRef.current.value = value;
96
+ } else if (saturation !== sat) {
97
+ changeTrigger = "palette-saturation";
98
+ colorInstanceRef.current.saturation = saturation;
99
+ } else if (value !== val) {
100
+ changeTrigger = "palette-brightness";
101
+ colorInstanceRef.current.value = value;
102
+ } else {
103
+ return;
104
+ }
105
+
106
+ emitColorChange(changeTrigger);
107
+ };
108
+
109
+ /**
110
+ * 色相变化
111
+ */
112
+ const handleHueChange = (hue: number) => {
113
+ colorInstanceRef.current.hue = hue;
114
+ emitColorChange("palette-hue-bar");
115
+ onPaletteBarChange?.({
116
+ color: getColorObject(colorInstanceRef.current)
117
+ });
118
+ };
119
+
120
+ /**
121
+ * 透明度变化
122
+ */
123
+ const handleAlphaChange = (alpha: number) => {
124
+ colorInstanceRef.current.alpha = alpha;
125
+ emitColorChange("palette-alpha-bar");
126
+ onPaletteBarChange?.({
127
+ color: getColorObject(colorInstanceRef.current)
128
+ });
129
+ };
130
+
131
+ // 渐变与色块区域的派生数据(不直接访问 ref)
132
+
133
+ // 系统预设颜色
134
+ let systemColorsDerived = swatchColors;
135
+ if (systemColorsDerived === undefined) {
136
+ systemColorsDerived = [...DEFAULT_SYSTEM_SWATCH_COLORS];
137
+ }
138
+
139
+ const showSystemColors = Array.isArray(systemColorsDerived);
140
+
141
+ const handleSwatchSetColor = (value: string, trigger: ColorPickerChangeTrigger) => {
142
+ // 确保在渐变模式下选择纯色块,能切换回单色模式
143
+ updateColor(value);
144
+ emitColorChange(trigger);
145
+ };
146
+
147
+ return (
148
+ <div
149
+ className={classNames(`${baseClassName}__panel`, disabled ? STATUS.disabled : false, className)}
150
+ onClick={(e) => e.stopPropagation()}
151
+ style={{ ...style }}
152
+ ref={ref}
153
+ >
154
+ <div className={`${baseClassName}__body`}>
155
+ <SaturationPanel {...baseProps} onChange={handleSatAndValueChange} />
156
+ <div className={`${baseClassName}__sliders-wrapper`}>
157
+ <div className={`${baseClassName}__sliders`}>
158
+ <HueSlider {...baseProps} onChange={handleHueChange} />
159
+ {enableAlpha && <AlphaSlider {...baseProps} onChange={handleAlphaChange} />}
160
+ </div>
161
+ {showPrimaryColorPreview ? (
162
+ <div className={classNames([`${baseClassName}__sliders-preview`, `${baseClassName}--bg-alpha`])}>
163
+ <span
164
+ className={`${baseClassName}__sliders-preview-inner`}
165
+ style={{
166
+ background: previewBackground
167
+ }}
168
+ />
169
+ </div>
170
+ ) : null}
171
+ </div>
172
+
173
+ {showSystemColors && (
174
+ <div className={`${baseClassName}__swatches-wrap`}>
175
+ {showSystemColors && (
176
+ <SwatchesPanel
177
+ {...baseProps}
178
+ title={t(local.swatchColorTitle)}
179
+ colors={systemColorsDerived}
180
+ onSetColor={(color: string) => handleSwatchSetColor(color, "preset")}
181
+ />
182
+ )}
183
+ </div>
184
+ )}
185
+ </div>
186
+ </div>
187
+ );
188
+ });
189
+
190
+ Panel.displayName = "Panel";
191
+ export default React.memo(Panel);
@@ -0,0 +1,81 @@
1
+ import React, { useCallback, useEffect, useRef } from "react";
2
+ import { SATURATION_PANEL_DEFAULT_HEIGHT, SATURATION_PANEL_DEFAULT_WIDTH } from "../../utils/color-picker";
3
+ import useMouseEvent, { type MouseCoordinate } from "../../../hooks/useMouseEvent";
4
+ import type { TdColorBaseProps } from "../../type";
5
+
6
+ const Saturation = (props: TdColorBaseProps) => {
7
+ const { color, disabled, onChange, baseClassName } = props;
8
+ const panelRef = useRef<HTMLDivElement>(null);
9
+ const panelRectRef = useRef({
10
+ width: SATURATION_PANEL_DEFAULT_WIDTH,
11
+ height: SATURATION_PANEL_DEFAULT_HEIGHT
12
+ });
13
+
14
+ const styles = () => {
15
+ const { saturation, value, rgb } = color;
16
+ const { width, height } = panelRectRef.current;
17
+ const top = Math.round((1 - value) * height);
18
+ const left = Math.round(saturation * width);
19
+ return {
20
+ color: rgb,
21
+ left: `${left}px`,
22
+ top: `${top}px`
23
+ };
24
+ };
25
+
26
+ const getSaturationAndValue = (coordinate: MouseCoordinate) => {
27
+ const { width, height } = panelRectRef.current;
28
+ const { x, y } = coordinate;
29
+ const saturation = Math.round((x / width) * 100);
30
+ const value = Math.round((1 - y / height) * 100);
31
+ return {
32
+ saturation,
33
+ value
34
+ };
35
+ };
36
+
37
+ const handleDrag = useCallback(
38
+ ({ x, y }: MouseCoordinate) => {
39
+ if (disabled) return;
40
+ const { saturation, value } = getSaturationAndValue({ x, y });
41
+ onChange?.({
42
+ saturation: saturation / 100,
43
+ value: value / 100
44
+ });
45
+ },
46
+ [disabled, onChange]
47
+ );
48
+
49
+ useMouseEvent(panelRef, {
50
+ onDown: () => {
51
+ if (disabled) return;
52
+ panelRectRef.current.width = panelRef.current?.offsetWidth || panelRectRef.current.width;
53
+ panelRectRef.current.height = panelRef.current?.offsetHeight || panelRectRef.current.height;
54
+ },
55
+ onMove: (_, ctx) => {
56
+ handleDrag(ctx.coordinate);
57
+ },
58
+ onUp: (_, ctx) => {
59
+ handleDrag(ctx.coordinate);
60
+ }
61
+ });
62
+
63
+ useEffect(() => {
64
+ panelRectRef.current.width = panelRef.current?.offsetWidth || SATURATION_PANEL_DEFAULT_WIDTH;
65
+ panelRectRef.current.height = panelRef.current?.offsetHeight || SATURATION_PANEL_DEFAULT_HEIGHT;
66
+ }, [handleDrag]);
67
+
68
+ return (
69
+ <div
70
+ ref={panelRef}
71
+ className={`${baseClassName}__saturation`}
72
+ style={{
73
+ background: `hsl(${color.hue}, 100%, 50%)`
74
+ }}
75
+ >
76
+ <span className={`${baseClassName}__thumb`} role="slider" tabIndex={0} style={styles()} />
77
+ </div>
78
+ );
79
+ };
80
+
81
+ export default React.memo(Saturation);
@@ -0,0 +1,76 @@
1
+ import React, { CSSProperties, useEffect, useRef } from "react";
2
+ import classNames from "classnames";
3
+ import { SLIDER_DEFAULT_WIDTH } from "../../utils/color-picker";
4
+ import useMouseEvent, { type MouseCoordinate } from "../../../hooks/useMouseEvent";
5
+ import useStyles from "../../hooks/useStyles";
6
+ import type { TdColorBaseProps } from "../../type";
7
+
8
+ export interface TdColorSliderProps extends TdColorBaseProps {
9
+ className?: string;
10
+ value?: number;
11
+ maxValue?: number;
12
+ railStyle?: CSSProperties;
13
+ type: "hue" | "alpha";
14
+ }
15
+
16
+ const ColorSlider = (props: TdColorSliderProps) => {
17
+ const {
18
+ color,
19
+ className = "",
20
+ value = 0,
21
+ railStyle = {},
22
+ maxValue = 360,
23
+ baseClassName,
24
+ disabled,
25
+ onChange,
26
+ type
27
+ } = props;
28
+ const panelRef = useRef<HTMLDivElement>(null);
29
+ const panelRectRef = useRef({
30
+ width: SLIDER_DEFAULT_WIDTH
31
+ });
32
+ const { styles } = useStyles({ color, value, maxValue, type }, panelRectRef);
33
+
34
+ const handleDrag = (coordinate: MouseCoordinate) => {
35
+ if (disabled) return;
36
+ const { width } = panelRectRef.current;
37
+ const { x } = coordinate;
38
+ const nextValue = Math.round((x / width) * Number(maxValue) * 100) / 100;
39
+ console.log(nextValue);
40
+ onChange?.(nextValue);
41
+ };
42
+
43
+ useMouseEvent(panelRef, {
44
+ onDown: (_, ctx) => {
45
+ if (disabled) return;
46
+ panelRectRef.current.width = panelRef.current?.offsetWidth || panelRectRef.current.width;
47
+ handleDrag(ctx.coordinate);
48
+ },
49
+ onMove: (_, ctx) => {
50
+ handleDrag(ctx.coordinate);
51
+ },
52
+ onUp: (_, ctx) => {
53
+ handleDrag(ctx.coordinate);
54
+ }
55
+ });
56
+
57
+ useEffect(() => {
58
+ panelRectRef.current.width = panelRef.current?.offsetWidth || SLIDER_DEFAULT_WIDTH;
59
+ }, []);
60
+
61
+ const paddingStyle = {
62
+ background: `linear-gradient(90deg, rgba(0,0,0,.0) 0%, rgba(0,0,0,.0) 93%, ${props.color.rgb} 93%, ${props.color.rgb} 100%)`
63
+ };
64
+
65
+ return (
66
+ <div className={classNames(`${baseClassName}__slider-wrapper`, `${baseClassName}__slider-wrapper--${type}-type`)}>
67
+ {type === "alpha" && <div className={`${baseClassName}__slider-padding`} style={paddingStyle} />}
68
+ <div className={classNames(`${baseClassName}__slider`, className)} ref={panelRef}>
69
+ <div className={`${baseClassName}__rail`} style={railStyle} />
70
+ <span className={`${baseClassName}__thumb`} role="slider" tabIndex={0} style={styles} />
71
+ </div>
72
+ </div>
73
+ );
74
+ };
75
+
76
+ export default React.memo(ColorSlider);
@@ -0,0 +1,84 @@
1
+ import React from "react";
2
+ import classNames from "classnames";
3
+ import { IconDelete as TdDeleteIcon, IconPlus as TdAddIcon } from "@tendaui/icons";
4
+ import Color from "../../utils/color-picker/color";
5
+ import useGlobalIcon from "../../../hooks/useGlobalIcon";
6
+ import { TdColorBaseProps } from "../../type";
7
+ import useCommonClassName from "../../../hooks/useCommonClassName";
8
+
9
+ export interface TdColorSwatchesProps extends TdColorBaseProps {
10
+ colors?: string[];
11
+ title?: string;
12
+ editable?: boolean;
13
+ onSetColor?: (color: string) => void;
14
+ handleAddColor?: () => void;
15
+ }
16
+
17
+ const Swatches = (props: TdColorSwatchesProps) => {
18
+ const { baseClassName, colors = [], editable = false, title, onChange, disabled, onSetColor, handleAddColor } = props;
19
+ const { DeleteIcon, AddIcon } = useGlobalIcon({ DeleteIcon: TdDeleteIcon, AddIcon: TdAddIcon });
20
+ const swatchesClass = `${baseClassName}__swatches`;
21
+ const { STATUS: statusClassNames } = useCommonClassName();
22
+ const isEqualCurrentColor = (color: string) => Color.compare(color, props.color.css);
23
+
24
+ const selectedColorIndex = () => colors.findIndex((color) => isEqualCurrentColor(color));
25
+ const handleRemoveColor = () => {
26
+ const selectedIndex = selectedColorIndex();
27
+ if (selectedIndex > -1) {
28
+ const newColors = colors.filter((_, index) => index !== selectedIndex);
29
+ onChange?.(newColors);
30
+ }
31
+ };
32
+
33
+ const handleClick = (color: string) => onSetColor?.(color);
34
+
35
+ return (
36
+ <div className={swatchesClass}>
37
+ {title ? (
38
+ <h3 className={`${swatchesClass}--title`}>
39
+ <span>{title}</span>
40
+ {editable && (
41
+ <div className={`${swatchesClass}--actions`}>
42
+ <span role="button" className={`${baseClassName}__icon`} onClick={() => handleAddColor?.()}>
43
+ <AddIcon />
44
+ </span>
45
+ {colors.length > 0 ? (
46
+ <span role="button" className={`${baseClassName}__icon`} onClick={() => handleRemoveColor()}>
47
+ <DeleteIcon />
48
+ </span>
49
+ ) : null}
50
+ </div>
51
+ )}
52
+ </h3>
53
+ ) : null}
54
+ <ul className={classNames(`${swatchesClass}--items`, "narrow-scrollbar")}>
55
+ {colors.map((color) => (
56
+ <li
57
+ className={classNames(
58
+ `${swatchesClass}--item`,
59
+ isEqualCurrentColor(color) && editable ? statusClassNames.active : ""
60
+ )}
61
+ key={color}
62
+ onClick={() => {
63
+ if (disabled) {
64
+ return;
65
+ }
66
+ handleClick(color);
67
+ }}
68
+ >
69
+ <div className={classNames(`${swatchesClass}--item__color`, `${baseClassName}--bg-alpha`)}>
70
+ <span
71
+ className={`${swatchesClass}--item__inner`}
72
+ style={{
73
+ background: color
74
+ }}
75
+ />
76
+ </div>
77
+ </li>
78
+ ))}
79
+ </ul>
80
+ </div>
81
+ );
82
+ };
83
+
84
+ export default React.memo(Swatches);
@@ -0,0 +1,49 @@
1
+ import React from "react";
2
+ import classNames from "classnames";
3
+ import { Color, getColorObject } from "../utils/color-picker/color";
4
+ import { Input } from "../../input";
5
+ import { TdColorPickerProps } from "../type";
6
+ import useClassName from "../hooks/useClassNames";
7
+ import { TdColorContext } from "../type";
8
+ import noop from "../../utils/noop";
9
+
10
+ export interface ColorTriggerProps
11
+ extends Pick<TdColorPickerProps, "disabled" | "inputProps" | "borderless" | "clearable" | "onClear"> {
12
+ value?: string;
13
+ onChange?: (v?: string, context?: TdColorContext) => void;
14
+ }
15
+
16
+ const ColorPickerTrigger = (props: ColorTriggerProps) => {
17
+ const baseClassName = useClassName();
18
+ const { disabled = false, borderless = false, inputProps = { autoWidth: true }, clearable, onClear } = props;
19
+
20
+ const handleChange = (input: string) => {
21
+ if (input !== props.value) {
22
+ props.onChange?.(input, {
23
+ color: getColorObject(new Color(input)),
24
+ trigger: "input"
25
+ });
26
+ }
27
+ };
28
+
29
+ return (
30
+ <div className={`${baseClassName}__trigger--default`}>
31
+ <Input
32
+ borderless={borderless}
33
+ clearable={clearable}
34
+ {...inputProps}
35
+ value={props.value}
36
+ disabled={disabled}
37
+ label={
38
+ <div className={classNames(`${baseClassName}__trigger--default__color`, `${baseClassName}--bg-alpha`)}>
39
+ <span className={"color-inner"} style={{ background: props.value }}></span>
40
+ </div>
41
+ }
42
+ onChange={handleChange}
43
+ onClear={onClear || noop}
44
+ />
45
+ </div>
46
+ );
47
+ };
48
+
49
+ export default React.memo(ColorPickerTrigger);
@@ -0,0 +1,7 @@
1
+ import { TdColorPickerProps } from "./type";
2
+
3
+ export const colorPickerDefaultProps: TdColorPickerProps = {
4
+ borderless: false,
5
+ clearable: false,
6
+ disabled: false
7
+ };