ant-design-vue/site/src/components/antdv-token-previewer/TokenInput.tsx

251 lines
7.1 KiB
Vue

import { Button, Popover, Input, InputNumber } from 'ant-design-vue';
import classNames from 'ant-design-vue/es/_util/classNames';
import type { PropType } from 'vue';
import { defineComponent, toRefs, computed, ref, watch } from 'vue';
import { debounce } from 'lodash';
import ColorPanel from './ColorPanel';
import ColorPreview from './ColorPreview';
import type { MutableTheme } from './interface';
import { useInjectLocaleContext } from './locale';
import isColor from './utils/isColor';
import makeStyle from './utils/makeStyle';
const useStyle = makeStyle('TokenInput', token => ({
'.previewer-token-input': {
[`${token.rootCls}-input-group-addon, ${token.rootCls}-input-number-group-addon`]: {
border: '0 !important',
color: `rgba(0, 0, 0, 0.25) !important`,
fontSize: `${token.fontSizeSM}px !important`,
padding: '0 !important',
backgroundColor: 'transparent !important',
'&:first-child': {
paddingInlineStart: 0,
},
'&:last-child': {
paddingInlineEnd: 0,
},
},
[`${token.rootCls}-input-group-wrapper, ${token.rootCls}-input-number-group-wrapper`]: {
padding: 0,
height: 24,
width: '100%',
input: {
fontSize: token.fontSizeSM,
lineHeight: token.lineHeightSM,
padding: `2px ${token.paddingXS}px`,
height: 24,
},
},
[`${token.rootCls}-input-group-wrapper ${token.rootCls}-input, ${token.rootCls}-input-number-group-wrapper ${token.rootCls}-input-number`]:
{
background: 'white',
borderRadius: `${token.borderRadiusLG}px !important`,
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
},
'&&-light': {
[`${token.rootCls}-input-group-addon, ${token.rootCls}-input-number-group-addon`]: {
backgroundColor: token.colorBgContainer,
},
[`${token.rootCls}-input-group-wrapper ${token.rootCls}-input,
${token.rootCls}-input-number-group-wrapper ${token.rootCls}-input-number-input`]: {
background: token.colorFillAlter,
},
},
'&&-readonly': {
input: {
cursor: 'text',
color: token.colorText,
},
},
},
}));
export type TokenInputProps = {
theme?: MutableTheme;
value?: string | number;
onChange?: (value: string | number) => void;
light?: boolean;
readonly?: boolean;
onReset?: () => void;
canReset?: boolean;
hideTheme?: boolean;
};
const TokenInput = defineComponent({
name: 'TokenInput',
inheritAttrs: false,
props: {
theme: { type: Object as PropType<MutableTheme> },
value: { type: [String, Number] },
onChange: { type: Function as PropType<(value: string | number) => void> },
light: { type: Boolean },
readonly: { type: Boolean },
onReset: { type: Function as PropType<() => void> },
canReset: { type: Boolean },
hideTheme: { type: Boolean },
},
setup(props, { attrs }) {
const { value, theme, light, readonly, canReset: customCanReset, hideTheme } = toRefs(props);
const valueRef = ref<number | string>(value.value || '');
const tokenValue = ref<string | number>(value.value || '');
const canReset = computed(() => customCanReset.value ?? valueRef.value !== tokenValue.value);
const locale = useInjectLocaleContext();
const [wrapSSR, hashId] = useStyle();
watch(
value,
val => {
if (val !== undefined) {
tokenValue.value = val;
}
},
{ immediate: true },
);
const debouncedOnChange = debounce((newValue: number | string) => {
props.onChange?.(newValue);
}, 500);
const handleTokenChange = (newValue: number | string) => {
if (!readonly.value) {
tokenValue.value = newValue;
debouncedOnChange(newValue);
}
};
const handleReset = () => {
if (props.onReset) {
props.onReset();
} else {
handleTokenChange(valueRef.value);
}
};
return () => {
const addonAfter = !readonly.value && (
<span
style={{
display: 'flex',
alignItems: 'center',
minWidth: hideTheme.value ? '' : '80px',
}}
>
{canReset.value || hideTheme.value ? (
<Button
style={{
fontSize: '12px',
}}
onClick={handleReset}
type="link"
size="small"
disabled={!canReset.value}
>
{locale.value.reset}
</Button>
) : (
<span style={{ padding: '0 8px' }}>{theme.value?.name}</span>
)}
</span>
);
let inputNode;
if (typeof valueRef.value === 'string' && isColor(valueRef.value)) {
inputNode = (
<Input
bordered={false}
addonAfter={addonAfter}
value={String(tokenValue.value)}
disabled={readonly.value}
addonBefore={
<Popover
trigger="click"
placement="bottomRight"
arrow-point-at-center
overlayInnerStyle={{ padding: 0 }}
v-slots={{
content: () => (
<ColorPanel
alpha
color={String(tokenValue.value)}
style={{ border: 'none' }}
onChange={(v: string) => {
handleTokenChange(v);
}}
/>
),
}}
>
<ColorPreview
color={String(tokenValue.value)}
dark={theme.value?.key === 'dark'}
style={{
cursor: 'pointer',
marginInlineEnd: '8px',
verticalAlign: 'top',
}}
/>
</Popover>
}
onChange={e => {
handleTokenChange(e.target.value);
}}
/>
);
} else if (typeof valueRef.value === 'number') {
inputNode = (
<InputNumber
addonAfter={addonAfter}
bordered={false}
value={tokenValue.value}
disabled={readonly.value}
onChange={newValue => {
handleTokenChange(Number(newValue));
}}
/>
);
} else {
inputNode = (
<Input
addonAfter={addonAfter}
bordered={false}
value={String(tokenValue.value)}
disabled={readonly.value}
onChange={e => {
handleTokenChange(
typeof value.value === 'number' ? Number(e.target.value) : e.target.value,
);
}}
/>
);
}
return wrapSSR(
<div
{...attrs}
class={classNames('previewer-token-input', hashId.value, attrs.class, {
'previewer-token-input-light': light.value,
'previewer-token-input-readonly': readonly.value,
})}
>
{inputNode}
</div>,
);
};
},
});
export default TokenInput;