diff --git a/packages/components/_private/trigger/src/types.ts b/packages/components/_private/trigger/src/types.ts index 8aad5d525..a2300bd8d 100644 --- a/packages/components/_private/trigger/src/types.ts +++ b/packages/components/_private/trigger/src/types.ts @@ -13,15 +13,15 @@ import type { DefineComponent, HTMLAttributes, PropType, Slot } from 'vue' export const triggerProps = { value: { type: null, default: undefined }, raw: { type: Boolean, default: false }, - borderless: Boolean, - clearable: Boolean, + borderless: { type: Boolean, default: undefined }, + clearable: { type: Boolean, default: undefined }, clearIcon: { type: String as PropType, default: 'close-circle' }, - disabled: Boolean, - focused: Boolean, + disabled: { type: Boolean, default: undefined }, + focused: { type: Boolean, default: undefined }, monitorFocus: { type: Boolean, default: true }, paddingless: { type: Boolean, default: false }, placeholder: String, - readonly: Boolean, + readonly: { type: Boolean, default: undefined }, size: { type: String as PropType, default: 'md' }, status: String as PropType, suffix: String as PropType, diff --git a/packages/components/cascader/src/Cascader.tsx b/packages/components/cascader/src/Cascader.tsx index 351636ee8..8af0a07a2 100644 --- a/packages/components/cascader/src/Cascader.tsx +++ b/packages/components/cascader/src/Cascader.tsx @@ -161,7 +161,6 @@ export default defineComponent({ focused={focused} maxLabel={props.maxLabel} multiple={props.multiple} - monitorFocus={false} opened={opened} placeholder={props.placeholder} readonly={readonly} @@ -225,8 +224,8 @@ export default defineComponent({ const controlTriggerProps = { autofocus: props.autofocus, overlayClassName: overlayClasses.value, - overlayContainer: props.overlayContainer, - overlayContainerFallback: `${mergedPrefixCls.value}-overlay-container`, + overlayContainer: props.overlayContainer ?? config.overlayContainer, + overlayContainerFallback: `.${mergedPrefixCls.value}-overlay-container`, overlayMatchWidth: props.overlayMatchWidth ?? config.overlayMatchWidth, class: mergedPrefixCls.value, borderless, diff --git a/packages/components/control-trigger/src/ControlTriggerOverlay.tsx b/packages/components/control-trigger/src/ControlTriggerOverlay.tsx index 4c1bc220f..41b33cb85 100644 --- a/packages/components/control-trigger/src/ControlTriggerOverlay.tsx +++ b/packages/components/control-trigger/src/ControlTriggerOverlay.tsx @@ -39,6 +39,7 @@ export default defineComponent({ disabled: controlTriggerProps.disabled || controlTriggerProps.readonly, offset: controlTriggerProps.offset ?? defaultOffset, placement: props.placement ?? 'bottomStart', + showArrow: props.showArrow, transitionName: `${common.prefixCls}-slide-auto`, trigger: props.trigger ?? 'click', triggerId: attrs.id, diff --git a/packages/components/index.ts b/packages/components/index.ts index e67aeffbc..74426cda3 100644 --- a/packages/components/index.ts +++ b/packages/components/index.ts @@ -250,6 +250,7 @@ export * from '@idux/components/radio' export * from '@idux/components/rate' export * from '@idux/components/result' export * from '@idux/components/select' +export * from '@idux/components/selector' export * from '@idux/components/skeleton' export * from '@idux/components/slider' export * from '@idux/components/space' diff --git a/packages/components/select/src/Select.tsx b/packages/components/select/src/Select.tsx index 03a831d58..de8de02ef 100644 --- a/packages/components/select/src/Select.tsx +++ b/packages/components/select/src/Select.tsx @@ -176,7 +176,6 @@ export default defineComponent({ focused={focused} maxLabel={props.maxLabel} multiple={props.multiple} - monitorFocus={false} opened={opened} placeholder={props.placeholder} readonly={readonly} @@ -237,8 +236,8 @@ export default defineComponent({ const controlTriggerProps = { autofocus: props.autofocus, overlayClassName: overlayClasses.value, - overlayContainer: props.overlayContainer, - overlayContainerFallback: `${mergedPrefixCls.value}-overlay-container`, + overlayContainer: props.overlayContainer ?? config.overlayContainer, + overlayContainerFallback: `.${mergedPrefixCls.value}-overlay-container`, overlayMatchWidth: props.overlayMatchWidth ?? config.overlayMatchWidth, class: mergedPrefixCls.value, borderless, diff --git a/packages/components/selector/style/index.less b/packages/components/selector/style/index.less index ff38eca8b..e94cbbfff 100644 --- a/packages/components/selector/style/index.less +++ b/packages/components/selector/style/index.less @@ -23,11 +23,16 @@ &-item { .ellipsis(); + max-width: 100%; user-select: none; &-label { .ellipsis(); } + + &-disabled { + color: var(--ix-color-text-disabled); + } } &-input { @@ -60,4 +65,20 @@ cursor: auto; } } + + &-sm { + .@{selector-prefix}-item { + max-width: calc(100% - var(--ix-font-size-icon) + var(--ix-control-padding-size-horizontal-sm)); + } + } + &-md { + .@{selector-prefix}-item { + max-width: calc(100% - var(--ix-font-size-icon) + var(--ix-control-padding-size-horizontal-md)); + } + } + &-lg { + .@{selector-prefix}-item { + max-width: calc(100% - var(--ix-font-size-icon) + var(--ix-control-padding-size-horizontal-lg)); + } + } } diff --git a/packages/components/tree-select/src/TreeSelect.tsx b/packages/components/tree-select/src/TreeSelect.tsx index b24b22b05..0fee3572c 100644 --- a/packages/components/tree-select/src/TreeSelect.tsx +++ b/packages/components/tree-select/src/TreeSelect.tsx @@ -164,7 +164,6 @@ export default defineComponent({ focused={focused} maxLabel={props.maxLabel} multiple={props.multiple} - monitorFocus={false} opened={opened} placeholder={props.placeholder} readonly={props.readonly} @@ -198,7 +197,7 @@ export default defineComponent({ offset: props.offset ?? config.offset, overlayClassName: overlayClasses.value, overlayContainer: props.overlayContainer ?? config.overlayContainer, - overlayContainerFallback: `${mergedPrefixCls.value}-overlay-container`, + overlayContainerFallback: `.${mergedPrefixCls.value}-overlay-container`, overlayMatchWidth: props.overlayMatchWidth ?? config.overlayMatchWidth, 'onUpdate:open': setOverlayOpened, onFocus: handleFocus, diff --git a/packages/pro/config/src/defaultConfig.ts b/packages/pro/config/src/defaultConfig.ts index c8684b2b7..2ac7fc027 100644 --- a/packages/pro/config/src/defaultConfig.ts +++ b/packages/pro/config/src/defaultConfig.ts @@ -35,6 +35,13 @@ export const defaultConfig: ProGlobalConfig = { searchable: false, }, }, + tagSelect: { + borderless: false, + clearable: false, + clearIcon: 'close-circle', + size: 'md', + suffix: 'down', + }, tree: { clearIcon: 'close-circle', collapseIcon: ['collapse', 'expand'], diff --git a/packages/pro/config/src/types.ts b/packages/pro/config/src/types.ts index 3f17b016b..d8f3b1f1d 100644 --- a/packages/pro/config/src/types.ts +++ b/packages/pro/config/src/types.ts @@ -23,6 +23,7 @@ export interface ProGlobalConfig { form: ProFormConfig table: ProTableConfig + tagSelect: ProTagSelectConfig tree: ProTreeConfig textarea: ProTextareaConfig search: ProSearchConfig @@ -80,6 +81,14 @@ export interface ProTableConfig { } } +export interface ProTagSelectConfig { + borderless: boolean + clearable: boolean + clearIcon: string + size: FormSize + suffix: string +} + export interface ProTreeConfig { clearIcon: string collapseIcon: [string, string] diff --git a/packages/pro/dark.full.css b/packages/pro/dark.full.css index cc33b12a9..b4998ee3d 100644 --- a/packages/pro/dark.full.css +++ b/packages/pro/dark.full.css @@ -86,6 +86,26 @@ --ix-pro-search-tree-select-panel-max-width: 400px; } +/* ------ pro-tag-select css variables ------ */ +:root { + --ix-pro-tag-select-preset-color-grey-label: #525966; + --ix-pro-tag-select-preset-color-grey-bg: rgb(24, 27, 32); + --ix-pro-tag-select-preset-color-green-label: #56bd37; + --ix-pro-tag-select-preset-color-green-bg: rgb(25, 47, 23); + --ix-pro-tag-select-preset-color-blue-label: #4083e8; + --ix-pro-tag-select-preset-color-blue-bg: rgb(21, 36, 58); + --ix-pro-tag-select-preset-color-yellow-label: #e8d43e; + --ix-pro-tag-select-preset-color-yellow-bg: rgb(54, 52, 24); + --ix-pro-tag-select-preset-color-red-label: #e8514c; + --ix-pro-tag-select-preset-color-red-bg: rgb(54, 26, 27); + --ix-pro-tag-select-preset-color-orange-label: #e88641; + --ix-pro-tag-select-preset-color-orange-bg: rgb(54, 36, 25); + --ix-pro-tag-select-color-indicator-size: 12px; + --ix-pro-tag-select-panel-max-height: 256px; + --ix-pro-tag-select-edit-panel-min-width: 225px; + --ix-pro-tag-select-tag-height: 20px; +} + /* ------ pro-textarea css variables ------ */ :root { --ix-pro-textarea-index-col-color: #f4f8ff; diff --git a/packages/pro/default.full.css b/packages/pro/default.full.css index 15fd0af4b..4348ea17a 100644 --- a/packages/pro/default.full.css +++ b/packages/pro/default.full.css @@ -76,6 +76,26 @@ --ix-pro-search-tree-select-panel-max-width: 400px; } +/* ------ pro-tag-select css variables ------ */ +:root { + --ix-pro-tag-select-preset-color-grey-label: #a1a7b3; + --ix-pro-tag-select-preset-color-grey-bg: rgb(246, 246, 247); + --ix-pro-tag-select-preset-color-green-label: #39c317; + --ix-pro-tag-select-preset-color-green-bg: rgb(235, 249, 232); + --ix-pro-tag-select-preset-color-blue-label: #1c6eff; + --ix-pro-tag-select-preset-color-blue-bg: rgb(232, 241, 255); + --ix-pro-tag-select-preset-color-yellow-label: #f8d81a; + --ix-pro-tag-select-preset-color-yellow-bg: rgb(254, 251, 232); + --ix-pro-tag-select-preset-color-red-label: #f52727; + --ix-pro-tag-select-preset-color-red-bg: rgb(254, 233, 233); + --ix-pro-tag-select-preset-color-orange-label: #fa721b; + --ix-pro-tag-select-preset-color-orange-bg: rgb(255, 241, 232); + --ix-pro-tag-select-color-indicator-size: 12px; + --ix-pro-tag-select-panel-max-height: 256px; + --ix-pro-tag-select-edit-panel-min-width: 225px; + --ix-pro-tag-select-tag-height: 20px; +} + /* ------ pro-textarea css variables ------ */ :root { --ix-pro-textarea-index-col-color: #2f3540; diff --git a/packages/pro/index.less b/packages/pro/index.less index 49de2ce72..d97a3d77c 100644 --- a/packages/pro/index.less +++ b/packages/pro/index.less @@ -3,6 +3,7 @@ @import './form/style/index.less'; @import './layout/style/index.less'; @import './table/style/index.less'; +@import './tag-select/style/index.less'; @import './textarea/style/index.less'; @import './transfer/style/index.less'; @import './tree/style/index.less'; diff --git a/packages/pro/index.ts b/packages/pro/index.ts index 2e89b8b47..f2be52a53 100644 --- a/packages/pro/index.ts +++ b/packages/pro/index.ts @@ -11,6 +11,7 @@ import { IxProForm } from '@idux/pro/form' import { IxProLayout, IxProLayoutSiderTrigger } from '@idux/pro/layout' import { IxProSearch, IxProSearchShortcut } from '@idux/pro/search' import { IxProTable, IxProTableLayoutTool } from '@idux/pro/table' +import { IxProTagSelect } from '@idux/pro/tag-select' import { IxProTextarea } from '@idux/pro/textarea' import { IxProTransfer } from '@idux/pro/transfer' import { IxProTree } from '@idux/pro/tree' @@ -24,6 +25,7 @@ const components = [ IxProLayoutSiderTrigger, IxProTable, IxProTableLayoutTool, + IxProTagSelect, IxProTransfer, IxProTree, IxProTextarea, @@ -50,6 +52,7 @@ export * from '@idux/pro/form' export * from '@idux/pro/layout' export * from '@idux/pro/search' export * from '@idux/pro/table' +export * from '@idux/pro/tag-select' export * from '@idux/pro/textarea' export * from '@idux/pro/transfer' export * from '@idux/pro/tree' diff --git a/packages/pro/locales/src/langs/en-US.ts b/packages/pro/locales/src/langs/en-US.ts index 5f9f8582b..e9541085c 100644 --- a/packages/pro/locales/src/langs/en-US.ts +++ b/packages/pro/locales/src/langs/en-US.ts @@ -31,6 +31,23 @@ const enUS: ProLocale = { noPinTitle: 'Unpinned', }, }, + tagSelect: { + colors: { + grey: 'grey', + green: 'green', + blue: 'blue', + yellow: 'yellow', + red: 'red', + orange: 'orange', + }, + remove: 'delete', + createTag: 'Create tag', + removeTag: 'Remove tag', + maxExceededAlert: 'Tags count reached limit of ${0}', + empty: 'No Data', + ok: 'OK', + cancel: 'Cancel', + }, tree: { expandAll: 'Expand', collapseAll: 'Collapse', diff --git a/packages/pro/locales/src/langs/zh-CN.ts b/packages/pro/locales/src/langs/zh-CN.ts index bf07c8d86..9d62a4634 100644 --- a/packages/pro/locales/src/langs/zh-CN.ts +++ b/packages/pro/locales/src/langs/zh-CN.ts @@ -31,6 +31,23 @@ const zhCN: ProLocale = { noPinTitle: '不固定', }, }, + tagSelect: { + colors: { + grey: '灰', + green: '绿', + blue: '蓝', + yellow: '黄', + red: '红', + orange: '橙', + }, + remove: '删除', + createTag: '创建标签', + removeTag: '删除标签', + maxExceededAlert: '标签已达${0}个上限,请及时清理', + empty: '暂无数据', + ok: '确定', + cancel: '取消', + }, tree: { expandAll: '展开全部', collapseAll: '收起全部', diff --git a/packages/pro/locales/src/types.ts b/packages/pro/locales/src/types.ts index 35bf94456..be0170030 100644 --- a/packages/pro/locales/src/types.ts +++ b/packages/pro/locales/src/types.ts @@ -43,10 +43,29 @@ export interface ProSearchLocale { switchToDatePanel: string } +export interface ProTagSelectLocale { + colors: { + grey: string + green: string + blue: string + yellow: string + red: string + orange: string + } + remove: string + createTag: string + removeTag: string + maxExceededAlert: string + empty: string + ok: string + cancel: string +} + export interface ProLocale { type: ProLocaleType table: ProTableLocale + tagSelect: ProTagSelectLocale tree: ProTreeLocale search: ProSearchLocale } diff --git a/packages/pro/style/variable/prefix.less b/packages/pro/style/variable/prefix.less index 355861ba0..0a15d2b02 100644 --- a/packages/pro/style/variable/prefix.less +++ b/packages/pro/style/variable/prefix.less @@ -7,4 +7,5 @@ @pro-textarea-prefix: ~'@{idux-pro-prefix}-textarea'; @pro-transfer-prefix: ~'@{idux-pro-prefix}-transfer'; @pro-tree-prefix: ~'@{idux-pro-prefix}-tree'; +@pro-tag-select-prefix: ~'@{idux-pro-prefix}-tag-select'; diff --git a/packages/pro/tag-select/__tests__/__snapshots__/proTagSelect.spec.ts.snap b/packages/pro/tag-select/__tests__/__snapshots__/proTagSelect.spec.ts.snap new file mode 100644 index 000000000..8fb2744c7 --- /dev/null +++ b/packages/pro/tag-select/__tests__/__snapshots__/proTagSelect.spec.ts.snap @@ -0,0 +1,20 @@ +// Vitest Snapshot v1 + +exports[`ProTagSelect > render work 1`] = ` +"
+ +
+
+ +
+
+ +
+
+
+
+
+
+ +
" +`; diff --git a/packages/pro/tag-select/__tests__/proTagSelect.spec.ts b/packages/pro/tag-select/__tests__/proTagSelect.spec.ts new file mode 100644 index 000000000..9e05b5fa4 --- /dev/null +++ b/packages/pro/tag-select/__tests__/proTagSelect.spec.ts @@ -0,0 +1,25 @@ +import { MountingOptions, mount } from '@vue/test-utils' + +import { renderWork } from '@tests' + +import ProTagSelect from '../src/ProTagSelect' +import { ProTagSelectProps } from '../src/types' + +describe.skip('ProTagSelect', () => { + const ProTagSelectMount = (options?: MountingOptions>) => + mount(ProTagSelect, { ...(options as MountingOptions) }) + + renderWork(ProTagSelect, { + props: {}, + }) + + test('xxx work', async () => { + const wrapper = ProTagSelectMount({ props: { xxx: 'Xxx' } }) + + expect(wrapper.classes()).toContain('ix-Xxx') + + await wrapper.setProps({ xxx: 'Yyy' }) + + expect(wrapper.classes()).toContain('ix-Yyy') + }) +}) diff --git a/packages/pro/tag-select/demo/Basic.md b/packages/pro/tag-select/demo/Basic.md new file mode 100644 index 000000000..0ddf241aa --- /dev/null +++ b/packages/pro/tag-select/demo/Basic.md @@ -0,0 +1,14 @@ +--- +order: 0 +title: + zh: 基本使用 + en: Basic usage +--- + +## zh + +最简单的用法。 + +## en + +The simplest usage. diff --git a/packages/pro/tag-select/demo/Basic.vue b/packages/pro/tag-select/demo/Basic.vue new file mode 100644 index 000000000..b84b2845f --- /dev/null +++ b/packages/pro/tag-select/demo/Basic.vue @@ -0,0 +1,66 @@ + + + + + diff --git a/packages/pro/tag-select/demo/ConfirmBeforeSelect.md b/packages/pro/tag-select/demo/ConfirmBeforeSelect.md new file mode 100644 index 000000000..7d77f6b3b --- /dev/null +++ b/packages/pro/tag-select/demo/ConfirmBeforeSelect.md @@ -0,0 +1,14 @@ +--- +order: 1 +title: + zh: 选择前确认 + en: Confirm before select +--- + +## zh + +选择前通过浮层确认选项的创建。 + +## en + +Confirm via an popper before select. diff --git a/packages/pro/tag-select/demo/ConfirmBeforeSelect.vue b/packages/pro/tag-select/demo/ConfirmBeforeSelect.vue new file mode 100644 index 000000000..401b668c1 --- /dev/null +++ b/packages/pro/tag-select/demo/ConfirmBeforeSelect.vue @@ -0,0 +1,76 @@ + + + + + diff --git a/packages/pro/tag-select/docs/Api.en.md b/packages/pro/tag-select/docs/Api.en.md new file mode 100644 index 000000000..9d47874e5 --- /dev/null +++ b/packages/pro/tag-select/docs/Api.en.md @@ -0,0 +1,19 @@ +### IxProTagSelect + +#### ProTagSelectProps + +| Name | Description | Type | Default | Global Config | Remark | +| --- | --- | --- | --- | --- | --- | +| - | - | - | - | ✅ | - | + +#### ProTagSelectSlots + +| Name | Description | Parameter Type | Remark | +| --- | --- | --- | --- | +| - | - | - | - | + +#### ProTagSelectMethods + +| Name | Description | Parameter Type | Remark | +| --- | --- | --- | --- | +| - | - | - | - | diff --git a/packages/pro/tag-select/docs/Api.zh.md b/packages/pro/tag-select/docs/Api.zh.md new file mode 100644 index 000000000..a4b8a4a20 --- /dev/null +++ b/packages/pro/tag-select/docs/Api.zh.md @@ -0,0 +1,83 @@ +### IxProTagSelect + +#### ProTagSelectProps + +| 名称 | 说明 | 类型 | 默认值 | 全局配置 | 备注 | +| --- | --- | --- | --- | --- | --- | +| `control` | 控件控制器 | `string \| number \| (string \| number)[] \| AbstractControl` | - | - | 配合 `@idux/cdk/forms` 使用, 参考 [Form](/components/form/zh) | +| `v-model:value` | 当前选中的 tag 的值 | `(string \| number \| symbol)[]` | - | - | 使用 `control` 时,此配置无效 | +| `v-model:open` | 下拉菜单是否展开 | `boolean` | - | - | - | +| `colors` | 标签颜色列表 | `TagSelectColor[]` | - | - | 不提供则使用内置的颜色列表,颜色色值可以通过主题自定义 | +| `clearable` | 是否可清除 | `boolean` | `false` | - | - | +| `clearIcon` | 设置清除图标 | `string \| #clearIcon` | `'close-circle'` | - | - | +| `dataSource` | 可选择的标签数据 | `TagSelectData[]` | - | - | - | +| `dataEditable` | 标签数据是否可以编辑 | `boolean` | `true` | - | 配置true则可以在下拉面板的选项末尾点击编辑图标进行编辑 | +| `disabled` | 是否禁用 | `boolean` | `false` | - | - | +| `confirmBeforeSelect` | 是否在选择标签之后需要确认 | `boolean \| 'force'` | `false` | - | 配置true或者force时,选择标签后会弹出一个浮层进行确定或者取消,配置force则强制必须确定或者取消才能进行下一步 | +| `createdTagDataModifier` | 修改创建的标签数据 | `(data: TagSelectData) => TagSelectData` | - | - | 标签创建时,会随机选择一个内置的颜色,可以通过该函数修改内部创建的数据 | +| `maxTags` | 选择框内展示的标签最大数量 | `number \| 'responsive'` | `Number.MAX_SAFE_INTEGER` | - | - | +| `tagsLimit` | 可以选择的标签最大数量 | `number` | `Number.MAX_SAFE_INTEGER` | - | - | +| `tagDataLimit` | 面板中允许的最大标签数据量 | `number` | `Number.MAX_SAFE_INTEGER` | - | 超出后不允许添加标签,并且会显示提示 | +| `overlayClassName` | 下拉菜单的 `class` | `string` | - | - | - | +| `overlayContainer` | 自定义下拉框容器节点 | `string \| HTMLElement \| (trigger?: Element) => string \| HTMLElement` | - | - | - | +| `overlayMatchWidth` | 下拉菜单和选择器同宽 | `boolean \| 'minWidth'` | `true` | - | - | +| `placeholder` | 选择框默认文本 | `string \| #placeholder` | - | - | - | +| `readonly` | 只读模式 | `boolean` | - | - | - | +| `removeConfirmHeader` | 删除标签的确认弹窗头部配置 | `string \| HeaderProps` | - | - | - | +| `removeConfirmTitle` | 删除标签的确认弹窗title | `string \| VNode \| (() => VNodeChild)` | - | - | - | +| `size` | 设置选择器大小 | `'sm' \| 'md' \| 'lg'` | `md` | - | - | +| `status` | 手动指定校验状态 | `valid \| invalid \| validating` | - | - | - | +| `suffix` | 设置后缀图标 | `string \| #suffix` | `down` | - | - | +| `onClear` | 清除图标被点击后的回调 | `(evt: MouseEvent) => void` | - | - | - | +| `onChange` | 选中值发生改变后的回调 | `(value: (string \| number \| symbol)[] \| undefined, oldValue: (string \| number \| symbol)[] \| undefined) => void` | - | - | - | +| `onFocus` | 获取焦点后的回调 | `(evt: FocusEvent) => void` | - | - | - | +| `onBlur` | 失去焦点后的回调 | `(evt: FocusEvent) => void` | - | - | - | +| `onTagDataChange` | 标签数据编辑之后的回调 | `(data: TagSelectData) => void` | - | - | - | +| `onTagDataRemove` | 标签数据删除之后的回调 | `(data: TagSelectData) => void` | - | - | - | +| `onTagDataAdd` | 标签数据创建添加之后的回调 | `(data: TagSelectData) => void` | - | - | - | +| `onTagSelect` | 标签选择触发之后的回调 | `(data: TagSelectData) => void` | - | - | - | +| `onTagSelectConfirm` | 标签选择之后确认的回调 | `(data: TagSelectData) => void` | - | - | - | +| `onTagSelectCancel` | 标签选择之后取消的回调 | `(data: TagSelectData) => void` | - | - | - | +| `onTagRemove` | 选择的标签被移除之后的回调 | `(data: TagSelectData) => void` | - | - | - | + +```ts +interface TagSelectColor { + key: VKey + name: string + labelColor: string + backgroundColor: string + borderColor?: string +} + +interface TagSelectData { + key: VKey + label: string + color: VKey | TagSelectColor + disabled?: boolean + + [key: string]: any +} +``` + +#### ProTagSelectSlots + +| 名称 | 说明 | 参数类型 | 备注 | +| --- | --- | --- | --- | +| `clearIcon` | 自定义清除图标 | - | - | +| `suffix` | 自定义选择框的后缀 | - | - | +| `selectedLabel` | 自定义选中标签的文本内容 | `TagSelectData` | - | +| `overflowedLabel` | 自定义超出最多显示多少个标签的内容 | `TagSelectData[]` | 参数为超出的数组 | +| `tagLabel` | 自定义面板中标签的文本内容 | `TagSelectData` | - | +| `optionLabel` | 自定义面板中选项的内容 | `TagSelectData` | - | +| `maxExceededAlert` | 自定义选中的标签超出最大限制时的告警提示 | - | - | +| `placeholder` | 自定义占位符 | - | - | +| `selectConfirmContent` | 自定义选中标签时的确认浮层内容 | `TagSelectData` | - | +| `removeConfirmTitle` | 自定义移除标签数据时的确认弹窗title | `TagSelectData` | - | +| `removeConfirmContent` | 自定义移除标签数据时的确认弹窗内容 | `TagSelectData` | - | + +#### ProTagSelectMethods + +| 名称 | 说明 | 参数类型 | 备注 | +| --- | --- | --- | --- | +| `blur` | 失去焦点 | - | - | +| `focus` | 获取焦点 | - | - | diff --git a/packages/pro/tag-select/docs/Design.en.md b/packages/pro/tag-select/docs/Design.en.md new file mode 100644 index 000000000..1aa4a9cf2 --- /dev/null +++ b/packages/pro/tag-select/docs/Design.en.md @@ -0,0 +1 @@ +### Usage scenarios diff --git a/packages/pro/tag-select/docs/Design.zh.md b/packages/pro/tag-select/docs/Design.zh.md new file mode 100644 index 000000000..aa5080171 --- /dev/null +++ b/packages/pro/tag-select/docs/Design.zh.md @@ -0,0 +1 @@ +### 使用场景 diff --git a/packages/pro/tag-select/docs/Index.en.md b/packages/pro/tag-select/docs/Index.en.md new file mode 100644 index 000000000..aeb31dba9 --- /dev/null +++ b/packages/pro/tag-select/docs/Index.en.md @@ -0,0 +1,8 @@ +--- +category: pro +type: Data Entry +order: 0 +title: ProTagSelect +subtitle: +--- + diff --git a/packages/pro/tag-select/docs/Index.zh.md b/packages/pro/tag-select/docs/Index.zh.md new file mode 100644 index 000000000..48ec9a88f --- /dev/null +++ b/packages/pro/tag-select/docs/Index.zh.md @@ -0,0 +1,8 @@ +--- +category: pro +type: 数据录入 +order: 0 +title: ProTagSelect +subtitle: 标签选择 +--- + diff --git a/packages/pro/tag-select/docs/Theme.en.md b/packages/pro/tag-select/docs/Theme.en.md new file mode 100644 index 000000000..06e4c3fce --- /dev/null +++ b/packages/pro/tag-select/docs/Theme.en.md @@ -0,0 +1,3 @@ +| name | default | seer | mark | +| --- | --- | --- | --- | +| - | - | - | - | diff --git a/packages/pro/tag-select/docs/Theme.zh.md b/packages/pro/tag-select/docs/Theme.zh.md new file mode 100644 index 000000000..2dc1ef68e --- /dev/null +++ b/packages/pro/tag-select/docs/Theme.zh.md @@ -0,0 +1,18 @@ +| 名称 | 描述 | 类型 | default | dark | +|---|---|---|---|---| +| `colorIndicatorSize` | 颜色指示圆圈的尺寸 | `number` | `12` | `12` | +| `editPanelMinWidth` | 编辑面板的最小宽度 | `number` | `225` | `225` | +| `panelMaxHeight` | 数据面板的最大高度 | `number` | `256` | `256` | +| `presetColorBlueBg` | 预设蓝色背景色 | `string` | `rgb(232, 241, 255)` | `rgb(21, 36, 58)` | +| `presetColorBlueLabel` | 预设蓝色文字色 | `string` | `#1c6eff` | `#4083E8` | +| `presetColorGreenBg` | 预设绿色背景色 | `string` | `rgb(235, 249, 232)` | `rgb(25, 47, 23)` | +| `presetColorGreenLabel` | 预设绿色文字色 | `string` | `#39c317` | `#56BD37` | +| `presetColorGreyBg` | 预设灰色背景色 | `string` | `rgb(246, 246, 247)` | `rgb(24, 27, 32)` | +| `presetColorGreyLabel` | 预设灰色文字色 | `string` | `#a1a7b3` | `#525966` | +| `presetColorOrangeBg` | 预设橙色背景色 | `string` | `rgb(255, 241, 232)` | `rgb(54, 36, 25)` | +| `presetColorOrangeLabel` | 预设橙色文字色 | `string` | `#fa721b` | `#E88641` | +| `presetColorRedBg` | 预设红色背景色 | `string` | `rgb(254, 233, 233)` | `rgb(54, 26, 27)` | +| `presetColorRedLabel` | 预设红色文字色 | `string` | `#f52727` | `#E8514C` | +| `presetColorYellowBg` | 预设黄色背景色 | `string` | `rgb(254, 251, 232)` | `rgb(54, 52, 24)` | +| `presetColorYellowLabel` | 预设黄色文字色 | `string` | `#f8d81a` | `#E8D43E` | +| `tagHeight` | 标签的高度 | `number` | `20` | `20` | diff --git a/packages/pro/tag-select/index.ts b/packages/pro/tag-select/index.ts new file mode 100644 index 000000000..2d56977de --- /dev/null +++ b/packages/pro/tag-select/index.ts @@ -0,0 +1,27 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { ProTagSelectComponent } from './src/types' + +import ProTagSelect from './src/ProTagSelect' + +const IxProTagSelect = ProTagSelect as unknown as ProTagSelectComponent + +export { IxProTagSelect } + +export type { + ProTagSelectInstance, + ProTagSelectComponent, + ProTagSelectPublicProps as ProTagSelectProps, + ProTagSelectSlots, + TagSelectColor, + TagSelectData, +} from './src/types' + +export { getThemeTokens as getProTagSelectThemeTokens } from './theme' + +export type { ProTagSelectThemeTokens } from './theme' diff --git a/packages/pro/tag-select/src/ProTagSelect.tsx b/packages/pro/tag-select/src/ProTagSelect.tsx new file mode 100644 index 000000000..5eb6f862a --- /dev/null +++ b/packages/pro/tag-select/src/ProTagSelect.tsx @@ -0,0 +1,251 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import { computed, defineComponent, normalizeClass, provide, ref, watch } from 'vue' + +import { useAccessorAndControl } from '@idux/cdk/forms' +import { VKey, callEmit, useState } from '@idux/cdk/utils' +import { type ControlTriggerSlots, IxControlTrigger } from '@idux/components/control-trigger' +import { useFormItemRegister, useFormStatus } from '@idux/components/form' +import { IxSelector, type SelectorInstance } from '@idux/components/selector' +import { useMergedCommonControlProps } from '@idux/components/utils' +import { useGlobalConfig } from '@idux/pro/config' +import { useThemeToken } from '@idux/pro/theme' + +import { useKeyboardEvents } from './composables/useKeyboardEvents' +import { useOperations } from './composables/useOperations' +import { useOverlayState } from './composables/useOverlayState' +import { usePanelActiveState } from './composables/usePanelActiveState' +import { useRemoveConfirm } from './composables/useRemoveConfirm' +import { useSelectConfirm } from './composables/useSelectConfirm' +import { useSelectedState } from './composables/useSelectedState' +import { useTagColors } from './composables/useTagColors' +import { useTagData } from './composables/useTagData' +import { useTagEdit } from './composables/useTagEdit' +import SelectedTag from './content/SelectedTag' +import TagDataRemoveConfirmModal from './content/TagDataRemoveConfirmModal' +import TagSelectPanel from './content/TagSelectPanel' +import { proTagSelectContext } from './token' +import { proTagSelectProps } from './types' +import { getThemeTokens } from '../theme' + +export default defineComponent({ + name: 'IxProTagSelect', + inheritAttrs: false, + props: proTagSelectProps, + setup(props, { expose, slots, attrs }) { + const commonConfig = useGlobalConfig('common') + const { globalHashId, hashId, registerToken } = useThemeToken('proTagSelect') + registerToken(getThemeTokens) + + const locales = useGlobalConfig('locale') + const config = useGlobalConfig('tagSelect') + const { accessor, control } = useAccessorAndControl() + const commonControlProps = useMergedCommonControlProps(props, config) + useFormItemRegister(control) + const mergedStatus = useFormStatus(props, control) + + const [focused, setFocused] = useState(false) + + const mergedPrefixCls = computed(() => `${commonConfig.prefixCls}-tag-select`) + + const triggerRef = ref() + const focus = () => triggerRef.value?.focus() + const blur = () => triggerRef.value?.blur() + + const tagColorsContext = useTagColors(props, locales.tagSelect) + const tagDataContext = useTagData(props, tagColorsContext) + const overlayStateContext = useOverlayState(props) + const tagEditContext = useTagEdit(overlayStateContext) + const selectedStateContext = useSelectedState(props, accessor, tagDataContext) + const selectConfirmContext = useSelectConfirm(props, tagDataContext, selectedStateContext, overlayStateContext) + const removeConfirmContext = useRemoveConfirm(tagDataContext, selectedStateContext, overlayStateContext) + const operationContext = useOperations( + tagDataContext, + selectConfirmContext, + removeConfirmContext, + selectedStateContext, + ) + const panelActiveStateContext = usePanelActiveState(tagDataContext, selectedStateContext) + const handleKeydown = useKeyboardEvents( + props, + tagDataContext, + selectedStateContext, + selectConfirmContext, + overlayStateContext, + panelActiveStateContext, + operationContext, + ) + + const { mergedData, inputValue, setInputValue } = tagDataContext + const { selectedData, selectedValue } = selectedStateContext + const { dataToSelect, handleTagSelectCancel } = selectConfirmContext + const { dataToEdit } = tagEditContext + const { overlayOpened, setOverlayOpened, setEditPanelOpened } = overlayStateContext + const { handleTagClear } = operationContext + + const mergedSelectedData = computed(() => { + const data = [...selectedData.value] + + if (dataToSelect.value) { + data.push(dataToSelect.value) + } + + return data + }) + + watch(mergedData, data => { + if ( + (focused.value || overlayOpened.value) && + dataToEdit.value && + data.findIndex(item => item.key === dataToEdit.value?.key) < 0 + ) { + setEditPanelOpened(false) + setTimeout(() => { + triggerRef.value?.focus() + }) + } + }) + provide(proTagSelectContext, { + triggerRef, + props, + focused, + mergedPrefixCls, + locale: locales.tagSelect, + mergedTagSelectColors: tagColorsContext.mergedTagSelectColors, + maxExceeded: selectedStateContext.maxExceeded, + selectedValue, + ...tagDataContext, + ...tagEditContext, + ...operationContext, + ...overlayStateContext, + ...panelActiveStateContext, + }) + + expose({ focus, blur }) + + const clearInput = () => { + triggerRef.value?.clearInput() + } + + const onFocus = (evt: FocusEvent) => { + setFocused(true) + callEmit(props.onFocus, evt) + } + const onBlur = (evt: FocusEvent) => { + setFocused(false) + clearInput() + + if (props.confirmBeforeSelect !== 'force' && dataToSelect.value) { + handleTagSelectCancel() + } + + accessor.markAsBlurred() + callEmit(props.onBlur, evt) + } + + const overlayClasses = computed(() => { + const { overlayClassName } = props + const prefixCls = mergedPrefixCls.value + return normalizeClass({ + [globalHashId.value]: !!globalHashId.value, + [hashId.value]: !!hashId.value, + [`${prefixCls}-overlay`]: true, + [overlayClassName || '']: !!overlayClassName, + }) + }) + + const renderSelectorItem = (options: { + disabled?: boolean + prefixCls: string + removable?: boolean + value?: VKey + }) => + + const renderTrigger: ControlTriggerSlots['trigger'] = ({ + borderless, + status, + clearable, + clearIcon, + readonly, + disabled, + size, + suffix, + suffixRotate, + focused, + opened, + }) => [ + , + ] + + const renderContent: ControlTriggerSlots['overlay'] = () => { + return [, ] + } + + return () => { + const { suffix, borderless, clearIcon, size } = commonControlProps.value + const controlTriggerProps = { + autofocus: false, + overlayClassName: overlayClasses.value, + overlayContainer: props.overlayContainer, + overlayContainerFallback: `${mergedPrefixCls.value}-overlay-container`, + overlayMatchWidth: props.overlayMatchWidth, + class: [mergedPrefixCls.value, globalHashId.value, hashId.value], + borderless, + value: selectedValue.value, + open: overlayOpened.value, + readonly: props.readonly, + size, + status: mergedStatus.value, + suffix, + clearable: props.clearable, + clearIcon, + disabled: accessor.disabled, + onKeydown: handleKeydown, + onFocus, + onBlur, + 'onUpdate:open': setOverlayOpened, + } + + return ( + + ) + } + }, +}) diff --git a/packages/pro/tag-select/src/composables/useKeyboardEvents.ts b/packages/pro/tag-select/src/composables/useKeyboardEvents.ts new file mode 100644 index 000000000..53287fa71 --- /dev/null +++ b/packages/pro/tag-select/src/composables/useKeyboardEvents.ts @@ -0,0 +1,87 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { OperationsContext } from './useOperations' +import type { OverlayStateContext } from './useOverlayState' +import type { SelectConfirmContext } from './useSelectConfirm' +import type { SelectedStateContext } from './useSelectedState' +import type { TagDataContext } from './useTagData' +import type { ProTagSelectProps } from '../types' + +import { isNil } from 'lodash-es' + +import { type PanelActiveStateContext, creationDataKey } from './usePanelActiveState' + +export function useKeyboardEvents( + props: ProTagSelectProps, + tagDataContext: TagDataContext, + selectedStateContext: SelectedStateContext, + selectConfirmContext: SelectConfirmContext, + overlayStateContext: OverlayStateContext, + panelActiveStateContext: PanelActiveStateContext, + operationsContext: OperationsContext, +): (evt: KeyboardEvent) => void { + const { inputValue, getTagDataByKey } = tagDataContext + const { selectedValue } = selectedStateContext + const { overlayOpened, setOverlayOpened } = overlayStateContext + const { activeValue, changeActiveIndex } = panelActiveStateContext + const { handleTagSelect, handleTagRemove, handleTagCreate } = operationsContext + const { dataToSelect, handleTagSelectCancel } = selectConfirmContext + + return (evt: KeyboardEvent) => { + switch (evt.code) { + case 'ArrowUp': + evt.preventDefault() + changeActiveIndex(-1) + break + case 'ArrowDown': + evt.preventDefault() + changeActiveIndex(1) + break + case 'Enter': { + evt.preventDefault() + const key = activeValue.value + + if (inputValue.value || overlayOpened.value) { + if (key === creationDataKey) { + handleTagCreate() + } else if (!isNil(key)) { + handleTagSelect(getTagDataByKey(key)!) + } + } + + break + } + case 'Backspace': { + const selectedLength = selectedValue.value?.length + + if (dataToSelect.value) { + if (props.confirmBeforeSelect !== 'force') { + handleTagSelectCancel() + } + + break + } + + if (!inputValue.value && selectedLength) { + handleTagRemove(selectedValue.value[selectedLength - 1]) + } + break + } + case 'Escape': { + evt.preventDefault() + setOverlayOpened(false) + + if (dataToSelect.value && props.confirmBeforeSelect !== 'force') { + handleTagSelectCancel() + } + + break + } + } + } +} diff --git a/packages/pro/tag-select/src/composables/useOperations.ts b/packages/pro/tag-select/src/composables/useOperations.ts new file mode 100644 index 000000000..42f3828b6 --- /dev/null +++ b/packages/pro/tag-select/src/composables/useOperations.ts @@ -0,0 +1,94 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { RemoveConfirmContext } from './useRemoveConfirm' +import type { SelectConfirmContext } from './useSelectConfirm' +import type { SelectedStateContext } from './useSelectedState' +import type { MergedTagData, TagDataContext } from './useTagData' +import type { TagSelectData } from '../types' +import type { VKey } from '@idux/cdk/utils' +import type { ComputedRef } from 'vue' + +export interface OperationsContext { + dataToSelect: ComputedRef + dataToRemove: ComputedRef + modalVisible: ComputedRef + handleTagSelect: (data: MergedTagData) => void + handleTagSelectOk: () => void + handleTagSelectCancel: () => void + handleTagCreate: () => Promise + handleTagRemove: (key: VKey) => void + handleTagClear: (evt: MouseEvent) => void + handleTagDataLabelInput: (label: string, data: TagSelectData) => void + handleTagDataColorChange: (color: VKey, data: TagSelectData) => void + handleTagDataRemove: (data: MergedTagData) => void + handleTagDataRemoveOk: () => void + handleTagDataRemoveCancel: () => void + handleTagDataRemoveModalAfterClose: () => void +} + +export function useOperations( + tagDataContext: TagDataContext, + selectConfirmContext: SelectConfirmContext, + removeConfirmContext: RemoveConfirmContext, + selectedStateContext: SelectedStateContext, +): OperationsContext { + const { inputValue, createData, addData, modifyData } = tagDataContext + const { + dataToSelect, + handleTagSelect: _handleTagSelect, + handleTagSelectCancel, + handleTagSelectOk, + } = selectConfirmContext + const { modalVisible, dataToRemove, handleTagDataRemove, handleModalOk, handleModalCancel, handleModalAfterClose } = + removeConfirmContext + const { handleRemove, handleClear } = selectedStateContext + + const handleTagSelect = (data: MergedTagData) => { + _handleTagSelect(data) + } + + const handleTagCreate = async () => { + if (!inputValue.value) { + return + } + + const data = createData(inputValue.value) + + const success = await _handleTagSelect(data) + + if (success) { + addData(data) + } + } + + const handleTagDataLabelInput = (label: string, data: TagSelectData) => { + modifyData({ ...data, label }) + } + + const handleTagDataColorChange = (color: VKey, data: TagSelectData) => { + modifyData({ ...data, color }) + } + + return { + dataToSelect, + dataToRemove, + modalVisible, + handleTagSelect, + handleTagSelectOk, + handleTagSelectCancel, + handleTagCreate, + handleTagRemove: handleRemove, + handleTagClear: handleClear, + handleTagDataLabelInput, + handleTagDataColorChange, + handleTagDataRemove, + handleTagDataRemoveOk: handleModalOk, + handleTagDataRemoveCancel: handleModalCancel, + handleTagDataRemoveModalAfterClose: handleModalAfterClose, + } +} diff --git a/packages/pro/tag-select/src/composables/useOverlayState.ts b/packages/pro/tag-select/src/composables/useOverlayState.ts new file mode 100644 index 000000000..2fc164021 --- /dev/null +++ b/packages/pro/tag-select/src/composables/useOverlayState.ts @@ -0,0 +1,73 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { ProTagSelectProps } from '../types' + +import { type ComputedRef, watch } from 'vue' + +import { useControlledProp, useState } from '@idux/cdk/utils' + +export interface OverlayStateContext { + overlayOpened: ComputedRef + setOverlayOpened: (opened: boolean) => void + editPanelOpened: ComputedRef + setEditPanelOpened: (opened: boolean) => void + selectConfirmPanelOpened: ComputedRef + setSelectConfirmPanelOpened: (opened: boolean) => void + locked: ComputedRef + setLocked: (locked: boolean) => void +} + +export function useOverlayState(props: ProTagSelectProps): OverlayStateContext { + const [overlayOpened, _setOverlayOpened] = useControlledProp(props, 'open', false) + const [editPanelOpened, _setEditPanelOpened] = useState(false) + const [selectConfirmPanelOpened, _setSelectConfirmPanelOpened] = useState(false) + + const [locked, setLocked] = useState(false) + + const setOverlayOpened = (opened: boolean) => { + if (locked.value) { + return + } + + _setOverlayOpened(opened) + } + const setEditPanelOpened = (opened: boolean) => { + if (locked.value) { + return + } + + _setEditPanelOpened(opened) + } + const setSelectConfirmPanelOpened = (opened: boolean) => { + if (locked.value) { + return + } + + _setSelectConfirmPanelOpened(opened) + } + + watch(overlayOpened, (opened, oldOpened) => { + if (!opened && oldOpened) { + setEditPanelOpened(false) + } else if (opened && !oldOpened) { + setEditPanelOpened(false) + setSelectConfirmPanelOpened(false) + } + }) + + return { + overlayOpened, + setOverlayOpened, + editPanelOpened, + setEditPanelOpened, + selectConfirmPanelOpened, + setSelectConfirmPanelOpened, + locked, + setLocked, + } +} diff --git a/packages/pro/tag-select/src/composables/usePanelActiveState.ts b/packages/pro/tag-select/src/composables/usePanelActiveState.ts new file mode 100644 index 000000000..f98cac10c --- /dev/null +++ b/packages/pro/tag-select/src/composables/usePanelActiveState.ts @@ -0,0 +1,126 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { SelectedStateContext } from './useSelectedState' +import type { TagDataContext } from './useTagData' + +import { type ComputedRef, type Ref, computed, onMounted, ref, watch } from 'vue' + +import { isNil } from 'lodash-es' + +import { type VKey, useState } from '@idux/cdk/utils' + +export interface PanelActiveStateContext { + activeValue: ComputedRef + activeIndex: Ref + changeActiveValue: (key: VKey) => void + changeActiveIndex: (offset: 0 | 1 | -1) => void +} + +export const creationDataKey = '__TAG_SELECT_CREATION_DATA__' + +interface PanelOption { + key: VKey + disabled?: boolean +} + +export function usePanelActiveState( + tagDataContext: TagDataContext, + selectedStateContext: SelectedStateContext, +): PanelActiveStateContext { + const [activeValue, setActiveValue] = useState(undefined) + + const { inputValue, inputFullyMatched, filteredData, dataMaxExceeded } = tagDataContext + const { selectedValue } = selectedStateContext + + const mergedOptions = computed(() => { + const options: PanelOption[] = [...filteredData.value] + + if (inputValue.value && !inputFullyMatched.value && !dataMaxExceeded.value) { + options.push({ key: creationDataKey }) + } + + return options + }) + + // cache a map from key to its index to avoid repeated traversal + const keyIndexMap = computed(() => { + const map = new Map() + mergedOptions.value.forEach((data, index) => { + map.set(data.key, index) + }) + return map + }) + const activeIndex = ref(0) + const setActiveIndex = (index: number) => { + activeIndex.value = index + const key = mergedOptions.value[index]?.key + + key !== activeValue.value && setActiveValue(mergedOptions.value[index]?.key) + } + + onMounted(() => { + const options = mergedOptions.value + const currValue = selectedValue.value?.length ? selectedValue.value : [activeValue.value] + const currIndex = options.findIndex(option => currValue.some(value => option.key === value)) + setActiveIndex(currIndex !== -1 ? getEnabledActiveIndex(options, currIndex, 1) : -1) + + watch( + mergedOptions, + options => { + const targetIndex = isNil(activeValue.value) ? -1 : keyIndexMap.value.get(activeValue.value) ?? -1 + setActiveIndex(targetIndex !== -1 ? getEnabledActiveIndex(options, targetIndex, 1) : 0) + }, + { + flush: 'post', + }, + ) + }) + + const changeActiveIndex = (offset: number) => { + const enabledIndex = getEnabledActiveIndex( + mergedOptions.value, + activeIndex.value + offset, + (offset % 2) as 0 | 1 | -1, + ) + + if (enabledIndex !== activeIndex.value) { + setActiveIndex(enabledIndex) + } + } + + const changeActiveValue = (key: VKey) => { + const index = mergedOptions.value.findIndex(option => option.key === key) + + if (index > -1) { + setActiveValue(key) + activeIndex.value = index + } + } + + return { + activeValue, + activeIndex, + changeActiveValue, + changeActiveIndex, + } +} + +function getEnabledActiveIndex(options: PanelOption[], currIndex: number, offset: 0 | 1 | -1) { + const length = options.length + + for (let index = 0; index < length; index++) { + const current = (currIndex + index * offset + length) % length + + const { disabled } = options[current] + if (!disabled) { + return current + } + } + + return -1 +} diff --git a/packages/pro/tag-select/src/composables/useRemoveConfirm.ts b/packages/pro/tag-select/src/composables/useRemoveConfirm.ts new file mode 100644 index 000000000..3a7e8bc0d --- /dev/null +++ b/packages/pro/tag-select/src/composables/useRemoveConfirm.ts @@ -0,0 +1,85 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { OverlayStateContext } from './useOverlayState' +import type { SelectedStateContext } from './useSelectedState' +import type { MergedTagData, TagDataContext } from './useTagData' +import type { ComputedRef } from 'vue' + +import { useState } from '@idux/cdk/utils' + +import { type Deferred, createDeferred } from '../utils' + +export interface RemoveConfirmContext { + dataToRemove: ComputedRef + modalVisible: ComputedRef + handleTagDataRemove: (data: MergedTagData) => Promise + handleModalOk: () => void + handleModalCancel: () => void + handleModalAfterClose: () => void +} + +export function useRemoveConfirm( + tagDataContext: TagDataContext, + selectedStateContext: SelectedStateContext, + overlayStateContext: OverlayStateContext, +): RemoveConfirmContext { + const [dataToRemove, setDataToRemove] = useState(undefined) + const [modalVisible, setModalVisible] = useState(false) + + const { removeData } = tagDataContext + const { handleRemove } = selectedStateContext + const { setLocked } = overlayStateContext + + let removeConfirmDeferred: Deferred + + const handleTagDataRemove = (data: MergedTagData) => { + if (dataToRemove.value) { + return Promise.resolve(false) + } + + setLocked(true) + setDataToRemove(data) + setModalVisible(true) + + removeConfirmDeferred = createDeferred() + + return removeConfirmDeferred.wait() + } + + const handleModalOk = () => { + setModalVisible(false) + + if (dataToRemove.value) { + removeData(dataToRemove.value) + handleRemove(dataToRemove.value.key) + } + setDataToRemove(undefined) + + removeConfirmDeferred.resolve(true) + } + + const handleModalCancel = () => { + setModalVisible(false) + setDataToRemove(undefined) + + removeConfirmDeferred.resolve(false) + } + + const handleModalAfterClose = () => { + setLocked(false) + } + + return { + dataToRemove, + modalVisible, + handleTagDataRemove, + handleModalOk, + handleModalCancel, + handleModalAfterClose, + } +} diff --git a/packages/pro/tag-select/src/composables/useSelectConfirm.ts b/packages/pro/tag-select/src/composables/useSelectConfirm.ts new file mode 100644 index 000000000..8a760ae8f --- /dev/null +++ b/packages/pro/tag-select/src/composables/useSelectConfirm.ts @@ -0,0 +1,108 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { OverlayStateContext } from './useOverlayState' +import type { SelectedStateContext } from './useSelectedState' +import type { MergedTagData, TagDataContext } from './useTagData' +import type { ProTagSelectProps } from '../types' + +import { type ComputedRef, watch } from 'vue' + +import { callEmit, useState } from '@idux/cdk/utils' + +import { type Deferred, createDeferred } from '../utils' + +export interface SelectConfirmContext { + dataToSelect: ComputedRef + handleTagSelect: (data: MergedTagData) => Promise + handleTagSelectOk: () => void + handleTagSelectCancel: () => void +} + +export function useSelectConfirm( + props: ProTagSelectProps, + tagDataContext: TagDataContext, + selectedStateContext: SelectedStateContext, + overlayStateContext: OverlayStateContext, +): SelectConfirmContext { + const { setInputValue } = tagDataContext + const { isSelected, changeSelected } = selectedStateContext + const { overlayOpened, setOverlayOpened, setSelectConfirmPanelOpened, setLocked } = overlayStateContext + + const [dataToSelect, setDataToSelect] = useState(undefined) + let tagSelectDeferred: Deferred + let isSettingDataToSelect = false + + const handleTagSelect = (data: MergedTagData) => { + const key = data.key + if (isSelected(key) || !data) { + return Promise.resolve(false) + } + + callEmit(props.onTagSelect, data) + + if (!props.confirmBeforeSelect) { + changeSelected(key) + return Promise.resolve(true) + } + + setInputValue(undefined) + isSettingDataToSelect = true + setTimeout(() => { + isSettingDataToSelect = false + }) + + setDataToSelect(data) + setSelectConfirmPanelOpened(true) + setOverlayOpened(false) + + if (props.confirmBeforeSelect === 'force') { + setLocked(true) + } + + tagSelectDeferred = createDeferred() + + return tagSelectDeferred.wait() + } + + const handleTagSelectOk = () => { + setLocked(false) + setSelectConfirmPanelOpened(false) + + if (dataToSelect.value) { + changeSelected(dataToSelect.value.key) + } + + callEmit(props.onTagSelectConfirm, dataToSelect.value!) + setDataToSelect(undefined) + + tagSelectDeferred.resolve(true) + } + + const handleTagSelectCancel = () => { + setLocked(false) + setSelectConfirmPanelOpened(false) + + callEmit(props.onTagSelectCancel, dataToSelect.value!) + setDataToSelect(undefined) + + tagSelectDeferred.resolve(false) + } + + watch(overlayOpened, (opened, preOpened) => { + if (!isSettingDataToSelect && opened !== preOpened && props.confirmBeforeSelect !== 'force' && dataToSelect.value) { + handleTagSelectCancel() + } + }) + + return { + dataToSelect, + handleTagSelect, + handleTagSelectOk, + handleTagSelectCancel, + } +} diff --git a/packages/pro/tag-select/src/composables/useSelectedState.ts b/packages/pro/tag-select/src/composables/useSelectedState.ts new file mode 100644 index 000000000..1cb556099 --- /dev/null +++ b/packages/pro/tag-select/src/composables/useSelectedState.ts @@ -0,0 +1,92 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { MergedTagData, TagDataContext } from './useTagData' +import type { ProTagSelectProps } from '../types' +import type { FormAccessor } from '@idux/cdk/forms' + +import { type ComputedRef, computed, toRaw } from 'vue' + +import { type VKey, callEmit } from '@idux/cdk/utils' + +export interface SelectedStateContext { + selectedValue: ComputedRef + selectedData: ComputedRef + maxExceeded: ComputedRef + isSelected: (key: VKey) => boolean + changeSelected: (key: VKey) => void + handleRemove: (key: VKey) => void + handleClear: (evt: MouseEvent) => void +} + +export function useSelectedState( + props: ProTagSelectProps, + accessor: FormAccessor, + tagDataContext: TagDataContext, +): SelectedStateContext { + const { getTagDataByKey } = tagDataContext + + const selectedValue = computed(() => accessor.value) + const maxExceeded = computed(() => (selectedValue.value?.length ?? 0) >= props.tagsLimit) + + const setSelectedValue = (value: VKey[] | undefined) => { + const oldValue = toRaw(accessor.value) + + if (value !== oldValue) { + accessor.setValue(value) + callEmit(props.onChange, value, oldValue) + } + } + + const selectedData = computed( + () => (selectedValue.value?.map(key => getTagDataByKey(key)).filter(Boolean) as MergedTagData[]) ?? [], + ) + + const changeSelected = (key: VKey) => { + const currValue = selectedValue.value ?? [] + const targetIndex = currValue?.findIndex(v => v === key) ?? -1 + const isSelected = targetIndex > -1 + + if (isSelected) { + return + } + + if (currValue.length < props.tagsLimit) { + setSelectedValue([...currValue, key]) + } + } + + const isSelected = (key: VKey) => { + return (selectedValue.value?.findIndex(v => v === key) ?? -1) > -1 + } + + const handleRemove = (key: VKey) => { + const data = getTagDataByKey(key) + + if (!data) { + return + } + + setSelectedValue(selectedValue.value?.filter(item => key !== item)) + callEmit(props.onTagRemove, data) + } + + const handleClear = (evt: MouseEvent) => { + setSelectedValue(undefined) + callEmit(props.onClear, evt) + } + + return { + selectedValue, + selectedData, + maxExceeded, + isSelected, + changeSelected, + handleRemove, + handleClear, + } +} diff --git a/packages/pro/tag-select/src/composables/useTagColors.ts b/packages/pro/tag-select/src/composables/useTagColors.ts new file mode 100644 index 000000000..2bbb54227 --- /dev/null +++ b/packages/pro/tag-select/src/composables/useTagColors.ts @@ -0,0 +1,78 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { ProTagSelectProps, TagSelectColor } from '../types' +import type { VKey } from '@idux/cdk/utils' +import type { ProTagSelectLocale } from '@idux/pro/locales' + +import { type ComputedRef, computed } from 'vue' + +export interface TagColorContext { + mergedTagSelectColors: ComputedRef + getTagSelectColorByKey: (key: VKey) => TagSelectColor | undefined + getRandomColor: () => TagSelectColor +} + +export function useTagColors(props: ProTagSelectProps, locale: ProTagSelectLocale): TagColorContext { + const defaultColors: TagSelectColor[] = [ + { + key: 'grey', + name: locale.colors.grey, + labelColor: 'var(--ix-pro-tag-select-preset-color-grey-label)', + backgroundColor: 'var(--ix-pro-tag-select-preset-color-grey-bg)', + }, + { + key: 'green', + name: locale.colors.green, + labelColor: 'var(--ix-pro-tag-select-preset-color-green-label)', + backgroundColor: 'var(--ix-pro-tag-select-preset-color-green-bg)', + }, + { + key: 'blue', + name: locale.colors.blue, + labelColor: 'var(--ix-pro-tag-select-preset-color-blue-label)', + backgroundColor: 'var(--ix-pro-tag-select-preset-color-blue-bg)', + }, + { + key: 'yellow', + name: locale.colors.yellow, + labelColor: 'var(--ix-pro-tag-select-preset-color-yellow-label)', + backgroundColor: 'var(--ix-pro-tag-select-preset-color-yellow-bg)', + }, + { + key: 'red', + name: locale.colors.red, + labelColor: 'var(--ix-pro-tag-select-preset-color-red-label)', + backgroundColor: 'var(--ix-pro-tag-select-preset-color-red-bg)', + }, + { + key: 'orange', + name: locale.colors.orange, + labelColor: 'var(--ix-pro-tag-select-preset-color-orange-label)', + backgroundColor: 'var(--ix-pro-tag-select-preset-color-orange-bg)', + }, + ] + + const mergedTagSelectColors = computed(() => (props.colors?.length ? props.colors : defaultColors)) + const colorKeyMap = computed(() => new Map(mergedTagSelectColors.value.map(color => [color.key, color]))) + + const getTagSelectColorByKey = (key: VKey) => { + return colorKeyMap.value.get(key) + } + + const getRandomColor = () => { + const randomIndex = Math.floor(Math.random() * mergedTagSelectColors.value.length) + + return mergedTagSelectColors.value[randomIndex] + } + + return { + mergedTagSelectColors, + getTagSelectColorByKey, + getRandomColor, + } +} diff --git a/packages/pro/tag-select/src/composables/useTagData.ts b/packages/pro/tag-select/src/composables/useTagData.ts new file mode 100644 index 000000000..abe40292a --- /dev/null +++ b/packages/pro/tag-select/src/composables/useTagData.ts @@ -0,0 +1,124 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { TagColorContext } from './useTagColors' +import type { ProTagSelectProps, TagSelectColor, TagSelectData } from '../types' + +import { type ComputedRef, computed } from 'vue' + +import { isNil, isObject, toString } from 'lodash-es' + +import { VKey, callEmit, useState } from '@idux/cdk/utils' + +export interface MergedTagData extends TagSelectData { + color: TagSelectColor +} + +export interface TagDataContext { + mergedData: ComputedRef + filteredData: ComputedRef + dataMaxExceeded: ComputedRef + inputValue: ComputedRef + inputFullyMatched: ComputedRef + setInputValue: (input: string | undefined) => void + + getTagDataByKey: (key: VKey) => MergedTagData | undefined + addData: (data: TagSelectData) => void + createData: (label: string) => MergedTagData + removeData: (data: TagSelectData) => void + modifyData: (data: TagSelectData) => void +} + +export function useTagData(props: ProTagSelectProps, tagColorContext: TagColorContext): TagDataContext { + const { getTagSelectColorByKey, getRandomColor } = tagColorContext + const [inputValue, setInputValue] = useState(undefined) + + const mergedData = computed(() => { + return ( + props.dataSource?.map(data => { + if (isObject(data.color)) { + return data as MergedTagData + } + + const color = getTagSelectColorByKey(data.color) ?? getRandomColor() + + return { + ...data, + color, + } + }) ?? [] + ) + }) + const mergedDataKeyMap = computed(() => new Map(mergedData.value.map(data => [data.key, data]))) + + const dataMaxExceeded = computed(() => mergedData.value.length > props.tagDataLimit) + + const filteredData = computed(() => { + if (!inputValue.value) { + return mergedData.value + } + + return mergedData.value.filter(data => { + const label = data.label + return matchRule(toString(label), inputValue.value!) + }) + }) + + const inputFullyMatched = computed( + () => filteredData.value.length === 1 && filteredData.value[0].label === inputValue.value, + ) + + const getTagDataByKey = (key: VKey) => { + return mergedDataKeyMap.value.get(key) + } + + const addData = (data: TagSelectData) => { + setInputValue(undefined) + callEmit(props.onTagDataAdd, data) + } + const createData = (label: string) => { + const randomColor = getRandomColor() + const data = { key: label, label, color: randomColor } + const modifiedData = props.createdTagDataModifier ? props.createdTagDataModifier(data) : data + + return { + ...modifiedData, + color: isObject(modifiedData.color) + ? modifiedData.color + : getTagSelectColorByKey(modifiedData.color) ?? randomColor, + } + } + const removeData = (data: TagSelectData) => { + callEmit(props.onTagDataRemove, data) + } + const modifyData = (data: TagSelectData) => { + if (!getTagDataByKey(data.key)) { + return + } + + callEmit(props.onTagDataChange, data) + } + + return { + mergedData, + filteredData, + dataMaxExceeded, + inputValue, + inputFullyMatched, + setInputValue, + + getTagDataByKey, + addData, + createData, + removeData, + modifyData, + } +} + +function matchRule(srcString: string | number | undefined, targetString: string) { + return !isNil(srcString) && String(srcString).toLowerCase().includes(targetString.toLowerCase()) +} diff --git a/packages/pro/tag-select/src/composables/useTagEdit.ts b/packages/pro/tag-select/src/composables/useTagEdit.ts new file mode 100644 index 000000000..fcd607b57 --- /dev/null +++ b/packages/pro/tag-select/src/composables/useTagEdit.ts @@ -0,0 +1,34 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { OverlayStateContext } from './useOverlayState' +import type { MergedTagData } from './useTagData' + +import { type ComputedRef, watch } from 'vue' + +import { useState } from '@idux/cdk/utils' + +export interface TagEditContext { + dataToEdit: ComputedRef + setDataToEdit: (data: MergedTagData | undefined) => void +} + +export function useTagEdit(overlayStateContext: OverlayStateContext): TagEditContext { + const { editPanelOpened } = overlayStateContext + const [dataToEdit, setDataToEdit] = useState(undefined) + + watch(editPanelOpened, opened => { + if (!opened) { + setDataToEdit(undefined) + } + }) + + return { + dataToEdit, + setDataToEdit, + } +} diff --git a/packages/pro/tag-select/src/content/SelectedTag.tsx b/packages/pro/tag-select/src/content/SelectedTag.tsx new file mode 100644 index 000000000..8646d9c76 --- /dev/null +++ b/packages/pro/tag-select/src/content/SelectedTag.tsx @@ -0,0 +1,122 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { VKey } from '@idux/cdk/utils' + +import { type PropType, computed, defineComponent, inject } from 'vue' + +import { isNil } from 'lodash-es' + +import { ɵHeader } from '@idux/components/_private/header' +import { IxButton } from '@idux/components/button' +import { IxControlTriggerOverlay } from '@idux/components/control-trigger' +import { IxIcon } from '@idux/components/icon' +import { IxTag } from '@idux/components/tag' + +import { proTagSelectContext } from '../token' + +export default defineComponent({ + props: { + disabled: Boolean, + prefixCls: String, + removable: Boolean, + value: [String, Number, Symbol] as PropType, + }, + setup(props, { slots }) { + const { + props: proTagSelectProps, + locale, + mergedPrefixCls, + dataToSelect, + selectConfirmPanelOpened, + getTagDataByKey, + handleTagRemove, + handleTagSelectOk, + handleTagSelectCancel, + } = inject(proTagSelectContext)! + const tagData = computed(() => { + if (isNil(props.value)) { + return + } + + if (props.value === dataToSelect.value?.key) { + return dataToSelect.value + } + + return getTagDataByKey(props.value) + }) + const removable = computed(() => props.removable && dataToSelect.value?.key !== props.value) + + const tagStyle = computed(() => { + if (!tagData.value) { + return + } + + const { + color: { labelColor, backgroundColor, borderColor }, + } = tagData.value + return { + '--ix-tag-color': labelColor, + '--ix-tag-background-color': backgroundColor, + '--ix-tag-border-color': borderColor ?? backgroundColor, + } + }) + + const handleRemoveIconClick = (evt: Event) => { + if (!props.value) { + return + } + + evt.preventDefault() + evt.stopImmediatePropagation() + + handleTagRemove(props.value) + } + + const renderConfirmOverlay = () => { + const prefixCls = `${mergedPrefixCls.value}-select-confirm` + return ( +
+ <ɵHeader class={`${prefixCls}-header`} header={proTagSelectProps.selectConfirmHeader} size="sm" /> +
{slots.selectConfirmContent?.(tagData.value)}
+
+ + {locale.ok} + + + {locale.cancel} + +
+
+ ) + } + + return () => { + const labelSlot = slots.selectedLabel ?? slots.tagLabel + + return ( + + + {labelSlot?.(tagData.value) ?? tagData.value?.label} + {removable.value && ( + + )} + + + ) + } + }, +}) diff --git a/packages/pro/tag-select/src/content/TagDataEditPanel.tsx b/packages/pro/tag-select/src/content/TagDataEditPanel.tsx new file mode 100644 index 000000000..59aa8a78a --- /dev/null +++ b/packages/pro/tag-select/src/content/TagDataEditPanel.tsx @@ -0,0 +1,121 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { MergedTagData } from '../composables/useTagData' +import type { TagSelectColor } from '../types' + +import { type PropType, defineComponent, inject, onMounted, ref, watch } from 'vue' + +import { IxIcon } from '@idux/components/icon' +import { type InputInstance, IxInput } from '@idux/components/input' +import { useThemeToken } from '@idux/pro/theme' + +import { proTagSelectContext } from '../token' + +export default defineComponent({ + props: { + data: { type: Object as PropType, required: true }, + }, + setup(props) { + const { + locale, + mergedPrefixCls, + mergedTagSelectColors, + editPanelOpened, + dataToEdit, + triggerRef, + focused, + handleTagDataLabelInput, + handleTagDataRemove, + handleTagDataColorChange, + } = inject(proTagSelectContext)! + + const { globalHashId, hashId } = useThemeToken('proTagSelect') + + const inputRef = ref() + + const handleInputChange = (input: string) => { + handleTagDataLabelInput(input, props.data) + } + + const handleDeleteClick = () => { + handleTagDataRemove(props.data) + } + + const handlePanelMousedown = (evt: MouseEvent) => { + if (!(evt.target instanceof HTMLInputElement)) { + evt.preventDefault() + } + } + + const renderColorItem = (prefixCls: string, color: TagSelectColor) => { + const isSelected = color.key === props.data.color.key + const colorItemPrefixCls = `${prefixCls}-item` + const classes = { + [colorItemPrefixCls]: true, + [`${colorItemPrefixCls}-selected`]: isSelected, + } + const colorIndicatorStyle = { + background: color.backgroundColor, + } + const handleClick = () => { + handleTagDataColorChange(color.key, props.data) + } + + return ( +
+
+ {color.name} + {isSelected && } +
+ ) + } + + onMounted(() => { + watch( + [editPanelOpened, dataToEdit], + ([opened, editData]) => { + if (opened && editData?.key === props.data.key) { + setTimeout(() => { + inputRef.value?.focus() + }) + } else if (!opened && focused.value) { + triggerRef.value?.focus() + } + }, + { + immediate: true, + }, + ) + }) + + return () => { + const prefixCls = `${mergedPrefixCls.value}-edit-panel` + + return ( +
+
+ +
+
+ + {locale.remove} +
+
+ {mergedTagSelectColors.value.map(color => renderColorItem(`${prefixCls}-colors`, color))} +
+
+ ) + } + }, +}) diff --git a/packages/pro/tag-select/src/content/TagDataRemoveConfirmModal.tsx b/packages/pro/tag-select/src/content/TagDataRemoveConfirmModal.tsx new file mode 100644 index 000000000..f8bfe29e7 --- /dev/null +++ b/packages/pro/tag-select/src/content/TagDataRemoveConfirmModal.tsx @@ -0,0 +1,55 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import { computed, defineComponent, inject } from 'vue' + +import { IxModal, type ModalProps } from '@idux/components/modal' + +import { proTagSelectContext } from '../token' + +export default defineComponent({ + setup(_, { slots }) { + const { + locale, + props: proTagSelectProps, + mergedPrefixCls, + modalVisible, + dataToRemove, + handleTagDataRemoveOk, + handleTagDataRemoveCancel, + handleTagDataRemoveModalAfterClose, + } = inject(proTagSelectContext)! + + const modalProps = computed(() => { + return { + header: proTagSelectProps.removeConfirmHeader ?? locale.removeTag, + title: proTagSelectProps.removeConfirmTitle, + closable: true, + visible: modalVisible.value, + onAfterClose: handleTagDataRemoveModalAfterClose, + onClose: handleTagDataRemoveCancel, + onOk: handleTagDataRemoveOk, + onCancel: handleTagDataRemoveCancel, + } + }) + + return () => { + const prefixCls = `${mergedPrefixCls.value}-remove-confirm-modal` + + const modalSlots = { + title: slots.removeConfirmTitle + ? () => (dataToRemove.value ? slots.removeConfirmTitle!(dataToRemove.value) : undefined) + : undefined, + default: slots.removeConfirmContent + ? () => (dataToRemove.value ? slots.removeConfirmContent!(dataToRemove.value) : undefined) + : undefined, + } + + return + } + }, +}) diff --git a/packages/pro/tag-select/src/content/TagSelectCreationOption.tsx b/packages/pro/tag-select/src/content/TagSelectCreationOption.tsx new file mode 100644 index 000000000..a9f1f9075 --- /dev/null +++ b/packages/pro/tag-select/src/content/TagSelectCreationOption.tsx @@ -0,0 +1,40 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import { computed, defineComponent, inject } from 'vue' + +import { creationDataKey } from '../composables/usePanelActiveState' +import { proTagSelectContext } from '../token' + +export default defineComponent({ + setup() { + const { locale, activeValue, mergedPrefixCls, inputValue, changeActiveValue, handleTagCreate } = + inject(proTagSelectContext)! + + const classes = computed(() => { + const prefixCls = `${mergedPrefixCls.value}-option` + + return { + [prefixCls]: true, + [`${prefixCls}-active`]: creationDataKey === activeValue.value, + } + }) + + const handleMouseEnter = () => { + changeActiveValue(creationDataKey) + } + + return () => { + return ( +
+ {locale.createTag} + {inputValue.value} +
+ ) + } + }, +}) diff --git a/packages/pro/tag-select/src/content/TagSelectOption.tsx b/packages/pro/tag-select/src/content/TagSelectOption.tsx new file mode 100644 index 000000000..5d085159b --- /dev/null +++ b/packages/pro/tag-select/src/content/TagSelectOption.tsx @@ -0,0 +1,112 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { MergedTagData } from '../composables/useTagData' + +import { type PropType, computed, defineComponent, inject } from 'vue' + +import { IxControlTriggerOverlay } from '@idux/components/control-trigger' +import { IxIcon } from '@idux/components/icon' +import { IxTag } from '@idux/components/tag' + +import TagDataEditPanel from './TagDataEditPanel' +import { proTagSelectContext } from '../token' + +export default defineComponent({ + props: { + data: { type: Object as PropType, required: true }, + }, + setup(props, { slots }) { + const { + props: tagSelectProps, + mergedPrefixCls, + editPanelOpened, + activeValue, + dataToEdit, + setDataToEdit, + setEditPanelOpened, + handleTagSelect, + changeActiveValue, + } = inject(proTagSelectContext)! + + const classes = computed(() => { + const prefixCls = `${mergedPrefixCls.value}-option` + + return { + [prefixCls]: true, + [`${prefixCls}-active`]: props.data.key === activeValue.value, + } + }) + const tagStyle = computed(() => { + const { + color: { labelColor, backgroundColor, borderColor }, + } = props.data + return { + '--ix-tag-color': labelColor, + '--ix-tag-background-color': backgroundColor, + '--ix-tag-border-color': borderColor ?? backgroundColor, + } + }) + + const handleClick = () => { + handleTagSelect(props.data) + } + const handleEditTriggerClick = (evt: Event) => { + evt.preventDefault() + evt.stopImmediatePropagation() + + if (dataToEdit.value?.key !== props.data.key) { + setEditPanelOpened(true) + setDataToEdit(props.data) + } else { + setEditPanelOpened(!editPanelOpened.value) + } + } + const handleEditTriggerMousedown = (evt: MouseEvent) => { + evt.preventDefault() + evt.stopImmediatePropagation() + } + const handleMouseEnter = () => { + changeActiveValue(props.data.key) + } + + const renderOverlayContent = () => { + return + } + + return () => { + const prefixCls = mergedPrefixCls.value + const { label } = props.data + + return ( +
+ {slots.optionLabel?.(props.data) ?? ( + + {slots.tagLabel?.(props.data) ?? label} + + )} + {tagSelectProps.dataEditable && ( + +
+ +
+
+ )} +
+ ) + } + }, +}) diff --git a/packages/pro/tag-select/src/content/TagSelectPanel.tsx b/packages/pro/tag-select/src/content/TagSelectPanel.tsx new file mode 100644 index 000000000..c2fe0c282 --- /dev/null +++ b/packages/pro/tag-select/src/content/TagSelectPanel.tsx @@ -0,0 +1,67 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import { computed, defineComponent, inject } from 'vue' + +import { toString } from 'lodash-es' + +import { IxAlert } from '@idux/components/alert' +import { useGlobalConfig } from '@idux/components/config' + +import TagSelectCreationOption from './TagSelectCreationOption' +import TagSelectOption from './TagSelectOption' +import { proTagSelectContext } from '../token' + +export default defineComponent({ + setup(_, { slots }) { + const common = useGlobalConfig('common') + const { + locale, + mergedPrefixCls, + filteredData, + dataMaxExceeded, + selectedValue, + inputValue, + inputFullyMatched, + setEditPanelOpened, + } = inject(proTagSelectContext)! + + const showEmpty = computed(() => !filteredData.value.length && (!inputValue.value || dataMaxExceeded.value)) + const showCreatOption = computed(() => inputValue.value && !inputFullyMatched.value && !dataMaxExceeded.value) + + const handleMousedown = (evt: MouseEvent) => { + setEditPanelOpened(false) + + if (!(evt.target instanceof HTMLInputElement)) { + evt.preventDefault() + } + } + + return () => { + const prefixCls = `${mergedPrefixCls.value}-panel` + + return ( +
+ {dataMaxExceeded.value && ( +
+ {slots.maxExceededAlert?.() ?? ( + + {locale.maxExceededAlert.replace('${0}', toString(selectedValue.value?.length ?? 0))} + + )} +
+ )} + {filteredData.value.map(data => ( + + ))} + {showEmpty.value &&
{locale.empty}
} + {showCreatOption.value && } +
+ ) + } + }, +}) diff --git a/packages/pro/tag-select/src/token.ts b/packages/pro/tag-select/src/token.ts new file mode 100644 index 000000000..c4956696f --- /dev/null +++ b/packages/pro/tag-select/src/token.ts @@ -0,0 +1,38 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { OperationsContext } from './composables/useOperations' +import type { OverlayStateContext } from './composables/useOverlayState' +import type { PanelActiveStateContext } from './composables/usePanelActiveState' +import type { MergedTagData, TagDataContext } from './composables/useTagData' +import type { TagEditContext } from './composables/useTagEdit' +import type { ProTagSelectProps, TagSelectColor } from './types' +import type { VKey } from '@idux/cdk/utils' +import type { SelectorInstance } from '@idux/components/selector' +import type { ProTagSelectLocale } from '@idux/pro/locales' +import type { ComputedRef, InjectionKey, Ref } from 'vue' + +export interface ProTagSelectContext + extends TagDataContext, + TagEditContext, + OperationsContext, + OverlayStateContext, + PanelActiveStateContext { + triggerRef: Ref + props: ProTagSelectProps + focused: ComputedRef + mergedTagSelectColors: ComputedRef + selectedValue: ComputedRef + mergedData: ComputedRef + maxExceeded: ComputedRef + inputValue: ComputedRef + inputFullyMatched: ComputedRef + mergedPrefixCls: ComputedRef + locale: ProTagSelectLocale +} + +export const proTagSelectContext: InjectionKey = Symbol('proTagSelectContext') diff --git a/packages/pro/tag-select/src/types.ts b/packages/pro/tag-select/src/types.ts new file mode 100644 index 000000000..65d1d3ff3 --- /dev/null +++ b/packages/pro/tag-select/src/types.ts @@ -0,0 +1,108 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { AbstractControl, ValidateStatus } from '@idux/cdk/forms' +import type { ExtractInnerPropTypes, ExtractPublicPropTypes, MaybeArray, VKey } from '@idux/cdk/utils' +import type { FormSize } from '@idux/components/form' +import type { HeaderProps } from '@idux/components/header' +import type { OverlayContainerType } from '@idux/components/utils' +import type { DefineComponent, HTMLAttributes, PropType, Slot, VNode, VNodeChild } from 'vue' + +export interface TagSelectColor { + key: VKey + name: string + labelColor: string + backgroundColor: string + borderColor?: string +} + +export interface TagSelectData { + key: VKey + label: string + color: VKey | TagSelectColor + disabled?: boolean + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [key: string]: any +} + +export const proTagSelectProps = { + control: { + type: [String, Number, Object, Array] as PropType, + default: undefined, + }, + value: { type: Array as PropType, default: undefined }, + open: { type: Boolean, default: undefined }, + colors: Array as PropType, + clearable: { type: Boolean, default: false }, + clearIcon: { type: String, default: undefined }, + dataSource: Array as PropType, + dataEditable: { type: Boolean, default: true }, + disabled: { type: Boolean, default: false }, + confirmBeforeSelect: { type: [Boolean, String] as PropType, default: false }, + maxTags: { type: [Number, String] as PropType, default: Number.MAX_SAFE_INTEGER }, + tagsLimit: { type: Number, default: Number.MAX_SAFE_INTEGER }, + tagDataLimit: { type: Number, default: Number.MAX_SAFE_INTEGER }, + overlayClassName: { type: String, default: undefined }, + overlayContainer: { + type: [String, HTMLElement, Function] as PropType, + default: undefined, + }, + overlayMatchWidth: { type: [Boolean, String] as PropType, default: 'minWidth' }, + placeholder: { type: String, default: undefined }, + readonly: { type: Boolean, default: false }, + createdTagDataModifier: Function as PropType<(data: TagSelectData) => TagSelectData>, + removeConfirmHeader: [String, Object] as PropType, + removeConfirmTitle: [String, Object, Function] as PropType VNodeChild)>, + selectConfirmHeader: [String, Object] as PropType, + size: { type: String as PropType, default: undefined }, + status: String as PropType, + suffix: { type: String, default: undefined }, + + 'onUpdate:value': [Function, Array] as PropType void>>, + 'onUpdate:open': [Function, Array] as PropType void>>, + onClear: [Function, Array] as PropType void>>, + onChange: [Function, Array] as PropType< + MaybeArray<(value: VKey[] | undefined, oldValue: VKey[] | undefined) => void> + >, + onFocus: [Function, Array] as PropType void>>, + onBlur: [Function, Array] as PropType void>>, + onTagDataChange: [Function, Array] as PropType void>>, + onTagDataRemove: [Function, Array] as PropType void>>, + onTagDataAdd: [Function, Array] as PropType void>>, + onTagSelect: [Function, Array] as PropType void>>, + onTagSelectConfirm: [Function, Array] as PropType void>>, + onTagSelectCancel: [Function, Array] as PropType void>>, + onTagRemove: [Function, Array] as PropType void>>, +} as const + +export interface ProTagSelectSlots { + clearIcon: Slot + suffix: Slot + selectedLabel: Slot + overflowedLabel: Slot + tagLabel: Slot + optionLabel: Slot + maxExceededAlert: Slot + placeholder: Slot + + selectConfirmContent: Slot + removeConfirmTitle: Slot + removeConfirmContent: Slot +} + +export interface ProTagSelectBindings { + blur: () => void + focus: (options?: FocusOptions) => void +} + +export type ProTagSelectProps = ExtractInnerPropTypes +export type ProTagSelectPublicProps = ExtractPublicPropTypes +export type ProTagSelectComponent = DefineComponent< + Omit & ProTagSelectPublicProps +> +export type ProTagSelectInstance = InstanceType> diff --git a/packages/pro/tag-select/src/utils/index.ts b/packages/pro/tag-select/src/utils/index.ts new file mode 100644 index 000000000..1662da8b3 --- /dev/null +++ b/packages/pro/tag-select/src/utils/index.ts @@ -0,0 +1,25 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export interface Deferred { + wait: () => Promise + resolve: (value: V) => void +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function createDeferred(): Deferred { + let resolve: (value: V) => void + const promise = new Promise(_resolve => { + resolve = _resolve + }) + + return { + wait: () => promise, + resolve: value => resolve(value), + } +} diff --git a/packages/pro/tag-select/style/index.less b/packages/pro/tag-select/style/index.less new file mode 100644 index 000000000..e2d721e4a --- /dev/null +++ b/packages/pro/tag-select/style/index.less @@ -0,0 +1,165 @@ +@import '../../style/variable/index.less'; +@import '../../../components/style/variable/index.less'; +@import '../../style/mixins/reset.less'; + +.@{pro-tag-select-prefix} { + .reset-component(); + + &-panel { + padding: var(--ix-padding-size-xs) 0; + max-height: var(--ix-pro-tag-select-panel-max-height); + overflow-y: auto; + + &-alert { + padding: 0 var(--ix-padding-size-sm); + } + &-empty { + height: var(--ix-height-sm); + padding: 0 var(--ix-padding-size-md); + color: var(--ix-color-text-placeholder); + } + } + &-option { + height: var(--ix-height-sm); + padding: 0 var(--ix-padding-size-sm); + display: flex; + align-items: center; + cursor: pointer; + + &-active { + background-color: var(--ix-color-container-bg-hover); + } + + &-tag { + height: var(--ix-pro-tag-select-tag-height); + line-height: calc(var(--ix-pro-tag-select-tag-height) - var(--ix-control-line-width) * 2); + } + + &-edit-trigger { + margin-left: auto; + height: 100%; + width: var(--ix-font-size-icon); + display: none; + align-items: center; + justify-content: center; + font-size: var(--ix-font-size-icon); + color: var(--ix-color-icon); + + &:hover { + color: var(--ix-color-icon-hover); + } + } + + &:hover { + .@{pro-tag-select-prefix}-option-edit-trigger { + display: flex; + } + } + } + &-creation { + color: var(--ix-color-text); + &-input { + font-weight: var(--ix-font-weight-lg); + margin-left: var(--ix-margin-size-xs); + } + } + + &-edit-panel { + padding: var(--ix-padding-size-sm) 0; + min-width: var(--ix-pro-tag-select-edit-panel-min-width); + font-size: var(--ix-font-size-sm); + color: var(--ix-color-text); + &-input { + padding: 0 var(--ix-padding-size-sm); + } + &-delete { + margin-top: var(--ix-margin-size-sm); + padding: 0 var(--ix-padding-size-md); + height: var(--ix-height-sm); + display: flex; + align-items: center; + cursor: pointer; + &-icon { + font-size: var(--ix-font-size-icon); + color: var(--ix-color-icon); + margin-right: var(--ix-margin-size-sm); + } + } + &-colors { + display: flex; + flex-direction: column; + align-items: center; + &::before { + content: ''; + height: var(--ix-line-width); + width: calc(100% - var(--ix-padding-size-sm) * 2); + } + &-item { + height: var(--ix-height-sm); + width: 100%; + padding: 0 var(--ix-padding-size-md); + display: flex; + align-items: center; + &:hover, + &-selected:hover { + background-color: var(--ix-color-container-bg-hover); + } + &-selected { + background-color: var(--ix-color-container-bg-active); + } + + &-indicator { + height: var(--ix-pro-tag-select-color-indicator-size); + width: var(--ix-pro-tag-select-color-indicator-size); + border-radius: 9999px; + margin-right: var(--ix-margin-size-sm); + } + + &-check { + margin-left: auto; + font-size: var(--ix-font-size-icon); + color: var(--ix-color-icon); + } + } + } + } + &-select-confirm { + &-header { + padding: 0 var(--ix-padding-size-lg); + } + &-content { + padding: 0 var(--ix-padding-size-lg) var(--ix-padding-size-sm) var(--ix-padding-size-lg); + } + &-footer { + display: flex; + align-items: center; + justify-content: flex-end; + padding: var(--ix-padding-size-sm) var(--ix-padding-size-lg); + + .@{button-prefix} + .@{button-prefix} { + margin-left: 8px; + } + } + } + + @select-margin-half: calc((var(--ix-margin-size-xs) / 2)); + @select-padding-vertical: ~'max(calc(var(--ix-margin-size-xs) - var(--ix-control-line-width) - @{select-margin-half}), 0px)'; + @select-item-height: var(--ix-pro-tag-select-tag-height); + @select-item-line-height: calc(@select-item-height - var(--ix-control-line-width) * 2); + + &-selected-tag { + height: @select-item-height; + line-height: @select-item-line-height; + margin: @select-margin-half 0; + margin-inline-end: var(--ix-margin-size-xs); + + &-remove-icon { + margin-left: var(--ix-margin-size-xs); + cursor: pointer; + color: var(--ix-color-icon); + &:hover { + color: var(--ix-color-icon-hover); + } + } + } +} diff --git a/packages/pro/tag-select/style/index.ts b/packages/pro/tag-select/style/index.ts new file mode 100644 index 000000000..231d3c7f2 --- /dev/null +++ b/packages/pro/tag-select/style/index.ts @@ -0,0 +1,11 @@ +// style dependencies +import '@idux/components/alert/style' +import '@idux/components/button/style' +import '@idux/components/selector/style' +import '@idux/components/control-trigger/style' +import '@idux/components/header/style' +import '@idux/components/modal/style' +import '@idux/components/input/style' +import '@idux/components/icon/style' + +import './index.less' diff --git a/packages/pro/tag-select/theme/dark.css b/packages/pro/tag-select/theme/dark.css new file mode 100644 index 000000000..9cbcaf054 --- /dev/null +++ b/packages/pro/tag-select/theme/dark.css @@ -0,0 +1,19 @@ +/* ------ pro-tag-select css variables ------ */ +:root { + --ix-pro-tag-select-preset-color-grey-label: #525966; + --ix-pro-tag-select-preset-color-grey-bg: rgb(24, 27, 32); + --ix-pro-tag-select-preset-color-green-label: #56bd37; + --ix-pro-tag-select-preset-color-green-bg: rgb(25, 47, 23); + --ix-pro-tag-select-preset-color-blue-label: #4083e8; + --ix-pro-tag-select-preset-color-blue-bg: rgb(21, 36, 58); + --ix-pro-tag-select-preset-color-yellow-label: #e8d43e; + --ix-pro-tag-select-preset-color-yellow-bg: rgb(54, 52, 24); + --ix-pro-tag-select-preset-color-red-label: #e8514c; + --ix-pro-tag-select-preset-color-red-bg: rgb(54, 26, 27); + --ix-pro-tag-select-preset-color-orange-label: #e88641; + --ix-pro-tag-select-preset-color-orange-bg: rgb(54, 36, 25); + --ix-pro-tag-select-color-indicator-size: 12px; + --ix-pro-tag-select-panel-max-height: 256px; + --ix-pro-tag-select-edit-panel-min-width: 225px; + --ix-pro-tag-select-tag-height: 20px; +} diff --git a/packages/pro/tag-select/theme/default.css b/packages/pro/tag-select/theme/default.css new file mode 100644 index 000000000..9ff2f4929 --- /dev/null +++ b/packages/pro/tag-select/theme/default.css @@ -0,0 +1,19 @@ +/* ------ pro-tag-select css variables ------ */ +:root { + --ix-pro-tag-select-preset-color-grey-label: #a1a7b3; + --ix-pro-tag-select-preset-color-grey-bg: rgb(246, 246, 247); + --ix-pro-tag-select-preset-color-green-label: #39c317; + --ix-pro-tag-select-preset-color-green-bg: rgb(235, 249, 232); + --ix-pro-tag-select-preset-color-blue-label: #1c6eff; + --ix-pro-tag-select-preset-color-blue-bg: rgb(232, 241, 255); + --ix-pro-tag-select-preset-color-yellow-label: #f8d81a; + --ix-pro-tag-select-preset-color-yellow-bg: rgb(254, 251, 232); + --ix-pro-tag-select-preset-color-red-label: #f52727; + --ix-pro-tag-select-preset-color-red-bg: rgb(254, 233, 233); + --ix-pro-tag-select-preset-color-orange-label: #fa721b; + --ix-pro-tag-select-preset-color-orange-bg: rgb(255, 241, 232); + --ix-pro-tag-select-color-indicator-size: 12px; + --ix-pro-tag-select-panel-max-height: 256px; + --ix-pro-tag-select-edit-panel-min-width: 225px; + --ix-pro-tag-select-tag-height: 20px; +} diff --git a/packages/pro/tag-select/theme/default.ts b/packages/pro/tag-select/theme/default.ts new file mode 100644 index 000000000..de644a436 --- /dev/null +++ b/packages/pro/tag-select/theme/default.ts @@ -0,0 +1,43 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { ProCertainThemeTokens } from '@idux/pro/theme' + +import { type GlobalThemeTokens, type ThemeTokenAlgorithms, getAlphaColor } from '@idux/components/theme' +export function getDefaultThemeTokens( + tokens: GlobalThemeTokens, + algrithms: ThemeTokenAlgorithms, +): ProCertainThemeTokens<'proTagSelect'> { + const { colorContainerBg, tagCompColorAlpha } = tokens + const { getBaseColors, getGreyColors } = algrithms + + const greyColor = getGreyColors().base + const greenColor = getBaseColors().green + const blueColor = getBaseColors().blue + const yelloColor = getBaseColors().yellow + const redColor = getBaseColors().red + const orangeColor = getBaseColors().orange + + return { + presetColorGreyLabel: greyColor, + presetColorGreyBg: getAlphaColor(greyColor, tagCompColorAlpha, colorContainerBg), + presetColorGreenLabel: greenColor, + presetColorGreenBg: getAlphaColor(greenColor, tagCompColorAlpha, colorContainerBg), + presetColorBlueLabel: blueColor, + presetColorBlueBg: getAlphaColor(blueColor, tagCompColorAlpha, colorContainerBg), + presetColorYellowLabel: yelloColor, + presetColorYellowBg: getAlphaColor(yelloColor, tagCompColorAlpha, colorContainerBg), + presetColorRedLabel: redColor, + presetColorRedBg: getAlphaColor(redColor, tagCompColorAlpha, colorContainerBg), + presetColorOrangeLabel: orangeColor, + presetColorOrangeBg: getAlphaColor(orangeColor, tagCompColorAlpha, colorContainerBg), + colorIndicatorSize: 12, + panelMaxHeight: 256, + editPanelMinWidth: 225, + tagHeight: 20, + } +} diff --git a/packages/pro/tag-select/theme/index.ts b/packages/pro/tag-select/theme/index.ts new file mode 100644 index 000000000..4311001ab --- /dev/null +++ b/packages/pro/tag-select/theme/index.ts @@ -0,0 +1,18 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { ProTokenGetter } from '@idux/pro/theme' + +import { getDefaultThemeTokens } from './default' + +export const getThemeTokens: ProTokenGetter<'proTagSelect'> = (tokens, presetTheme, algorithms) => { + return presetTheme === 'default' + ? getDefaultThemeTokens(tokens, algorithms) + : getDefaultThemeTokens(tokens, algorithms) +} + +export type { ProTagSelectThemeTokens } from './tokens' diff --git a/packages/pro/tag-select/theme/tokens.ts b/packages/pro/tag-select/theme/tokens.ts new file mode 100644 index 000000000..a317252d5 --- /dev/null +++ b/packages/pro/tag-select/theme/tokens.ts @@ -0,0 +1,88 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +export interface ProTagSelectThemeTokens { + /** + * @desc 预设灰色文字色 + */ + presetColorGreyLabel: string + + /** + * @desc 预设灰色背景色 + */ + presetColorGreyBg: string + + /** + * @desc 预设绿色文字色 + */ + presetColorGreenLabel: string + + /** + * @desc 预设绿色背景色 + */ + presetColorGreenBg: string + + /** + * @desc 预设蓝色文字色 + */ + presetColorBlueLabel: string + + /** + * @desc 预设蓝色背景色 + */ + presetColorBlueBg: string + + /** + * @desc 预设黄色文字色 + */ + presetColorYellowLabel: string + + /** + * @desc 预设黄色背景色 + */ + presetColorYellowBg: string + + /** + * @desc 预设红色文字色 + */ + presetColorRedLabel: string + + /** + * @desc 预设红色背景色 + */ + presetColorRedBg: string + + /** + * @desc 预设橙色文字色 + */ + presetColorOrangeLabel: string + + /** + * @desc 预设橙色背景色 + */ + presetColorOrangeBg: string + + /** + * @desc 颜色指示圆圈的尺寸 + */ + colorIndicatorSize: number + + /** + * @desc 编辑面板的最小宽度 + */ + editPanelMinWidth: number + + /** + * @desc 数据面板的最大高度 + */ + panelMaxHeight: number + + /** + * @desc 标签的高度 + */ + tagHeight: number +} diff --git a/packages/pro/theme/src/types.ts b/packages/pro/theme/src/types.ts index 31ff33171..b34d06efc 100644 --- a/packages/pro/theme/src/types.ts +++ b/packages/pro/theme/src/types.ts @@ -8,11 +8,13 @@ import type { CertainThemeTokens, ThemeKeys, TokenGetter, TokenTransforms } from '@idux/components/theme' import type { ProLayoutThemeTokens } from '@idux/pro/layout' import type { ProSearchThemeTokens } from '@idux/pro/search' +import type { ProTagSelectThemeTokens } from '@idux/pro/tag-select' import type { ProTextareaThemeTokens } from '@idux/pro/textarea' import type { ProTransferThemeTokens } from '@idux/pro/transfer' import type { ProTreeThemeTokens } from '@idux/pro/tree' export interface ProComponentThemeTokens { + proTagSelect: ProTagSelectThemeTokens proTextarea: ProTextareaThemeTokens proTransfer: ProTransferThemeTokens proTree: ProTreeThemeTokens diff --git a/packages/pro/types.d.ts b/packages/pro/types.d.ts index e59d1fd25..b80d648f1 100644 --- a/packages/pro/types.d.ts +++ b/packages/pro/types.d.ts @@ -10,6 +10,7 @@ import type { ProFormComponent } from '@idux/pro/form' import type { ProLayoutComponent } from '@idux/pro/layout' import type { ProSearchComponent, ProSearchShortcutComponent } from '@idux/pro/search' import type { ProTableComponent, ProTableLayoutToolComponent } from '@idux/pro/table' +import type { ProTagSelectComponent } from '@idux/pro/tag-select' import type { ProTextareaComponent } from '@idux/pro/textarea' import type { ProTransferComponent } from '@idux/pro/transfer' import type { ProTreeComponent } from '@idux/pro/tree' @@ -24,6 +25,7 @@ declare module 'vue' { IxProLayoutSiderTrigger: LayoutSiderTriggerComponent IxProTable: ProTableComponent IxProTableLayoutTool: ProTableLayoutToolComponent + IxProTagSelect: ProTagSelectComponent IxProTextarea: ProTextareaComponent IxProTransfer: ProTransferComponent IxProTree: ProTreeComponent diff --git a/scripts/gen/theme/update.ts b/scripts/gen/theme/update.ts index 55640b809..55e8d1fd0 100644 --- a/scripts/gen/theme/update.ts +++ b/scripts/gen/theme/update.ts @@ -54,6 +54,7 @@ async function generateThemeVariables(theme: PresetTheme, tokenMeta: TokenMeta) tokens, hashId: '', }, + undefined, transforms, )