diff --git a/examples/sites/demos/apis/action-menu.js b/examples/sites/demos/apis/action-menu.js index a66a41298..c2c7b90d9 100644 --- a/examples/sites/demos/apis/action-menu.js +++ b/examples/sites/demos/apis/action-menu.js @@ -17,6 +17,17 @@ export default { mode: ['pc'], pcDemo: 'max-show-num' }, + { + name: 'mode', + type: '"default" | "card"', + defaultValue: '"default"', + desc: { + 'zh-CN': '菜单按钮模式', + 'en-US': 'Card mode' + }, + mode: ['pc'], + pcDemo: 'card-mode' + }, { name: 'more-text', type: 'string', diff --git a/examples/sites/demos/pc/app/action-menu/card-mode-composition-api.vue b/examples/sites/demos/pc/app/action-menu/card-mode-composition-api.vue new file mode 100644 index 000000000..881ee9d6e --- /dev/null +++ b/examples/sites/demos/pc/app/action-menu/card-mode-composition-api.vue @@ -0,0 +1,31 @@ + + + diff --git a/examples/sites/demos/pc/app/action-menu/card-mode.spec.ts b/examples/sites/demos/pc/app/action-menu/card-mode.spec.ts new file mode 100644 index 000000000..94c4027ec --- /dev/null +++ b/examples/sites/demos/pc/app/action-menu/card-mode.spec.ts @@ -0,0 +1,16 @@ +import { test, expect } from '@playwright/test' + +test('基本用法', async ({ page }) => { + page.on('pageerror', (exception) => expect(exception).toBeNull()) + await page.goto('action-menu#card-mode') + + const wrap = page.locator('#card-mode') + const actionMenu = wrap.locator('.tiny-action-menu') + const visibleItem = actionMenu.locator('.tiny-action-menu__item') + const moreItem = visibleItem.last() + + await expect(visibleItem).toHaveCount(4) + await expect(moreItem).not.toHaveText(/更多/) + // 三点图标 + await expect(moreItem.locator('circle')).toHaveCount(3) +}) diff --git a/examples/sites/demos/pc/app/action-menu/card-mode.vue b/examples/sites/demos/pc/app/action-menu/card-mode.vue new file mode 100644 index 000000000..7f6149075 --- /dev/null +++ b/examples/sites/demos/pc/app/action-menu/card-mode.vue @@ -0,0 +1,39 @@ + + + diff --git a/examples/sites/demos/pc/app/action-menu/webdoc/action-menu.js b/examples/sites/demos/pc/app/action-menu/webdoc/action-menu.js index a6930b72e..41453d3d9 100644 --- a/examples/sites/demos/pc/app/action-menu/webdoc/action-menu.js +++ b/examples/sites/demos/pc/app/action-menu/webdoc/action-menu.js @@ -80,6 +80,20 @@ export default { }, codeFiles: ['spacing.vue'] }, + { + demoId: 'card-mode', + name: { + 'zh-CN': '菜单模式', + 'en-US': 'Mode' + }, + desc: { + 'zh-CN': + '

通过 mode 属性设置菜单模式以适配在不同场景中能够使用,例如:菜单按钮在卡片中使用,可以配置为 card,卡片模式字体为黑色,间距为10px。 mode 默认为值default

', + 'en-US': + '

Use the mode attribute to set the menu mode so that the vehicle can be used in different scenarios. For example, if the menu button is used in configuration, it can be configured as card, and the auxiliary mode font is Black with 10px spacing. mode defaults to default.

' + }, + codeFiles: ['card-mode.vue'] + }, { demoId: 'popper-class', name: { diff --git a/packages/renderless/src/action-menu/index.ts b/packages/renderless/src/action-menu/index.ts index 295dc34af..e0a975b4e 100644 --- a/packages/renderless/src/action-menu/index.ts +++ b/packages/renderless/src/action-menu/index.ts @@ -12,6 +12,58 @@ import type { IActionMenuRenderlessParams, IActionMenuItemData } from '@/types' +export const computedMaxShowNum = + ({ props, state }: Pick) => + (): number => { + if (props.maxShowNum !== undefined) { + return props.maxShowNum + } + if (state.isCardMode) { + return 3 + } else { + return 2 + } + } + +export const computedSpacing = + ({ props, state }: Pick) => + (): string => { + if (props.spacing !== undefined) { + return String(props.spacing).includes('px') ? props.spacing : props.spacing + 'px' + } + if (state.isCardMode) { + return '10px' + } else { + return '5px' + } + } + +export const computedMoreText = + ({ props, state, t }: Pick) => + (): string => { + if (props.moreText !== undefined) { + return props.moreText + } + if (state.isCardMode) { + return '' + } else { + return t('ui.actionMenu.moreText') + } + } + +export const computedSuffixIcon = + ({ props, state }: Pick) => + (): string | Object => { + if (props.suffixIcon) { + return props.suffixIcon + } + if (state.isCardMode) { + return 'tiny-icon-ellipsis' + } else { + return '' + } + } + export const handleMoreClick = (emit: IActionMenuRenderlessParams['emit']) => () => { emit('more-click') } diff --git a/packages/renderless/src/action-menu/vue.ts b/packages/renderless/src/action-menu/vue.ts index 99c8c6bc9..2b8f041e2 100644 --- a/packages/renderless/src/action-menu/vue.ts +++ b/packages/renderless/src/action-menu/vue.ts @@ -17,27 +17,45 @@ import type { ISharedRenderlessParamHooks, IActionMenuRenderlessParamUtils } from '@/types' -import { handleMoreClick, handleItemClick, visibleChange } from './index' +import { + handleMoreClick, + handleItemClick, + visibleChange, + computedMaxShowNum, + computedSpacing, + computedMoreText, + computedSuffixIcon +} from './index' export const api = ['state', 'handleMoreClick', 'handleItemClick', 'visibleChange'] export const renderless = ( props: IActionMenuProps, { computed, reactive }: ISharedRenderlessParamHooks, - { emit }: IActionMenuRenderlessParamUtils + { emit, t }: IActionMenuRenderlessParamUtils ): IActionMenuApi => { + const api = {} as IActionMenuApi + const state: IActionMenuState = reactive({ - visibleOptions: computed(() => props.options.slice(0, props.maxShowNum)), - moreOptions: computed(() => props.options.slice(props.maxShowNum)), - spacing: computed(() => (String(props.spacing).includes('px') ? props.spacing : props.spacing + 'px')) + visibleOptions: computed(() => props.options.slice(0, state.maxShowNum)), + isCardMode: computed(() => props.mode === 'card'), + moreOptions: computed(() => props.options.slice(state.maxShowNum)), + spacing: computed(() => api.computedSpacing()), + maxShowNum: computed(() => api.computedMaxShowNum()), + moreText: computed(() => api.computedMoreText()), + suffixIcon: computed(() => api.computedSuffixIcon()) }) - const api: IActionMenuApi = { + Object.assign(api, { + state, handleMoreClick: handleMoreClick(emit), handleItemClick: handleItemClick(emit), visibleChange: visibleChange(emit), - state - } + computedMaxShowNum: computedMaxShowNum({ props, state }), + computedSpacing: computedSpacing({ props, state }), + computedMoreText: computedMoreText({ props, state, t }), + computedSuffixIcon: computedSuffixIcon({ props, state }) + }) return api } diff --git a/packages/renderless/types/action-menu.type.ts b/packages/renderless/types/action-menu.type.ts index 6293c57dc..279dc3e35 100644 --- a/packages/renderless/types/action-menu.type.ts +++ b/packages/renderless/types/action-menu.type.ts @@ -10,14 +10,27 @@ * */ -import type { ComputedRef, ExtractPropTypes, ComponentPublicInstance } from 'vue' +import type { ExtractPropTypes, ComponentPublicInstance } from 'vue' import type { actionMenuProps } from '@/action-menu/src' import type { ISharedRenderlessFunctionParams, ISharedRenderlessParamUtils } from './shared.type' +import type { + handleMoreClick, + handleItemClick, + visibleChange, + computedMaxShowNum, + computedSpacing, + computedMoreText, + computedSuffixIcon +} from '../src/action-menu' export interface IActionMenuState { - visibleOptions: ComputedRef - moreOptions: ComputedRef - spacing: ComputedRef + visibleOptions: object + moreOptions: object + isCardMode: boolean + spacing: string | number + maxShowNum: number + moreText: string + suffixIcon: string | Object } export type IActionMenuProps = ExtractPropTypes @@ -34,9 +47,13 @@ export interface IActionMenuItemData { } export interface IActionMenuApi { - handleMoreClick: () => void - handleItemClick: (data: IActionMenuItemData) => void - visibleChange: (status: boolean) => void + handleMoreClick: ReturnType + handleItemClick: ReturnType + visibleChange: ReturnType + computedMaxShowNum: ReturnType + computedSpacing: ReturnType + computedMoreText: ReturnType + computedSuffixIcon: ReturnType state: IActionMenuState } diff --git a/packages/theme/src/action-menu/index.less b/packages/theme/src/action-menu/index.less index b9df25a6b..390b281b9 100644 --- a/packages/theme/src/action-menu/index.less +++ b/packages/theme/src/action-menu/index.less @@ -35,6 +35,24 @@ &__wrap { display: flex; + &.@{action-menu-prefix-cls}__card-mode { + .@{action-menu-prefix-cls}__item { + .tiny-svg { + fill: var(--ti-action-menu-item-card-text-color); + } + + .@{dropdown-prefix-cls} { + .@{dropdown-prefix-cls}-trigger { + &:hover { + .tiny-svg { + fill: var(--ti-action-menu-item-card-text-color); + } + } + } + } + } + } + .@{action-menu-prefix-cls}__item { display: flex; justify-content: space-between; @@ -54,15 +72,9 @@ } &:not(.is-disabled):hover { - color: var(--ti-action-menu-item-hover-text-color); - &.@{action-menu-prefix-cls}__item-visable { .@{dropdown-item-prefix-cls} { background-color: var(--ti-action-menu-item-hover-bg-color); - - &__wrap { - color: var(--ti-action-menu-item-hover-text-color); - } } } @@ -80,6 +92,21 @@ &:hover { background-color: var(--ti-action-menu-item-hover-bg-color); text-decoration: var(--ti-action-menu-hover-text-decoratio); + color: var(--ti-action-menu-item-hover-text-color); + } + } + + &.@{action-menu-prefix-cls}__card-item { + .@{dropdown-item-prefix-cls}__wrap { + color: var(--ti-action-menu-item-card-text-color); + + .tiny-svg { + fill: var(--ti-action-menu-item-card-text-color); + } + + &:hover { + color: var(--ti-action-menu-item-card-hover-text-color); + } } } diff --git a/packages/theme/src/action-menu/smb-theme.js b/packages/theme/src/action-menu/smb-theme.js index 5963ed6b6..eef6e1884 100644 --- a/packages/theme/src/action-menu/smb-theme.js +++ b/packages/theme/src/action-menu/smb-theme.js @@ -9,5 +9,7 @@ export const tinyActionMenuSmbTheme = { 'ti-action-menu-hover-text-decoratio': 'underline', 'ti-dropdown-line-height': 'calc(var(--ti-common-line-height-4) + 2px)', 'ti-action-menu-more-icon-width': 'var(--ti-common-size-5x)', - 'ti-action-menu-more-icon-height': 'var(--ti-common-size-5x)' + 'ti-action-menu-more-icon-height': 'var(--ti-common-size-5x)', + 'ti-action-menu-item-card-text-color': 'var(--ti-common-color-text-primary)', + 'ti-action-menu-item-card-hover-text-color': 'var(--ti-common-color-text-primary)' } diff --git a/packages/theme/src/action-menu/vars.less b/packages/theme/src/action-menu/vars.less index 9e3463495..a0a1981bb 100644 --- a/packages/theme/src/action-menu/vars.less +++ b/packages/theme/src/action-menu/vars.less @@ -51,4 +51,8 @@ --ti-action-menu-item-svg-margin-bottom: var(--ti-common-space-0, 0px); // 下拉菜单项图标左侧内边距 --ti-action-menu-item-svg-margin-left: var(--ti-common-space-0, 0px); + // 下拉菜单卡片模式字体颜色 + --ti-action-menu-item-card-text-color: var(--ti-common-color-text-link, #526ecc); + // 下拉菜单卡片模式字体颜色 + --ti-action-menu-item-card-hover-text-color: var(--ti-common-color-text-link-hover, #344899); } diff --git a/packages/theme/src/dropdown-item/smb-theme.js b/packages/theme/src/dropdown-item/smb-theme.js index d49d54d95..f317104cc 100644 --- a/packages/theme/src/dropdown-item/smb-theme.js +++ b/packages/theme/src/dropdown-item/smb-theme.js @@ -3,7 +3,7 @@ export const tinyDropdownItemSmbTheme = { 'ti-dropdown-item-padding-vertical': 'var(--ti-common-space-0)', 'ti-dropdown-item-padding-horizontal': 'var(--ti-common-space-4x)', 'ti-dropdown-item-hover-text-color': 'var(--ti-common-color-text-primary)', - 'ti-dropdown-item-icon-color-hover': 'var(--ti-common-color-icon-graybg-hover)', + 'ti-dropdown-item-icon-color-hover': 'var(--ti-common-color-text-link-hover)', 'ti-dropdown-item-expand-svg-margin-left': 'var(--ti-common-space-0)', 'ti-dropdown-item-expand-svg-margin-right': 'var(--ti-common-space-2x)', 'ti-dropdown-item-content-margin-left': 'calc(var(--ti-dropdown-item-expand-icon-size) + var(--ti-common-space-2x))', diff --git a/packages/theme/src/dropdown/smb-theme.js b/packages/theme/src/dropdown/smb-theme.js index da3c16583..f3891bc83 100644 --- a/packages/theme/src/dropdown/smb-theme.js +++ b/packages/theme/src/dropdown/smb-theme.js @@ -1,8 +1,8 @@ export const tinyDropdownSmbTheme = { 'ti-dropdown-icon-size': 'var(--ti-common-font-size-2)', - 'ti-dropdown-text-color-hover': 'var(--ti-common-color-text-primary)', - 'ti-dropdown-icon-color': 'var(--ti-common-color-placeholder)', // TINY-TODO:缺少#808080图标色变量 - 'ti-dropdown-icon-color-hover': 'var(--ti-common-color-icon-graybg-hover)', + 'ti-dropdown-text-color-hover': 'var(--ti-common-color-text-link)', + 'ti-dropdown-icon-color': 'var(--ti-common-color-text-link)', + 'ti-dropdown-icon-color-hover': 'var(--ti-common-color-text-link)', 'ti-dropdown-caret-svg-margin-horizontal': 'var(--ti-common-space-0)', 'ti-dropdown-caret-button-padding-right': 'var(--ti-common-space-6x)', 'ti-dropdown-caret-line-width': 'var(--ti-common-size-0)', diff --git a/packages/vue/src/action-menu/src/index.ts b/packages/vue/src/action-menu/src/index.ts index 54d0dbe39..a76ebf676 100644 --- a/packages/vue/src/action-menu/src/index.ts +++ b/packages/vue/src/action-menu/src/index.ts @@ -19,14 +19,10 @@ export const actionMenuProps = { type: Array, default: () => [] }, - maxShowNum: { - type: Number, - default: 2 - }, + maxShowNum: Number, moreText: String, spacing: { - type: [String, Number], - default: '5px' + type: [String, Number] }, textField: { type: String, @@ -48,6 +44,10 @@ export const actionMenuProps = { showIcon: { type: Boolean, default: true + }, + mode: { + type: String, + default: 'default' } } diff --git a/packages/vue/src/action-menu/src/pc.vue b/packages/vue/src/action-menu/src/pc.vue index 5a3586341..a587f2282 100644 --- a/packages/vue/src/action-menu/src/pc.vue +++ b/packages/vue/src/action-menu/src/pc.vue @@ -1,10 +1,14 @@