Skip to content

Commit f429148

Browse files
authored
Feat v4 theme editor (#6348)
* feat: add theme editor container * feat: add theme editor layout * add left panel * add vue-colorful & fix bug * 修复hue组件抖动问题 * fix bug && add demo * fix bug * fix demo preview * fix theme editor components demo
1 parent 23a213a commit f429148

File tree

308 files changed

+13767
-8
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

308 files changed

+13767
-8
lines changed

components/collapse/Collapse.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ export default defineComponent({
8181

8282
return (
8383
<div
84-
class={`${prefixCls.value}-expand-icon`}
84+
class={[`${prefixCls.value}-expand-icon`, hashId.value]}
8585
onClick={() =>
8686
['header', 'icon'].includes(props.collapsible) && onClickItem(panelProps.panelKey)
8787
}

components/tag/index.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ export const tagProps = () => ({
2525
onClose: {
2626
type: Function as PropType<(e: MouseEvent) => void>,
2727
},
28+
onClick: {
29+
type: Function as PropType<(e: MouseEvent) => void>,
30+
},
2831
'onUpdate:visible': Function as PropType<(vis: boolean) => void>,
2932
icon: PropTypes.any,
3033
});
@@ -86,7 +89,7 @@ const Tag = defineComponent({
8689
);
8790

8891
const tagClassName = computed(() =>
89-
classNames(prefixCls.value, hashId.value, {
92+
classNames(prefixCls.value, hashId.value, attrs.class, {
9093
[`${prefixCls.value}-${props.color}`]: isInternalColor.value,
9194
[`${prefixCls.value}-has-color`]: props.color && !isInternalColor.value,
9295
[`${prefixCls.value}-hidden`]: !visible.value,

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@
246246
"stylelint-order": "^5.0.0",
247247
"terser-webpack-plugin": "^5.1.1",
248248
"through2": "^3.0.0",
249+
"tinycolor2": "^1.6.0",
249250
"ts-jest": "^28.0.5",
250251
"ts-loader": "^9.1.0",
251252
"ts-node": "^10.8.2",
@@ -254,6 +255,7 @@
254255
"umi-request": "^1.3.5",
255256
"unified": "9.2.2",
256257
"url-loader": "^3.0.0",
258+
"vanilla-jsoneditor": "^0.15.1",
257259
"vite": "^3.0.0",
258260
"vue": "^3.2.0",
259261
"vue-antd-md-loader": "^1.2.1-beta.1",
Lines changed: 344 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,344 @@
1+
import type { InputProps } from 'ant-design-vue';
2+
import { Input, InputNumber, Select, theme } from 'ant-design-vue';
3+
import classNames from 'ant-design-vue/es/_util/classNames';
4+
import type { PropType } from 'vue';
5+
import { defineComponent, watchEffect, watch, computed, toRefs, ref } from 'vue';
6+
import { HexColorPicker, RgbaColorPicker } from '../vue-colorful';
7+
import tinycolor from 'tinycolor2';
8+
import makeStyle from './utils/makeStyle';
9+
10+
const { useToken } = theme;
11+
12+
const useStyle = makeStyle('ColorPanel', token => ({
13+
'.color-panel': {
14+
padding: 12,
15+
backgroundColor: '#fff',
16+
borderRadius: 12,
17+
border: '1px solid rgba(0, 0, 0, 0.06)',
18+
boxShadow: token.boxShadow,
19+
width: 224,
20+
boxSizing: 'border-box',
21+
22+
'.color-panel-mode': {
23+
display: 'flex',
24+
alignItems: 'center',
25+
marginBottom: 6,
26+
},
27+
'.color-panel-preview': {
28+
width: 24,
29+
height: 24,
30+
borderRadius: 4,
31+
boxShadow: '0 2px 3px -1px rgba(0,0,0,0.20), inset 0 0 0 1px rgba(0,0,0,0.09)',
32+
flex: 'none',
33+
overflow: 'hidden',
34+
background:
35+
'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAFpJREFUWAntljEKADAIA23p6v//qQ+wfUEcCu1yriEgp0FHRJSJcnehmmWm1Dv/lO4HIg1AAAKjTqm03ea88zMCCEDgO4HV5bS757f+7wRoAAIQ4B9gByAAgQ3pfiDmXmAeEwAAAABJRU5ErkJggg==) 0% 0% / 32px',
36+
},
37+
'.color-panel-preset-colors': {
38+
paddingTop: 12,
39+
display: 'flex',
40+
flexWrap: 'wrap',
41+
width: 200,
42+
},
43+
'.color-panel-preset-color-btn': {
44+
borderRadius: 4,
45+
width: 20,
46+
height: 20,
47+
border: 'none',
48+
outline: 'none',
49+
margin: 4,
50+
cursor: 'pointer',
51+
boxShadow: '0 2px 3px -1px rgba(0,0,0,0.20), inset 0 0 0 1px rgba(0,0,0,0.09)',
52+
},
53+
'.color-panel-mode-title': {
54+
color: token.colorTextPlaceholder,
55+
marginTop: 2,
56+
fontSize: 12,
57+
textAlign: 'center',
58+
},
59+
'.color-panel-rgba-input': {
60+
display: 'flex',
61+
alignItems: 'center',
62+
'&-part': {
63+
flex: 1,
64+
width: 0,
65+
display: 'flex',
66+
flexDirection: 'column',
67+
alignItems: 'center',
68+
69+
'&-title': {
70+
color: token.colorTextPlaceholder,
71+
marginTop: 2,
72+
fontSize: 12,
73+
},
74+
75+
'&:not(:last-child)': {
76+
marginRight: 4,
77+
},
78+
79+
[`${token.rootCls}-input-number`]: {
80+
width: '100%',
81+
input: {
82+
fontSize: 12,
83+
padding: '0 4px',
84+
},
85+
},
86+
},
87+
},
88+
},
89+
}));
90+
91+
export type HexColorInputProps = {
92+
value: string;
93+
onChange?: (value: string) => void;
94+
alpha?: boolean;
95+
};
96+
97+
const getHexValue = (value: string, alpha: boolean = false) => {
98+
return alpha ? tinycolor(value).toHex8() : tinycolor(value).toHex();
99+
};
100+
const HexColorInput = defineComponent({
101+
name: 'HexColorInput',
102+
props: {
103+
value: { type: String },
104+
alpha: { type: Boolean },
105+
onChange: { type: Function as PropType<(value: string) => void> },
106+
},
107+
setup(props) {
108+
const { value, alpha } = toRefs(props);
109+
110+
const hexValue = ref<string>(value.value);
111+
const focusRef = ref<boolean>(false);
112+
113+
const handleChange: InputProps['onChange'] = e => {
114+
hexValue.value = e.target.value;
115+
props.onChange(getHexValue(e.target.value, alpha.value));
116+
};
117+
118+
const handleBlur: InputProps['onBlur'] = (e: any) => {
119+
focusRef.value = false;
120+
hexValue.value = getHexValue(e.target.value, alpha.value);
121+
};
122+
123+
const handleFocus = () => {
124+
focusRef.value = true;
125+
};
126+
127+
watchEffect(() => {
128+
if (!focusRef.value) {
129+
hexValue.value = getHexValue(value.value, alpha.value);
130+
}
131+
});
132+
133+
return () => {
134+
return (
135+
<div>
136+
<Input
137+
size="small"
138+
value={hexValue.value}
139+
onFocus={handleFocus}
140+
onChange={handleChange}
141+
onBlur={handleBlur}
142+
v-slots={{
143+
prefix: () => '#',
144+
}}
145+
/>
146+
<div class="color-panel-mode-title">HEX{alpha.value ? '8' : ''}</div>
147+
</div>
148+
);
149+
};
150+
},
151+
});
152+
153+
type RgbaColor = tinycolor.ColorFormats.RGBA;
154+
155+
export type RgbColorInputProps = {
156+
value?: RgbaColor;
157+
onChange?: (value: RgbaColor) => void;
158+
alpha?: boolean;
159+
};
160+
161+
const RgbColorInput = defineComponent({
162+
name: 'RgbColorInput',
163+
props: {
164+
value: { type: Object as PropType<RgbaColor>, default: () => ({ r: 0, g: 0, b: 0, a: 1 }) },
165+
onChange: { type: Function as PropType<(value: RgbaColor) => void> },
166+
alpha: { type: Boolean },
167+
},
168+
setup(props) {
169+
const { value, alpha } = toRefs(props);
170+
171+
watch(value, val => {
172+
props.onChange(val);
173+
});
174+
175+
return () => {
176+
return (
177+
<div class="color-panel-rgba-input">
178+
{/* <ConfigProvider theme={{ components: { InputNumber: { handleWidth: 12 } } }}> */}
179+
<div class="color-panel-rgba-input-part">
180+
<InputNumber min={0} max={255} size="small" v-model={[value.value.r, 'value']} />
181+
<div class="color-panel-mode-title">R</div>
182+
</div>
183+
<div class="color-panel-rgba-input-part">
184+
<InputNumber min={0} max={255} size="small" v-model={[value.value.g, 'value']} />
185+
<div class="color-panel-mode-title">G</div>
186+
</div>
187+
<div class="color-panel-rgba-input-part">
188+
<InputNumber min={0} max={255} size="small" v-model={[value.value.b, 'value']} />
189+
<div class="color-panel-mode-title">B</div>
190+
</div>
191+
{alpha && (
192+
<div class="color-panel-rgba-input-part">
193+
<InputNumber
194+
min={0}
195+
max={1}
196+
step={0.01}
197+
size="small"
198+
v-model={[value.value.a, 'value']}
199+
/>
200+
<div class="color-panel-mode-title">A</div>
201+
</div>
202+
)}
203+
{/* </ConfigProvider> */}
204+
</div>
205+
);
206+
};
207+
},
208+
});
209+
210+
export type ColorPanelProps = {
211+
color: string;
212+
onChange: (color: string) => void;
213+
alpha?: boolean;
214+
};
215+
216+
const colorModes = ['HEX', 'HEX8', 'RGB', 'RGBA'] as const;
217+
218+
type ColorMode = (typeof colorModes)[number];
219+
220+
const getColorStr = (color: any, mode: ColorMode) => {
221+
switch (mode) {
222+
case 'HEX':
223+
return tinycolor(color).toHexString();
224+
case 'HEX8':
225+
return tinycolor(color).toHex8String();
226+
case 'RGBA':
227+
case 'RGB':
228+
default:
229+
return tinycolor(color).toRgbString();
230+
}
231+
};
232+
const ColorPanel = defineComponent({
233+
name: 'ColorPanel',
234+
props: {
235+
color: { type: String },
236+
onChange: { type: Function as PropType<(color: string) => void> },
237+
alpha: { type: Boolean },
238+
},
239+
inheritAttrs: false,
240+
setup(props, { attrs }) {
241+
const { color, alpha } = toRefs(props);
242+
243+
const { token } = useToken();
244+
const [wrapSSR, hashId] = useStyle();
245+
const colorMode = ref<ColorMode>('HEX');
246+
247+
const presetColors = computed(() => {
248+
return [
249+
token.value.blue,
250+
token.value.purple,
251+
token.value.cyan,
252+
token.value.green,
253+
token.value.magenta,
254+
token.value.pink,
255+
token.value.red,
256+
token.value.orange,
257+
token.value.yellow,
258+
token.value.volcano,
259+
token.value.geekblue,
260+
token.value.gold,
261+
token.value.lime,
262+
'#000',
263+
];
264+
});
265+
266+
const handleColorModeChange = (value: ColorMode) => {
267+
colorMode.value = value;
268+
props.onChange(getColorStr(color.value, value));
269+
};
270+
return () => {
271+
return wrapSSR(
272+
<div {...attrs} class={classNames(hashId.value, 'color-panel')}>
273+
{(colorMode.value === 'HEX' || colorMode.value === 'RGB') && (
274+
<HexColorPicker
275+
style={{ height: '160px' }}
276+
color={tinycolor(color.value).toHex()}
277+
onChange={value => {
278+
props.onChange(getColorStr(value, colorMode.value));
279+
}}
280+
/>
281+
)}
282+
{(colorMode.value === 'RGBA' || colorMode.value === 'HEX8') && (
283+
<RgbaColorPicker
284+
style={{ height: '160px' }}
285+
color={tinycolor(color).toRgb()}
286+
onChange={value => {
287+
props.onChange(getColorStr(value, colorMode.value));
288+
}}
289+
/>
290+
)}
291+
<div style={{ marginTop: '12px' }}>
292+
<div class="color-panel-mode">
293+
<div class="color-panel-preview">
294+
<div style={{ backgroundColor: color.value, width: '100%', height: '100%' }} />
295+
</div>
296+
<Select
297+
value={colorMode.value}
298+
onChange={handleColorModeChange}
299+
options={colorModes
300+
.filter(item => alpha.value || item === 'HEX' || item === 'RGB')
301+
.map(item => ({ value: item, key: item }))}
302+
size="small"
303+
bordered={false}
304+
dropdownMatchSelectWidth={false}
305+
/>
306+
</div>
307+
{colorMode.value === 'HEX' && (
308+
<HexColorInput
309+
value={tinycolor(color.value).toHex()}
310+
onChange={v => props.onChange(tinycolor(v).toHexString())}
311+
/>
312+
)}
313+
{colorMode.value === 'HEX8' && (
314+
<HexColorInput
315+
alpha
316+
value={tinycolor(color.value).toHex8()}
317+
onChange={v => props.onChange(tinycolor(v).toHex8String())}
318+
/>
319+
)}
320+
{(colorMode.value === 'RGBA' || colorMode.value === 'RGB') && (
321+
<RgbColorInput
322+
alpha={colorMode.value === 'RGBA'}
323+
value={tinycolor(color.value).toRgb()}
324+
onChange={v => props.onChange(tinycolor(v).toRgbString())}
325+
/>
326+
)}
327+
</div>
328+
<div class="color-panel-preset-colors">
329+
{presetColors.value.map(presetColor => (
330+
<button
331+
key={presetColor}
332+
class="color-panel-preset-color-btn"
333+
style={{ backgroundColor: presetColor }}
334+
onClick={() => props.onChange(presetColor)}
335+
/>
336+
))}
337+
</div>
338+
</div>,
339+
);
340+
};
341+
},
342+
});
343+
344+
export default ColorPanel;

0 commit comments

Comments
 (0)