feat(components): [date-picker] add type years for year multiple select (#15980)

* feat(components): [date-picker] add type years for year multiple select

* fix(components): [date-picker] fix footerVisible while type is not years

* style(components): [date-picker] stand out selected item for year/month

* style(components): [date-picker] remove the in-range background

* docs(components): [date-picker] add 'years' for attribute type
This commit is contained in:
yuchen 2024-03-01 09:35:52 +08:00 committed by GitHub
parent 5341166584
commit 42d1738463
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 110 additions and 34 deletions

View File

@ -158,7 +158,7 @@ Note, date time locale (month name, first day of the week ...) are also configur
| placeholder | placeholder in non-range mode | ^[string] | '' |
| start-placeholder | placeholder for the start date in range mode | ^[string] | — |
| end-placeholder | placeholder for the end date in range mode | ^[string] | — |
| type | type of the picker | ^[enum]`'year' \| 'month' \| 'date' \| 'dates' \| 'datetime' \| 'week' \| 'datetimerange' \| 'daterange' \| 'monthrange'` | date |
| type | type of the picker | ^[enum]`'year' \| 'years' \|'month' \| 'date' \| 'dates' \| 'datetime' \| 'week' \| 'datetimerange' \| 'daterange' \| 'monthrange'` | date |
| format | format of the displayed value in the input box | ^[string] see [date formats](/en-US/component/date-picker#date-formats) | YYYY-MM-DD |
| popper-class | custom class name for DatePicker's dropdown | ^[string] | — |
| popper-options | Customized popper option see more at [popper.js](https://popper.js.org/docs/v2/) | ^[object]`Partial<PopperOptions>` | {} |

View File

@ -21,7 +21,9 @@
@keydown.space.prevent.stop="handleYearTableClick"
@keydown.enter.prevent.stop="handleYearTableClick"
>
<span class="cell">{{ startYear + i * 4 + j }}</span>
<div>
<span class="cell">{{ startYear + i * 4 + j }}</span>
</div>
</td>
<td v-else />
</template>
@ -82,7 +84,8 @@ const isSelectedCell = (year: number) => {
(year === startYear.value &&
props.date.year() < startYear.value &&
props.date.year() > startYear.value + 9) ||
castArray(props.date).findIndex((date) => date.year() === year) >= 0
castArray(props.date).findIndex((date) => date.year() === year) >= 0 ||
castArray(props.parsedValue).findIndex((date) => date?.year() === year) >= 0
)
}
@ -92,7 +95,18 @@ const handleYearTableClick = (event: MouseEvent | KeyboardEvent) => {
if (target && target.textContent) {
if (hasClass(target, 'disabled')) return
const year = target.textContent || target.innerText
emit('pick', Number(year))
if (props.selectionMode === 'years') {
if (event.type === 'keydown') {
emit('pick', castArray(props.parsedValue), false)
return
}
const newValue = hasClass(target, 'current')
? castArray(props.parsedValue).filter((d) => d?.year() !== Number(year))
: castArray(props.parsedValue).concat([dayjs(year)])
emit('pick', newValue)
} else {
emit('pick', Number(year))
}
}
}

View File

@ -142,6 +142,7 @@
<year-table
v-if="currentView === 'year'"
ref="currentViewRef"
:selection-mode="selectionMode"
:date="innerDate"
:disabled-date="disabledDate"
:parsed-value="parsedValue"
@ -158,12 +159,9 @@
</div>
</div>
</div>
<div
v-show="footerVisible && currentView === 'date'"
:class="ppNs.e('footer')"
>
<div v-show="footerVisible" :class="ppNs.e('footer')">
<el-button
v-show="selectionMode !== 'dates'"
v-show="selectionMode !== 'dates' && selectionMode !== 'years'"
text
size="small"
:class="ppNs.e('link-btn')"
@ -228,6 +226,7 @@ import type {
DateTableEmits,
DatesPickerEmits,
WeekPickerEmits,
YearsPickerEmits,
} from '../props/basic-date-table'
type DatePickType = PanelDatePickProps['type']
@ -394,7 +393,7 @@ const handleShortcutClick = (shortcut: Shortcut) => {
const selectionMode = computed<DatePickType>(() => {
const { type } = props
if (['week', 'month', 'year', 'dates'].includes(type)) return type
if (['week', 'month', 'year', 'years', 'dates'].includes(type)) return type
return 'date' as DatePickType
})
@ -421,12 +420,17 @@ const handleMonthPick = async (month: number) => {
handlePanelChange('month')
}
const handleYearPick = async (year: number) => {
const handleYearPick = async (
year: number | YearsPickerEmits,
keepOpen?: boolean
) => {
if (selectionMode.value === 'year') {
innerDate.value = innerDate.value.startOf('year').year(year)
innerDate.value = innerDate.value.startOf('year').year(year as number)
emit(innerDate.value, false)
} else if (selectionMode.value === 'years') {
emit(year as YearsPickerEmits, keepOpen ?? true)
} else {
innerDate.value = innerDate.value.year(year)
innerDate.value = innerDate.value.year(year as number)
currentView.value = 'month'
if (['month', 'year', 'date', 'week'].includes(selectionMode.value)) {
emit(innerDate.value, true)
@ -448,7 +452,11 @@ const showTime = computed(
)
const footerVisible = computed(() => {
return showTime.value || selectionMode.value === 'dates'
const showDateFooter = showTime.value || selectionMode.value === 'dates'
const showYearFooter = selectionMode.value === 'years'
const isDateView = currentView.value === 'date'
const isYearView = currentView.value === 'year'
return (showDateFooter && isDateView) || (showYearFooter && isYearView)
})
const disabledConfirm = computed(() => {
@ -460,7 +468,7 @@ const disabledConfirm = computed(() => {
return disabledDate(props.parsedValue.toDate())
})
const onConfirm = () => {
if (selectionMode.value === 'dates') {
if (selectionMode.value === 'dates' || selectionMode.value === 'years') {
emit(props.parsedValue as Dayjs[])
} else {
// deal with the scenario where: user opens the date time picker, then confirm without doing anything
@ -585,10 +593,9 @@ const isValidValue = (date: unknown) => {
}
const formatToString = (value: Dayjs | Dayjs[]) => {
if (selectionMode.value === 'dates') {
return (value as Dayjs[]).map((_) => _.format(props.format))
}
return (value as Dayjs).format(props.format)
return Array.isArray(value)
? (value as Dayjs[]).map((_) => _.format(props.format))
: (value as Dayjs).format(props.format)
}
const parseUserInput = (value: Dayjs) => {
@ -730,6 +737,9 @@ watch(
if (['month', 'year'].includes(val)) {
currentView.value = val
return
} else if (val === 'years') {
currentView.value = 'year'
return
}
currentView.value = 'date'
},
@ -757,7 +767,8 @@ watch(
() => props.parsedValue,
(val) => {
if (val) {
if (selectionMode.value === 'dates') return
if (selectionMode.value === 'dates' || selectionMode.value === 'years')
return
if (Array.isArray(val)) return
innerDate.value = val
} else {

View File

@ -2,6 +2,7 @@ import type { Dayjs } from 'dayjs'
export declare type IDatePickerType =
| 'year'
| 'years'
| 'month'
| 'date'
| 'dates'

View File

@ -21,6 +21,7 @@ export type BasicDateTableEmits = typeof basicDateTableEmits
export type RangePickerEmits = { minDate: Dayjs; maxDate: null }
export type DatePickerEmits = Dayjs
export type DatesPickerEmits = Dayjs[]
export type YearsPickerEmits = Dayjs[]
export type WeekPickerEmits = {
year: number
week: number

View File

@ -1,5 +1,5 @@
import { buildProps } from '@element-plus/utils'
import { datePickerSharedProps } from './shared'
import { datePickerSharedProps, selectionModeWithDefault } from './shared'
import type { ExtractPropTypes } from 'vue'
@ -9,6 +9,7 @@ export const basicYearTableProps = buildProps({
date,
disabledDate,
parsedValue,
})
selectionMode: selectionModeWithDefault('year'),
} as const)
export type BasicYearTableProps = ExtractPropTypes<typeof basicYearTableProps>

View File

@ -5,7 +5,15 @@ import type { ExtractPropTypes } from 'vue'
import type { Dayjs } from 'dayjs'
import type { DatePickType } from '@element-plus/constants'
const selectionModes = ['date', 'dates', 'year', 'month', 'week', 'range']
const selectionModes = [
'date',
'dates',
'year',
'years',
'month',
'week',
'range',
]
export type RangeState = {
endDate: null | Dayjs

View File

@ -33,7 +33,13 @@
:placeholder="placeholder"
:class="[nsDate.b('editor'), nsDate.bm('editor', type), $attrs.class]"
:style="$attrs.style"
:readonly="!editable || readonly || isDatesPicker || type === 'week'"
:readonly="
!editable ||
readonly ||
isDatesPicker ||
isYearsPicker ||
type === 'week'
"
:label="label"
:tabindex="tabindex"
:validate-event="false"
@ -470,7 +476,7 @@ const displayValue = computed<UserInput>(() => {
if (!isTimePicker.value && valueIsEmpty.value) return ''
if (!pickerVisible.value && valueIsEmpty.value) return ''
if (formattedValue) {
return isDatesPicker.value
return isDatesPicker.value || isYearsPicker.value
? (formattedValue as Array<string>).join(', ')
: formattedValue
}
@ -483,6 +489,8 @@ const isTimePicker = computed(() => props.type.startsWith('time'))
const isDatesPicker = computed(() => props.type === 'dates')
const isYearsPicker = computed(() => props.type === 'years')
const triggerIcon = computed(
() => props.prefixIcon || (isTimeLikePicker.value ? Clock : Calendar)
)

View File

@ -7,6 +7,7 @@ export const DEFAULT_FORMATS_DATEPICKER = {
dates: DEFAULT_FORMATS_DATE,
week: 'gggg[w]ww',
year: 'YYYY',
years: 'YYYY',
month: 'YYYY-MM',
datetime: `${DEFAULT_FORMATS_DATE} ${DEFAULT_FORMATS_TIME}`,
monthrange: 'YYYY-MM',

View File

@ -1,5 +1,6 @@
export const datePickTypes = [
'year',
'years',
'month',
'date',
'dates',

View File

@ -130,11 +130,7 @@
&.selected .#{$namespace}-date-table-cell {
margin-left: 5px;
margin-right: 5px;
background-color: getCssVar('datepicker-inrange-bg-color');
border-radius: 15px;
&:hover {
background-color: getCssVar('datepicker-inrange-hover-bg-color');
}
}
&.selected .#{$namespace}-date-table-cell__text {

View File

@ -7,9 +7,11 @@
border-collapse: collapse;
td {
width: 68px;
text-align: center;
padding: 8px 0;
cursor: pointer;
position: relative;
& div {
height: 48px;
padding: 6px 0;
@ -37,13 +39,16 @@
}
.cell {
width: 60px;
width: 54px;
height: 36px;
display: block;
line-height: 36px;
color: getCssVar('datepicker-text-color');
margin: 0 auto;
border-radius: 18px;
position: absolute;
left: 50%;
transform: translateX(-50%);
&:hover {
color: getCssVar('datepicker-hover-text-color');
}
@ -67,23 +72,33 @@
}
&.start-date div {
margin-left: 3px;
border-top-left-radius: 24px;
border-bottom-left-radius: 24px;
}
&.end-date div {
margin-right: 3px;
border-top-right-radius: 24px;
border-bottom-right-radius: 24px;
}
&.current:not(.disabled) div {
border-radius: 24px;
margin-left: 3px;
margin-right: 3px;
}
&.current:not(.disabled) .cell {
color: getCssVar('datepicker-active-color');
color: $color-white;
background-color: getCssVar('datepicker-active-color');
}
&:focus-visible {
outline: none;
.cell {
outline: 2px solid getCssVar('datepicker-active-color');
outline-offset: 1px;
}
}
}

View File

@ -11,9 +11,17 @@
}
td {
width: 68px;
text-align: center;
padding: 20px 3px;
padding: 8px 0px;
cursor: pointer;
position: relative;
& div {
height: 48px;
padding: 6px 0;
box-sizing: border-box;
}
&.today {
.cell {
@ -33,27 +41,38 @@
}
.cell {
width: 48px;
width: 54px;
height: 36px;
display: block;
line-height: 36px;
color: getCssVar('datepicker-text-color');
border-radius: 18px;
margin: 0 auto;
position: absolute;
left: 50%;
transform: translateX(-50%);
&:hover {
color: getCssVar('datepicker-hover-text-color');
}
}
&.current:not(.disabled) div {
border-radius: 24px;
margin-left: 3px;
margin-right: 3px;
}
&.current:not(.disabled) .cell {
color: getCssVar('datepicker-active-color');
color: $color-white;
background-color: getCssVar('datepicker-active-color');
}
&:focus-visible {
outline: none;
.cell {
outline: 2px solid getCssVar('datepicker-active-color');
outline-offset: 1px;
}
}
}