feat(components): segmented support vertical direction (#18653)

* feat(components): segmented support vertical direction

* fix(components): improve

* fix(components): empty commit

* fix(components): update api to direction

* chore(components): use n.m

* fix(components): improve

* fix(components): fix

* fix(components): fix

* Update docs/examples/segmented/custom-direction.vue

Co-authored-by: btea <2356281422@qq.com>

* Update docs/examples/segmented/custom-direction.vue

Co-authored-by: btea <2356281422@qq.com>

* fix(components): lint fix

---------

Co-authored-by: btea <2356281422@qq.com>
This commit is contained in:
thinkasany 2024-10-31 22:52:43 +08:00 committed by GitHub
parent dab6b73040
commit 6b1e951368
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 150 additions and 5 deletions

View File

@ -17,6 +17,16 @@ segmented/basic
:::
## Direction Usage ^(2.8.7)
Set `vertical` to change direction.
:::demo
segmented/custom-direction
:::
## Disabled
Set `disabled` of segmented or option to `true` to disable it.

View File

@ -0,0 +1,96 @@
<template>
<div>
<el-segmented
v-model="size"
:options="sizeOptions"
style="margin-bottom: 1rem"
/>
<br />
<el-segmented
v-model="direction"
:options="directionOptions"
style="margin-bottom: 1rem"
/>
<br />
<el-segmented
v-model="value"
:options="options"
:direction="direction"
:size="size"
>
<template #default="{ item }">
<div
:class="[
'flex',
'items-center',
'gap-2',
'flex-col',
direction === 'horizontal' && 'p-2',
]"
>
<el-icon size="20">
<component :is="item.icon" />
</el-icon>
<div>{{ item.label }}</div>
</div>
</template>
</el-segmented>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import {
Apple,
Cherry,
Grape,
Orange,
Pear,
Watermelon,
} from '@element-plus/icons-vue'
import type { SegmentedProps } from 'element-plus'
const value = ref('Apple')
const direction = ref<SegmentedProps['direction']>('horizontal')
const size = ref<SegmentedProps['size']>('default')
const directionOptions = [
{ label: 'Horizontal', value: 'horizontal' },
{ label: 'Vertical', value: 'vertical' },
]
const sizeOptions = ['large', 'default', 'small']
const options = [
{
label: 'Apple',
value: 'Apple',
icon: Apple,
},
{
label: 'Cherry',
value: 'Cherry',
icon: Cherry,
},
{
label: 'Grape',
value: 'Grape',
icon: Grape,
},
{
label: 'Orange',
value: 'Orange',
icon: Orange,
},
{
label: 'Pear',
value: 'Pear',
icon: Pear,
},
{
label: 'Watermelon',
value: 'Watermelon',
icon: Watermelon,
disabled: true,
},
]
</script>

View File

@ -13,6 +13,10 @@ import type { ExtractPropTypes } from 'vue'
import type Segmented from './segmented.vue'
export const segmentedProps = buildProps({
direction: {
type: definePropType<'vertical' | 'horizontal'>(String),
default: 'horizontal',
},
/**
* @description options of segmented
*/

View File

@ -8,7 +8,7 @@
:aria-label="!isLabeledByFormItem ? ariaLabel || 'segmented' : undefined"
:aria-labelledby="isLabeledByFormItem ? formItem!.labelId : undefined"
>
<div :class="ns.e('group')">
<div :class="[ns.e('group'), ns.m(props.direction)]">
<div :style="selectedStyle" :class="selectedCls" />
<label
v-for="(item, index) in options"
@ -68,7 +68,9 @@ const activeElement = useActiveElement()
const state = reactive({
isInit: false,
width: 0,
height: 0,
translateX: 0,
translateY: 0,
focusVisible: false,
})
@ -117,13 +119,19 @@ const updateSelect = () => {
if (!selectedItem || !selectedItemInput) {
state.width = 0
state.translateX = 0
state.translateY = 0
state.focusVisible = false
return
}
const rect = selectedItem.getBoundingClientRect()
state.isInit = true
state.width = rect.width
state.translateX = selectedItem.offsetLeft
if (props.direction === 'vertical') {
state.height = rect.height
state.translateY = selectedItem.offsetTop
} else {
state.width = rect.width
state.translateX = selectedItem.offsetLeft
}
try {
// This will failed in test
state.focusVisible = selectedItemInput.matches(':focus-visible')
@ -137,8 +145,12 @@ const segmentedCls = computed(() => [
])
const selectedStyle = computed(() => ({
width: `${state.width}px`,
transform: `translateX(${state.translateX}px)`,
width: props.direction === 'vertical' ? '100%' : `${state.width}px`,
height: props.direction === 'vertical' ? `${state.height}px` : '100%',
transform:
props.direction === 'vertical'
? `translateY(${state.translateY}px)`
: `translateX(${state.translateX}px)`,
display: state.isInit ? 'block' : 'none',
}))

View File

@ -35,6 +35,23 @@ $segmented-item-padding: map.merge(
$segmented-item-padding
);
$segmented-item-padding-vertical: () !default;
$segmented-item-padding-vertical: map.merge(
(
'large': 11px 11px,
'default':11px 11px,
'small': 7px 7px,
),
$segmented-item-padding-vertical
);
.#{$namespace}-segmented--vertical {
flex-direction: column;
.#{$namespace}-segmented__item {
padding: map.get($segmented-item-padding-vertical, 'default');
}
}
@include b(segmented) {
@include set-component-css-var('segmented', $segmented);
}
@ -151,6 +168,12 @@ $segmented-item-padding: map.merge(
border-radius: calc(#{map.get($segmented-border-radius, $size)} - 2px);
}
.#{$namespace}-segmented--vertical {
@include e(item) {
padding: map.get($segmented-item-padding-vertical, $size);
}
}
@include e(item) {
border-radius: calc(#{map.get($segmented-border-radius, $size)} - 2px);
padding: map.get($segmented-item-padding, $size);