Skip to content

Commit f819a1b

Browse files
authored
Feat v4 theme editor (#6349)
* 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 * add theme editor token drawer * add theme editor token drawer * fix bug * open commment * fix error demo * fix theme editor bug
1 parent dde2ff1 commit f819a1b

Some content is hidden

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

46 files changed

+1919
-1303
lines changed

site/src/components/antdv-token-previewer/ColorPanel.tsx

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { InputProps } from 'ant-design-vue';
2-
import { Input, InputNumber, Select, theme } from 'ant-design-vue';
2+
import { ConfigProvider, Input, InputNumber, Select, theme } from 'ant-design-vue';
33
import classNames from 'ant-design-vue/es/_util/classNames';
44
import type { PropType } from 'vue';
55
import { defineComponent, watchEffect, watch, computed, toRefs, ref } from 'vue';
@@ -175,32 +175,32 @@ const RgbColorInput = defineComponent({
175175
return () => {
176176
return (
177177
<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 && (
178+
<ConfigProvider theme={{ components: { InputNumber: { handleWidth: 12 } } }}>
192179
<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>
180+
<InputNumber min={0} max={255} size="small" v-model={[value.value.r, 'value']} />
181+
<div class="color-panel-mode-title">R</div>
201182
</div>
202-
)}
203-
{/* </ConfigProvider> */}
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.value && (
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>
204204
</div>
205205
);
206206
};
@@ -282,7 +282,7 @@ const ColorPanel = defineComponent({
282282
{(colorMode.value === 'RGBA' || colorMode.value === 'HEX8') && (
283283
<RgbaColorPicker
284284
style={{ height: '160px' }}
285-
color={tinycolor(color).toRgb()}
285+
color={tinycolor(color.value).toRgb()}
286286
onChange={value => {
287287
props.onChange(getColorStr(value, colorMode.value));
288288
}}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import type { CSSProperties, PropType } from 'vue';
2+
import { defineComponent, toRefs } from 'vue';
3+
import { antdComponents } from './component-panel';
4+
import type { Theme } from './interface';
5+
import ComponentDemoPro from './token-panel-pro/ComponentDemoPro';
6+
7+
export type PreviewDemoProps = {
8+
theme: Theme;
9+
};
10+
11+
const PreviewDemo = defineComponent({
12+
name: 'PreviewDemo',
13+
props: {
14+
theme: { type: Object as PropType<Theme> },
15+
},
16+
setup(props, { attrs }) {
17+
const { theme } = toRefs(props);
18+
19+
return () => {
20+
return (
21+
<div {...attrs} style={{ ...(attrs.style as CSSProperties), overflow: 'auto' }}>
22+
<ComponentDemoPro
23+
theme={theme.value}
24+
components={antdComponents}
25+
componentDrawer={false}
26+
showAll
27+
style={{ minHeight: '100%' }}
28+
/>
29+
</div>
30+
);
31+
};
32+
},
33+
});
34+
35+
export default PreviewDemo;
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
import type { PropType } from 'vue';
2+
import { defineComponent, toRefs, computed } from 'vue';
3+
import makeStyle from './utils/makeStyle';
4+
import classNames from 'ant-design-vue/es/_util/classNames';
5+
import { Button, Dropdown, Menu } from 'ant-design-vue';
6+
import { CloseOutlined, PlusOutlined } from '@ant-design/icons-vue';
7+
import type { Theme } from './interface';
8+
9+
interface ThemeItem extends Theme {
10+
icon?: any;
11+
closable?: boolean;
12+
fixed?: boolean;
13+
}
14+
15+
export type ThemeSelectProps = {
16+
onEnabledThemeChange: (themes: string[]) => void;
17+
onShownThemeChange: (
18+
themes: string[],
19+
selectTheme: string,
20+
info: { type: 'select' | 'deselect' },
21+
) => void;
22+
enabledThemes: string[];
23+
shownThemes: string[];
24+
themes: ThemeItem[];
25+
showAddTheme?: boolean;
26+
};
27+
28+
const useStyle = makeStyle('ThemeSelect', token => ({
29+
'.previewer-theme-select': {
30+
padding: `${token.paddingXXS}px ${token.paddingXS}px`,
31+
borderRadius: 4,
32+
backgroundColor: 'rgba(0, 0, 0, 0.02)',
33+
height: token.controlHeight,
34+
display: 'flex',
35+
alignItems: 'center',
36+
overflow: 'hidden',
37+
38+
[`${token.rootCls}-btn.previewer-theme-select-add-btn`]: {
39+
minWidth: 0,
40+
width: 16,
41+
height: 16,
42+
fontSize: 8,
43+
display: 'inline-flex',
44+
alignItems: 'center',
45+
justifyContent: 'center',
46+
marginInlineStart: token.marginSM,
47+
boxShadow: 'none',
48+
},
49+
50+
'.previewer-theme-select-tag': {
51+
height: 22,
52+
display: 'inline-flex',
53+
alignItems: 'center',
54+
justifyContent: 'center',
55+
boxSizing: 'border-box',
56+
borderRadius: 4,
57+
backgroundColor: token.colorBgContainer,
58+
border: `${token.lineWidth}px ${token.lineType} ${token.colorBorder}`,
59+
paddingInline: 10,
60+
fontSize: token.fontSizeSM,
61+
position: 'relative',
62+
cursor: 'pointer',
63+
// transition: `all ${token.motionDurationMid}`,
64+
65+
'&:not(:last-child)': {
66+
marginInlineEnd: token.marginXS,
67+
},
68+
69+
'&.previewer-theme-select-tag-active': {
70+
border: `${token.lineWidth}px ${token.lineType} ${token['blue-1']}`,
71+
backgroundColor: 'rgba(22,119,255,0.10)',
72+
color: token.colorPrimary,
73+
74+
'&::after': {
75+
content: '""',
76+
borderStartEndRadius: 2,
77+
position: 'absolute',
78+
insetInlineEnd: 2,
79+
top: 2,
80+
width: 6,
81+
height: 6,
82+
background: `linear-gradient(to right top, transparent, transparent 50%, ${token.colorPrimary} 50%, ${token.colorPrimary} 100%)`,
83+
},
84+
},
85+
86+
'.previewer-theme-select-tag-close-btn': {
87+
position: 'absolute',
88+
top: -2,
89+
insetInlineEnd: -2,
90+
width: 12,
91+
height: 12,
92+
display: 'flex',
93+
alignItems: 'center',
94+
justifyContent: 'center',
95+
background: token.colorBgContainer,
96+
boxShadow:
97+
'0 2px 8px -2px rgba(0,0,0,0.05), 0 1px 4px -1px rgba(25,15,15,0.07), 0 0 1px 0 rgba(0,0,0,0.08)',
98+
borderRadius: '50%',
99+
opacity: 0,
100+
pointerEvents: 'none',
101+
zIndex: 2,
102+
color: token.colorIcon,
103+
104+
'> .anticon': {
105+
fontSize: 6,
106+
},
107+
},
108+
109+
'&:hover': {
110+
'.previewer-theme-select-tag-close-btn': {
111+
opacity: 1,
112+
pointerEvents: 'initial',
113+
},
114+
},
115+
},
116+
},
117+
118+
'.previewer-theme-select-dropdown': {
119+
'.previewer-theme-select-dropdown-title': {
120+
[`${token.rootCls}-dropdown-menu-item-group-title`]: {
121+
fontSize: token.fontSizeSM,
122+
paddingBottom: token.padding,
123+
paddingTop: 10,
124+
},
125+
},
126+
},
127+
}));
128+
129+
const ThemeSelect = defineComponent({
130+
name: 'ThemeSelect',
131+
inheritAttrs: false,
132+
props: {
133+
onEnabledThemeChange: { type: Function as PropType<(themes: string[]) => void> },
134+
onShownThemeChange: {
135+
type: Function as PropType<
136+
(themes: string[], selectTheme: string, info: { type: 'select' | 'deselect' }) => void
137+
>,
138+
},
139+
enabledThemes: { type: Array as PropType<string[]> },
140+
shownThemes: { type: Array as PropType<string[]> },
141+
themes: { type: Array as PropType<ThemeItem[]> },
142+
showAddTheme: { type: Boolean },
143+
},
144+
setup(props, { attrs }) {
145+
const { enabledThemes, shownThemes, themes, showAddTheme } = toRefs(props);
146+
147+
const [wrapSSR, hashId] = useStyle();
148+
149+
const dropdownItems = computed(() => [
150+
{
151+
disabled: true,
152+
label: '添加主题即可预览',
153+
className: 'previewer-theme-select-dropdown-title',
154+
type: 'group',
155+
key: 'add-theme-to-preview',
156+
},
157+
...themes.value
158+
.filter(theme => !shownThemes.value.includes(theme.key))
159+
.map(theme => ({
160+
icon: theme.icon,
161+
value: theme.key,
162+
label: theme.name,
163+
key: theme.key,
164+
onClick: () => {
165+
props.onShownThemeChange([...shownThemes.value, theme.key], theme.key, {
166+
type: 'select',
167+
});
168+
},
169+
})),
170+
]);
171+
172+
const shownThemeEntities = computed(() =>
173+
themes.value.filter(theme => shownThemes.value.includes(theme.key)),
174+
);
175+
176+
return () => {
177+
return wrapSSR(
178+
<div {...attrs} class={classNames('previewer-theme-select', hashId.value, attrs.class)}>
179+
{shownThemeEntities.value.map(theme => (
180+
<span
181+
onClick={() => {
182+
if (theme.fixed) {
183+
return;
184+
}
185+
props.onEnabledThemeChange(
186+
enabledThemes.value.includes(theme.key)
187+
? enabledThemes.value.filter(item => item !== theme.key)
188+
: [...enabledThemes.value, theme.key],
189+
);
190+
}}
191+
key={theme.key}
192+
class={classNames('previewer-theme-select-tag', {
193+
'previewer-theme-select-tag-active': enabledThemes.value.includes(theme.key),
194+
})}
195+
>
196+
{theme.name}
197+
{theme.closable && (
198+
<span
199+
class="previewer-theme-select-tag-close-btn"
200+
onClick={e => {
201+
e.stopPropagation();
202+
props.onEnabledThemeChange(
203+
enabledThemes.value.filter(item => item !== theme.key),
204+
);
205+
props.onShownThemeChange(
206+
shownThemes.value.filter(item => item !== theme.key),
207+
theme.key,
208+
{ type: 'deselect' },
209+
);
210+
}}
211+
>
212+
<CloseOutlined />
213+
</span>
214+
)}
215+
</span>
216+
))}
217+
{showAddTheme.value && (
218+
<Dropdown
219+
placement="bottomRight"
220+
trigger={['click']}
221+
overlayClassName={classNames('previewer-theme-select-dropdown', hashId.value)}
222+
v-slots={{
223+
overlay: () => <Menu items={dropdownItems.value} />,
224+
}}
225+
>
226+
<Button
227+
type="primary"
228+
shape="circle"
229+
class="previewer-theme-select-add-btn"
230+
icon={<PlusOutlined />}
231+
/>
232+
</Dropdown>
233+
)}
234+
</div>,
235+
);
236+
};
237+
},
238+
});
239+
240+
export default ThemeSelect;

site/src/components/antdv-token-previewer/TokenInput.tsx

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Button, Dropdown, Input, InputNumber } from 'ant-design-vue';
1+
import { Button, Popover, Input, InputNumber } from 'ant-design-vue';
22
import classNames from 'ant-design-vue/es/_util/classNames';
33
import type { PropType } from 'vue';
44
import { defineComponent, toRefs, computed, ref, watch } from 'vue';
@@ -171,17 +171,23 @@ const TokenInput = defineComponent({
171171
value={String(tokenValue.value)}
172172
disabled={readonly.value}
173173
addonBefore={
174-
<Dropdown
175-
trigger={['click']}
176-
overlay={
177-
<ColorPanel
178-
alpha
179-
color={String(tokenValue.value)}
180-
onChange={(v: string) => {
181-
handleTokenChange(v);
182-
}}
183-
/>
184-
}
174+
<Popover
175+
trigger="click"
176+
placement="bottomRight"
177+
arrow-point-at-center
178+
overlayInnerStyle={{ padding: 0 }}
179+
v-slots={{
180+
content: () => (
181+
<ColorPanel
182+
alpha
183+
color={String(tokenValue.value)}
184+
style={{ border: 'none' }}
185+
onChange={(v: string) => {
186+
handleTokenChange(v);
187+
}}
188+
/>
189+
),
190+
}}
185191
>
186192
<ColorPreview
187193
color={String(tokenValue.value)}
@@ -192,7 +198,7 @@ const TokenInput = defineComponent({
192198
verticalAlign: 'top',
193199
}}
194200
/>
195-
</Dropdown>
201+
</Popover>
196202
}
197203
onChange={e => {
198204
handleTokenChange(e.target.value);

0 commit comments

Comments
 (0)