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>
This commit is contained in:
Den Moshkin
2025-11-23 04:07:43 +03:00
committed by GitHub
parent 3e0d623e62
commit e3eff3725f
6 changed files with 135 additions and 40 deletions

View File

@@ -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 `<el-button-group>` 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

View File

@@ -1,24 +1,33 @@
<template>
<el-button-group>
<el-button-group class="mb-4">
<el-button type="primary" :icon="ArrowLeft">Previous Page</el-button>
<el-button type="primary">
Next Page<el-icon class="el-icon--right"><ArrowRight /></el-icon>
</el-button>
</el-button-group>
<br />
<el-radio-group v-model="direction" class="mb-2">
<el-radio value="horizontal">Horizontal</el-radio>
<el-radio value="vertical">Vertical</el-radio>
</el-radio-group>
<br />
<el-button-group class="ml-4">
<el-button type="primary" :icon="Edit" />
<el-button type="primary" :icon="Share" />
<el-button type="primary" :icon="Delete" />
<el-button-group :direction="direction">
<el-button type="primary" :icon="House" />
<el-button type="primary" :icon="Operation" />
<el-button type="primary" :icon="Notification" />
</el-button-group>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import {
ArrowLeft,
ArrowRight,
Delete,
Edit,
Share,
House,
Notification,
Operation,
} from '@element-plus/icons-vue'
const direction = ref<'horizontal' | 'vertical'>('horizontal')
</script>

View File

@@ -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: () => () => (
<ButtonGroup type="warning">
<Button type="primary">Prev</Button>
<Button>Next</Button>
</ButtonGroup>
),
})
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'))
})
})

View File

@@ -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<typeof buttonGroupProps>
export type ButtonGroupPropsPublic = __ExtractPublicPropTypes<

View File

@@ -1,5 +1,5 @@
<template>
<div :class="ns.b('group')">
<div :class="[ns.b('group'), ns.bm('group', props.direction)]">
<slot />
</div>
</template>

View File

@@ -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');
}
}
}
}