Skip to content

feat(ColorPicker): add ColorPicker component #6722

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
39e8611
feat: vc-color-picker
CCherry07 Jul 9, 2023
141348c
feat: color-picker
CCherry07 Jul 9, 2023
10331e3
fix: colorClear change ColorPickerPanel action error
CCherry07 Jul 10, 2023
4571932
chore: add demo Allow Clear
CCherry07 Jul 10, 2023
8898fba
feat: add locale with ColorPicker
CCherry07 Jul 10, 2023
cd949cf
feat: add demo
CCherry07 Jul 10, 2023
9fb4115
docs(ColorPicker): add docs
CCherry07 Jul 10, 2023
c8ed00a
test(ColorPicker): upload demo snap
CCherry07 Jul 10, 2023
ec82d22
fix: word spelling mistake
CCherry07 Jul 10, 2023
65f22ee
test(ColorPicker): upload demo snap
CCherry07 Jul 10, 2023
fe3a422
docs: update docs
CCherry07 Jul 10, 2023
35221cb
style(ColorPicker): code style
CCherry07 Jul 11, 2023
6191a75
fix: other error
CCherry07 Jul 11, 2023
1453d3f
feat(ColorPicker): Sync with antd
CCherry07 Jul 11, 2023
ab11c67
feat: color action
Jul 12, 2023
3674c23
demo(colorpicker): add som demo
Jul 12, 2023
9ea2b12
test: upload demo snap
Jul 12, 2023
1cd6409
docs(colorpicker): update docs
CCherry07 Jul 13, 2023
797e50b
docs(colorpicker): update docs
CCherry07 Jul 13, 2023
e22e09f
fix: disabledDrag and direction
CCherry07 Jul 18, 2023
531ea07
fix: should work after closing the browser menu
CCherry07 Jul 18, 2023
ef50d4e
Merge branch 'main' into feat-color-picker
tangjinzhou Aug 6, 2023
cdf17c0
feat: update some code
CCherry07 Sep 11, 2023
99339a6
Merge branch 'vueComponent:main' into feat-color-picker
CCherry07 Sep 12, 2023
8986643
test (ColorPicker): add test
CCherry07 Sep 12, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
259 changes: 259 additions & 0 deletions components/color-picker/ColorPicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
import type { CSSProperties, ComputedRef, ExtractPropTypes, PropType } from 'vue';
import type { HsbaColorType } from '../vc-color-picker';
import type { PopoverProps } from '../popover';
import type { Color } from './color';
import type { PanelRender } from './ColorPickerPanel';

import { computed, defineComponent, shallowRef } from 'vue';

import useConfigInject from '../config-provider/hooks/useConfigInject';
import Popover from '../popover';
import theme from '../theme';
import { generateColor } from './util';
import PropTypes from '../_util/vue-types';
import useMergedState from '../_util/hooks/useMergedState';
import classNames from '../_util/classNames';
import ColorPickerPanel from './ColorPickerPanel';

import ColorTrigger from './components/ColorTrigger';
import useColorState from './hooks/useColorState';
import useStyle from './style';

import type {
ColorFormat,
ColorPickerBaseProps,
PresetsItem,
TriggerPlacement,
TriggerType,
} from './interface';
import type { VueNode } from '../_util/type';
import { useCompactItemContext } from '../space/Compact';

const colorPickerProps = () => ({
value: {
type: [String, Object] as PropType<string | Color>,
default: undefined,
},
defaultValue: {
type: [String, Object] as PropType<string | Color>,
default: undefined,
},
open: PropTypes.looseBool,
disabled: PropTypes.looseBool,
placement: {
type: String as PropType<TriggerPlacement>,
default: 'bottomLeft',
},
trigger: {
type: String as PropType<TriggerType>,
default: 'click',
},
format: {
type: String as PropType<'hex' | 'hsb' | 'rgb'>,
default: 'hex',
},
allowClear: {
type: Boolean as PropType<boolean>,
default: false,
},
presets: {
type: Array as PropType<PresetsItem[]>,
default: undefined,
},
arrow: {
type: [Boolean, Object] as PropType<boolean | { pointAtCenter: boolean }>,
default: true,
},
styles: {
type: Object as PropType<{ popup?: CSSProperties; popupOverlayInner?: StyleSheet }>,
default: () => ({}),
},
rootClassName: PropTypes.string,
onOpenChange: {
type: Function as PropType<(open: boolean) => void>,
default: () => {},
},
onFormatChange: {
type: Function as PropType<(format: ColorFormat) => void>,
default: () => {},
},
onChange: {
type: Function as PropType<(value: Color, hex: string) => void>,
default: () => {},
},
getPopupContainer: {
type: Function as PropType<PopoverProps['getPopupContainer']>,
default: undefined,
},
autoAdjustOverflow: {
type: Boolean as PropType<PopoverProps['autoAdjustOverflow']>,
default: true,
},
onChangeComplete: {
type: Function as PropType<(value: Color) => void>,
default: () => {},
},
showText: {
type: [Boolean, Function] as PropType<boolean | ((color: Color) => VueNode)>,
default: false,
},

size: PropTypes.oneOf(['small', 'middle', 'large']),

destroyTooltipOnHide: PropTypes.looseBool,

panelRender: {
type: Function as PropType<PanelRender>,
default: undefined,
},
});

export type ColorPickerProps = Partial<ExtractPropTypes<ReturnType<typeof colorPickerProps>>>;

const ColorPicker = defineComponent({
name: 'AColorPicker',
inheritAttrs: false,
props: colorPickerProps(),
setup(props, { slots, attrs, emit }) {
const { prefixCls, getPopupContainer, direction } = useConfigInject('color-picker', props);
const { token } = theme.useToken();
const value = computed(() => props.value);
const [colorValue, setColorValue] = useColorState(token.value.colorPrimary, {
value,
defaultValue: props.defaultValue,
});

const open = computed(() => props.open);
const [popupOpen, setPopupOpen] = useMergedState(false, {
value: open,
postState: openData => !props.disabled && openData,
onChange: props.onOpenChange,
});
const format = computed(() => props.format);

const [formatValue, setFormatValue] = useMergedState(props.format, {
value: format,
onChange: props.onFormatChange,
});

// ===================== Style =====================
const { compactSize } = useCompactItemContext(prefixCls, direction);
const mergedSize = computed(() => props.size || compactSize.value);
const [wrapSSR, hashId] = useStyle(prefixCls);

const rtlCls = computed(() => ({ [`${prefixCls.value}-rtl`]: direction.value }));
const mergeCls = computed(() => {
const mergeRootCls = classNames(props.rootClassName, rtlCls.value);
return classNames(
{
[`${prefixCls.value}-sm`]: mergedSize.value === 'small',
[`${prefixCls.value}-lg`]: mergedSize.value === 'large',
},
mergeRootCls,
hashId.value,
);
});

const mergePopupCls = computed(() => classNames(prefixCls.value, rtlCls.value));

const colorCleared = shallowRef(false);

const popupAllowCloseRef = shallowRef(true);

const handleChange = (data: Color, type?: HsbaColorType, pickColor?: boolean) => {
let color: Color = generateColor(data);
const isNull = value.value === null || (!value.value && props.defaultValue === null);
if (colorCleared.value || isNull) {
colorCleared.value = false;
const hsba = color.toHsb();
// ignore alpha slider
if (colorValue.value.toHsb().a === 0 && type !== 'alpha') {
hsba.a = 1;
color = generateColor(hsba);
}
}
if (pickColor) {
popupAllowCloseRef.value = false;
}

setColorValue(color);
emit('update:value', color, color.toHexString());
emit('change', color, color.toHexString());
};

const handleClear = () => {
colorCleared.value = true;
emit('clear');
};
const handleChangeComplete = color => {
popupAllowCloseRef.value = true;
emit('changeComplete', generateColor(color));
};

const onFormatChange = (format: ColorFormat) => {
setFormatValue(format);
emit('formatChange', format);
};
const popoverProps: ComputedRef<PopoverProps> = computed(() => ({
open: popupOpen.value,
trigger: props.trigger,
placement: props.placement,
arrow: props.arrow,
rootClassName: props.rootClassName,
getPopupContainer: getPopupContainer.value,
autoAdjustOverflow: props.autoAdjustOverflow,
destroyTooltipOnHide: props.destroyTooltipOnHide,
}));
const colorBaseProps: ComputedRef<ColorPickerBaseProps> = computed(() => ({
prefixCls: prefixCls.value,
color: colorValue.value,
allowClear: props.allowClear,
colorCleared: colorCleared.value,
disabled: props.disabled,
presets: props.presets,
format: formatValue.value,
panelRender: props.panelRender,
onFormatChange,
onChangeComplete: handleChangeComplete,
}));
return () => {
return wrapSSR(
<Popover
// @ts-ignore
style={props.styles?.popup}
// @ts-ignore
overlayInnerStyle={props.styles?.popupOverlayInner}
onOpenChange={visible => {
if (popupAllowCloseRef.value) {
setPopupOpen(visible);
}
}}
content={
<ColorPickerPanel
{...colorBaseProps.value}
onChange={handleChange}
onClear={handleClear}
/>
}
overlayClassName={mergePopupCls.value}
{...popoverProps.value}
>
{slots.children?.() || (
<ColorTrigger
open={popupOpen.value}
class={mergeCls.value}
style={attrs.style}
color={props.value ? generateColor(props.value) : colorValue.value}
prefixCls={prefixCls.value}
disabled={props.disabled}
showText={props.showText}
colorCleared={colorCleared.value}
/>
)}
</Popover>,
);
};
},
});

export default ColorPicker;
72 changes: 72 additions & 0 deletions components/color-picker/ColorPickerPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import type { HsbaColorType } from '../vc-color-picker';
import type { ColorPickerBaseProps } from './interface';
import type { Color } from './color';

import { computed, defineComponent, provide } from 'vue';
import { PanelPickerContext, PanelPresetsContext } from './context';
import Divider from '../divider';
import PanelPicker from './components/PanelPicker';
import PanelPresets from './components/PanelPresets';
import type { VueNode } from '../_util/type';

export type PanelRender = (
innerPanel: VueNode,
{
components,
}: {
components: { Picker: typeof PanelPicker; Presets: typeof PanelPresets };
},
) => VueNode;

interface ColorPickerPanelProps extends ColorPickerBaseProps {
onChange?: (value?: Color, type?: HsbaColorType) => void;
onClear?: (clear?: boolean) => void;
panelRender?: PanelRender;
}

const ColorPickerPanel = defineComponent({
name: 'ColorPickerPanel',
inheritAttrs: false,
props: ['prefixCls', 'presets', 'onChange', 'onClear', 'color', 'panelRender'],
setup(props: ColorPickerPanelProps, { attrs }) {
const colorPickerPanelPrefixCls = computed(() => `${props.prefixCls}-inner-content`);
// ==== Inject props ===
const panelPickerProps = computed(() => ({
prefixCls: props.prefixCls,
value: props.color,
onChange: props.onChange,
onClear: props.onClear,
...attrs,
}));
const panelPresetsProps = computed(() => ({
prefixCls: props.prefixCls,
value: props.color,
presets: props.presets,
onChange: props.onChange,
}));
// ==== Inject ===
provide(PanelPickerContext, panelPickerProps);
provide(PanelPresetsContext, panelPresetsProps);
// ==== Render ===
const innerPanel = computed(() => (
<>
<PanelPicker />
{Array.isArray(props.presets) && (
<Divider class={`${colorPickerPanelPrefixCls.value}-divider`} />
)}
<PanelPresets />
</>
));
return () => (
<div class={colorPickerPanelPrefixCls.value}>
{typeof props.panelRender === 'function'
? props.panelRender(innerPanel.value, {
components: { Picker: PanelPicker, Presets: PanelPresets } as any,
})
: innerPanel.value}
</div>
);
},
});

export default ColorPickerPanel;
Loading