From e3eff3725fd6ea6a9f588455b966f573d36330da Mon Sep 17 00:00:00 2001 From: Den Moshkin <57352899+VisualYuki@users.noreply.github.com> Date: Sun, 23 Nov 2025 04:07:43 +0300 Subject: [PATCH] feat(components): [button-group]: add direction prop (#18906) * feat(components): [button-group]:add vertical direction for button group * feat(components): [button-group]: add direction prop (update) * feat(components): [button-group]: fix docs * feat(components): [button-group]: update version * Update docs/en-US/component/button.md Co-authored-by: btea <2356281422@qq.com> * Update docs/en-US/component/button.md * chore: format * docs: improve sentence * docs: improve display example * refactor: enhance prop type - fit with segmented direction prop - enhance type according with https://github.com/element-plus/element-plus/pull/22757 --------- Co-authored-by: btea <2356281422@qq.com> Co-authored-by: Dsaquel <291874700n@gmail.com> --- docs/en-US/component/button.md | 11 +- docs/examples/button/group.vue | 25 ++-- .../button/__tests__/button.test.tsx | 21 ++++ .../components/button/src/button-group.ts | 9 ++ .../components/button/src/button-group.vue | 2 +- packages/theme-chalk/src/button-group.scss | 107 +++++++++++++----- 6 files changed, 135 insertions(+), 40 deletions(-) diff --git a/docs/en-US/component/button.md b/docs/en-US/component/button.md index dfea7eae84..dc3ae2c4fc 100644 --- a/docs/en-US/component/button.md +++ b/docs/en-US/component/button.md @@ -75,6 +75,8 @@ button/icon Displayed as a button group, can be used to group a series of similar operations. +In ^(2.11.9) you can use the `direction` attribute. + :::demo Use tag `` to group your buttons. button/group @@ -180,10 +182,11 @@ button/custom ### ButtonGroup Attributes -| Name | Description | Type | Default | -| ---- | ------------------------------------------------ | ------------------------------------------------------------------ | ------- | -| size | control the size of buttons in this button-group | ^[enum]`'large' \| 'default' \| 'small'` | — | -| type | control the type of buttons in this button-group | ^[enum]`'primary' \| 'success' \| 'warning' \| 'danger' \| 'info'` | — | +| Name | Description | Type | Default | +| ------------------- | ------------------------------------------------ | ------------------------------------------------------------------ | ---------- | +| size | control the size of buttons in this button-group | ^[enum]`'large' \| 'default' \| 'small'` | — | +| type | control the type of buttons in this button-group | ^[enum]`'primary' \| 'success' \| 'warning' \| 'danger' \| 'info'` | — | +| direction ^(2.11.9) | display direction | ^[enum]`'horizontal' \| 'vertical'` | horizontal | ### ButtonGroup Slots diff --git a/docs/examples/button/group.vue b/docs/examples/button/group.vue index e5aa60be81..6166d8da00 100644 --- a/docs/examples/button/group.vue +++ b/docs/examples/button/group.vue @@ -1,24 +1,33 @@ diff --git a/packages/components/button/__tests__/button.test.tsx b/packages/components/button/__tests__/button.test.tsx index 58628d24ed..2e75a85a29 100644 --- a/packages/components/button/__tests__/button.test.tsx +++ b/packages/components/button/__tests__/button.test.tsx @@ -3,12 +3,14 @@ import { mount } from '@vue/test-utils' import { describe, expect, it, test } from 'vitest' import { Loading, Search } from '@element-plus/icons-vue' import Form from '@element-plus/components/form' +import { useNamespace } from '@element-plus/hooks' import Button from '../src/button.vue' import ButtonGroup from '../src/button-group.vue' import type { ComponentSize } from '@element-plus/constants' const AXIOM = 'Rem is the best girl' +const ns = useNamespace('button') describe('Button.vue', () => { it('create', () => { @@ -323,4 +325,23 @@ describe('Button Group', () => { await btn.trigger('click') expect(wrapper.emitted('click')).toHaveLength(1) }) + + it('direction prop', async () => { + const wrapper = mount({ + setup: () => () => ( + + + + + ), + }) + + expect(wrapper.classes()).toContain(ns.bm('group', 'horizontal')) + expect(wrapper.classes()).not.toContain(ns.bm('group', 'vertical')) + + await wrapper.setProps({ direction: 'vertical' }) + + expect(wrapper.classes()).toContain(ns.bm('group', 'vertical')) + expect(wrapper.classes()).not.toContain(ns.bm('group', 'horizontal')) + }) }) diff --git a/packages/components/button/src/button-group.ts b/packages/components/button/src/button-group.ts index 46115b071c..46cd9c29a0 100644 --- a/packages/components/button/src/button-group.ts +++ b/packages/components/button/src/button-group.ts @@ -1,3 +1,4 @@ +import { definePropType } from '@element-plus/utils' import { buttonProps } from './button' import type { ExtractPropTypes, __ExtractPublicPropTypes } from 'vue' @@ -11,6 +12,14 @@ export const buttonGroupProps = { * @description control the type of buttons in this button-group */ type: buttonProps.type, + /** + * @description display direction + */ + direction: { + type: definePropType<'horizontal' | 'vertical'>(String), + values: ['horizontal', 'vertical'], + default: 'horizontal', + }, } as const export type ButtonGroupProps = ExtractPropTypes export type ButtonGroupPropsPublic = __ExtractPublicPropTypes< diff --git a/packages/components/button/src/button-group.vue b/packages/components/button/src/button-group.vue index a9425e58f5..8d1ae5522c 100644 --- a/packages/components/button/src/button-group.vue +++ b/packages/components/button/src/button-group.vue @@ -1,5 +1,5 @@ diff --git a/packages/theme-chalk/src/button-group.scss b/packages/theme-chalk/src/button-group.scss index b6d312e2dc..954e7eec12 100644 --- a/packages/theme-chalk/src/button-group.scss +++ b/packages/theme-chalk/src/button-group.scss @@ -5,24 +5,11 @@ @use 'mixins/utils' as *; @include b(button-group) { - display: inline-block; - vertical-align: middle; - @include utils-clearfix; - & > .#{$namespace}-button { - float: left; - position: relative; & + .#{$namespace}-button { margin-left: 0; } - &:first-child { - border-top-right-radius: 0; - border-bottom-right-radius: 0; - } - &:last-child { - border-top-left-radius: 0; - border-bottom-left-radius: 0; - } + &:first-child:last-child { border-top-right-radius: map.get($button-border-radius, 'default'); border-bottom-right-radius: map.get($button-border-radius, 'default'); @@ -37,12 +24,10 @@ border-radius: 50%; } } + &:not(:first-child):not(:last-child) { border-radius: 0; } - &:not(:last-child) { - margin-right: -1px; - } &:hover, &:focus, @@ -55,25 +40,93 @@ } } - & > .#{$namespace}-dropdown { + @include m('horizontal') { + display: inline-block; + vertical-align: middle; + @include utils-clearfix; + & > .#{$namespace}-button { - border-top-left-radius: 0; - border-bottom-left-radius: 0; - border-left-color: getCssVar('button', 'divide-border-color'); + float: left; + position: relative; + + &:first-child { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + &:last-child { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + + &:not(:last-child) { + margin-right: -1px; + } + } + + @each $type in (primary, success, warning, danger, info) { + .#{$namespace}-button--#{$type} { + &:first-child { + border-right-color: getCssVar('button', 'divide-border-color'); + } + &:last-child { + border-left-color: getCssVar('button', 'divide-border-color'); + } + &:not(:first-child):not(:last-child) { + border-left-color: getCssVar('button', 'divide-border-color'); + border-right-color: getCssVar('button', 'divide-border-color'); + } + } + } + + & > .#{$namespace}-dropdown { + & > .#{$namespace}-button { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-left-color: getCssVar('button', 'divide-border-color'); + } } } - @each $type in (primary, success, warning, danger, info) { - .#{$namespace}-button--#{$type} { + @include m('vertical') { + display: inline-flex; + flex-direction: column; + align-items: stretch; + + & > .#{$namespace}-button { + margin-top: -1px; + &:first-child { - border-right-color: getCssVar('button', 'divide-border-color'); + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; } &:last-child { + border-top-left-radius: 0; + border-top-right-radius: 0; + } + } + + & > .#{$namespace}-dropdown { + margin-top: -1px; + + & > .#{$namespace}-button { + border-top-left-radius: 0; + border-top-right-radius: 0; border-left-color: getCssVar('button', 'divide-border-color'); } - &:not(:first-child):not(:last-child) { - border-left-color: getCssVar('button', 'divide-border-color'); - border-right-color: getCssVar('button', 'divide-border-color'); + } + + @each $type in (primary, success, warning, danger, info) { + .#{$namespace}-button--#{$type} { + &:first-child { + border-bottom-color: getCssVar('button', 'divide-border-color'); + } + &:last-child { + border-top-color: getCssVar('button', 'divide-border-color'); + } + &:not(:first-child):not(:last-child) { + border-top-color: getCssVar('button', 'divide-border-color'); + border-bottom-color: getCssVar('button', 'divide-border-color'); + } } } }