Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(DatePicker): add quarter precision #6664

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:

- name: Install pnpm
id: pnpm-install
uses: pnpm/action-setup@v2.2.4
uses: pnpm/action-setup@v4
with:
version: 7
run_install: false
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/compressed-size.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
steps:
- name: Install pnpm
id: pnpm-install
uses: pnpm/action-setup@v2
uses: pnpm/action-setup@v4
with:
version: 7
run_install: false
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/doc-site.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:

- name: Install pnpm
id: pnpm-install
uses: pnpm/action-setup@v2
uses: pnpm/action-setup@v4
with:
version: 7
run_install: false
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/preview-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:

- name: Install pnpm
id: pnpm-install
uses: pnpm/action-setup@v2
uses: pnpm/action-setup@v4
with:
version: 7
run_install: false
Expand Down
20 changes: 20 additions & 0 deletions src/components/date-picker-view/demos/demo1.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ export default () => {
/>
</DemoBlock>

<DemoBlock title='季度选择器' padding='0'>
<DatePickerView
onChange={val => console.log('onChange', val)}
precision='quarter'
defaultValue={now}
renderLabel={quarterLabelRenderer}
/>
</DemoBlock>

<DemoBlock title='过滤可供选择的时间' padding='0'>
<DatePickerView
defaultValue={now}
Expand Down Expand Up @@ -96,6 +105,17 @@ const weekdayLabelRenderer = (type: string, data: number) => {
}
}

const quarterLabelRenderer = (type: string, data: number) => {
switch (type) {
case 'year':
return data + '年'
case 'quarter':
return data + '季度'
default:
return data
}
}

const dateFilter: DatePickerFilter = {
day: (val, { date }) => {
// 去除所有周末
Expand Down
20 changes: 20 additions & 0 deletions src/components/date-picker-view/demos/demo2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ export default () => {
filter={weekFilter}
/>
</DemoBlock>

<DemoBlock title='季度选择器过滤' padding='0'>
<DatePickerView
onChange={val => console.log('onChange', val)}
precision='quarter'
defaultValue={now}
filter={quarterFilter}
/>
</DemoBlock>
</>
)
}
Expand Down Expand Up @@ -70,3 +79,14 @@ const weekFilter: DatePickerFilter = {
return true
},
}

const quarterFilter: DatePickerFilter = {
year: val => val > new Date().getFullYear() - 3,
quarter: val => {
// 去除每年的1季度和3季度
if (val === 1 || val === 3) {
return false
}
return true
},
}
111 changes: 111 additions & 0 deletions src/components/date-picker/date-picker-quarter-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import dayjs from 'dayjs'
import quarterOfYear from 'dayjs/plugin/quarterOfYear'
import type { ReactNode } from 'react'
import { PickerColumn } from '../picker'
import type { DatePickerFilter } from './date-picker-utils'

dayjs.extend(quarterOfYear)

export type QuarterPrecision = 'year' | 'quarter'

const precisionRankRecord: Record<QuarterPrecision, number> = {
year: 0,
quarter: 1,
}

export function generateDatePickerColumns(
selected: string[],
min: Date,
max: Date,
precision: QuarterPrecision,
renderLabel: (type: QuarterPrecision, data: number) => ReactNode,
filter: DatePickerFilter | undefined
) {
const ret: PickerColumn[] = []

const minYear = min.getFullYear()
const maxYear = max.getFullYear()

const rank = precisionRankRecord[precision]

const selectedYear = parseInt(selected[0])
const isInMinYear = selectedYear === minYear
const isInMaxYear = selectedYear === maxYear

const minDay = dayjs(min)
const maxDay = dayjs(max)
const minQuarter = minDay.quarter()
const maxQuarter = maxDay.quarter()

const generateColumn = (
from: number,
to: number,
precision: QuarterPrecision
) => {
let column: number[] = []
for (let i = from; i <= to; i++) {
column.push(i)
}
const prefix = selected.slice(0, precisionRankRecord[precision])
const currentFilter = filter?.[precision]
if (currentFilter && typeof currentFilter === 'function') {
column = column.filter(i =>
currentFilter(i, {
get date() {
const stringArray = [...prefix, i.toString()]
return convertStringArrayToDate(stringArray)
},
})
)
}
return column
}

if (rank >= precisionRankRecord.year) {
const lower = minYear
const upper = maxYear
const years = generateColumn(lower, upper, 'year')
ret.push(
years.map(v => ({
label: renderLabel('year', v),
value: v.toString(),
}))
)
}

if (rank >= precisionRankRecord.quarter) {
const lower = isInMinYear ? minQuarter : 1
const upper = isInMaxYear ? maxQuarter : 4
const quarters = generateColumn(lower, upper, 'quarter')
ret.push(
quarters.map(v => ({
label: renderLabel('quarter', v),
value: v.toString(),
}))
)
}

return ret
}

export function convertDateToStringArray(
date: Date | undefined | null
): string[] {
if (!date) return []
const day = dayjs(date)
return [day.year().toString(), day.quarter().toString()]
}

export function convertStringArrayToDate<
T extends string | number | null | undefined,
>(value: T[]): Date {
const yearString = value[0] ?? '1900'
const quarterString = value[1] ?? '1'
const day = dayjs()
.year(parseInt(yearString as string))
.quarter(parseInt(quarterString as string))
.hour(0)
.minute(0)
.second(0)
return day.toDate()
}
25 changes: 20 additions & 5 deletions src/components/date-picker/date-picker-utils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { RenderLabel } from '../date-picker-view/date-picker-view'
import type { DatePrecision } from './date-picker-date-utils'
import type { WeekPrecision } from './date-picker-week-utils'
import * as dateUtils from './date-picker-date-utils'
import type { QuarterPrecision } from './date-picker-quarter-utils'
import * as quarterUtils from './date-picker-quarter-utils'
import type { WeekPrecision } from './date-picker-week-utils'
import * as weekUtils from './date-picker-week-utils'
import { RenderLabel } from '../date-picker-view/date-picker-view'
import { TILL_NOW } from './util'
import type { PickerDate } from './util'
import { TILL_NOW } from './util'

export type Precision = DatePrecision | WeekPrecision
export type Precision = DatePrecision | WeekPrecision | QuarterPrecision

export type DatePickerFilter = Partial<
Record<
Expand Down Expand Up @@ -35,6 +37,8 @@ export const convertDateToStringArray = (
) => {
if (precision.includes('week')) {
return weekUtils.convertDateToStringArray(date)
} else if (precision.includes('quarter')) {
return quarterUtils.convertDateToStringArray(date)
} else {
const datePrecision = precision as DatePrecision
const stringArray = dateUtils.convertDateToStringArray(date)
Expand All @@ -43,7 +47,7 @@ export const convertDateToStringArray = (
}

export const convertStringArrayToDate = <
T extends string | number | null | undefined
T extends string | number | null | undefined,
>(
value: T[],
precision: Precision
Expand All @@ -57,6 +61,8 @@ export const convertStringArrayToDate = <

if (precision.includes('week')) {
return weekUtils.convertStringArrayToDate(value)
} else if (precision.includes('quarter')) {
return quarterUtils.convertStringArrayToDate(value)
} else {
return dateUtils.convertStringArrayToDate(value)
}
Expand All @@ -80,6 +86,15 @@ export const generateDatePickerColumns = (
renderLabel,
filter
)
} else if (precision.startsWith('quarter')) {
return quarterUtils.generateDatePickerColumns(
selected,
min,
max,
precision as QuarterPrecision,
renderLabel,
filter
)
} else {
return dateUtils.generateDatePickerColumns(
selected,
Expand Down
45 changes: 45 additions & 0 deletions src/components/date-picker/demos/demo1.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,47 @@ function DayOfWeekDemo() {
)
}

// 季度选择器
function QuarterDemo() {
const [visible, setVisible] = useState(false)

const labelRenderer = useCallback((type: string, data: number) => {
switch (type) {
case 'year':
return data + '年'
case 'quarter':
return data + '季度'
default:
return data
}
}, [])

return (
<>
<Button
onClick={() => {
setVisible(true)
}}
>
选择
</Button>
<DatePicker
visible={visible}
onClose={() => {
setVisible(false)
}}
defaultValue={now}
onConfirm={val => {
Toast.show(val.toDateString())
}}
onSelect={val => console.log(val)}
renderLabel={labelRenderer}
precision='quarter'
/>
</>
)
}

// 基础用法
function TillNowDemo() {
const [value, setValue] = useState(() => new Date())
Expand Down Expand Up @@ -252,6 +293,10 @@ export default () => {
<DayOfWeekDemo />
</DemoBlock>

<DemoBlock title='季度选择器'>
<QuarterDemo />
</DemoBlock>

<DemoBlock title='至今'>
<TillNowDemo />
</DemoBlock>
Expand Down
23 changes: 23 additions & 0 deletions src/components/date-picker/tests/date-picker.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,29 @@ describe('DatePicker', () => {
expect(fn.mock.calls[0][0]).toEqual(today.toDateString())
})

test('precision quarter', async () => {
const today = new Date()
const fn = jest.fn()
const { getByText } = render(
<DatePicker
visible
precision='quarter'
value={now}
onConfirm={val => {
fn(val.toDateString())
}}
/>
)

expect(
document.body.querySelectorAll(`.${classPrefix}-view-column`).length
).toBe(2)
await waitFor(() => {
fireEvent.click(getByText('确定'))
})
expect(fn.mock.calls[0][0]).toEqual(today.toDateString())
})

test('test imperative call', async () => {
const today = new Date()
const fn = jest.fn()
Expand Down
2 changes: 1 addition & 1 deletion src/components/picker-view/index.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ type PickerDate = Date & {
| min | Minimum value | `PickerDate` | ten years ago |
| mouseWheel | Whether to allow interact with mouse wheel | `boolean` | `false` |
| onChange | Triggered when the options are changed | `(value: PickerDate) => void` | - |
| precision | Precision | `'year' \| 'month' \| 'day' \| 'hour' \| 'minute' \| 'second' \| 'week' \| 'week-day'` | `'day'` |
| precision | Precision | `'year' \| 'month' \| 'day' \| 'hour' \| 'minute' \| 'second' \| 'week' \| 'week-day' \| 'quarter'` | `'day'` |
| renderLabel | The function to custom rendering the label shown on a column. `type` means any value in `precision`, `data` means the default number | `(type: string, data: number) => ReactNode` | - |
| value | Selected options | `PickerDate` | - |
| loading | Should the Picker displays as loading state | `boolean` | `false` |
Expand Down
2 changes: 1 addition & 1 deletion src/components/picker-view/index.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ type PickerDate = Date & {
| min | 最小值 | `PickerDate` | 十年前 |
| mouseWheel | 是否允许通过鼠标滚轮进行选择 | `boolean` | `false` |
| onChange | 选项改变时触发 | `(value: PickerDate) => void` | - |
| precision | 精度 | `'year' \| 'month' \| 'day' \| 'hour' \| 'minute' \| 'second' \| 'week' \| 'week-day'` | `'day'` |
| precision | 精度 | `'year' \| 'month' \| 'day' \| 'hour' \| 'minute' \| 'second' \| 'week' \| 'week-day' \| 'quarter'` | `'day'` |
| renderLabel | 自定义渲染每列展示的内容。其中 `type` 参数为 `precision` 中的任意值,`data` 参数为默认渲染的数字 | `(type: string, data: number) => ReactNode` | - |
| value | 选中项 | `PickerDate` | - |
| loading | 是否处于加载状态 | `boolean` | `false` |
Expand Down
2 changes: 1 addition & 1 deletion src/components/picker/index.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ type PickerDate = Date & {
| mouseWheel | Whether to allow interact with mouse wheel | `boolean` | `false` |
| onConfirm | Triggered when confirming | `(value: PickerDate) => void` | - |
| onSelect | Triggered when the options are changed | `(value: PickerDate) => void` | - |
| precision | Precision | `'year' \| 'month' \| 'day' \| 'hour' \| 'minute' \| 'second' \| 'week' \| 'week-day'` | `'day'` |
| precision | Precision | `'year' \| 'month' \| 'day' \| 'hour' \| 'minute' \| 'second' \| 'week' \| 'week-day' \| 'quarter'` | `'day'` |
| renderLabel | The function to custom rendering the label shown on a column. `type` means any value in `precision` or `now`, `data` means the default number | `(type: Precision \| 'now', data: number) => ReactNode` | - |
| tillNow | Show till now in list | `boolean` | - | 5.27.0 |
| value | Selected value | `PickerDate` | - |
Expand Down
2 changes: 1 addition & 1 deletion src/components/picker/index.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ type PickerDate = Date & {
| mouseWheel | 是否允许通过鼠标滚轮进行选择 | `boolean` | `false` |
| onConfirm | 确认时触发 | `(value: PickerDate) => void` | - |
| onSelect | 选项改变时触发 | `(value: PickerDate) => void` | - |
| precision | 精度 | `'year' \| 'month' \| 'day' \| 'hour' \| 'minute' \| 'second' \| 'week' \| 'week-day'` | `'day'` |
| precision | 精度 | `'year' \| 'month' \| 'day' \| 'hour' \| 'minute' \| 'second' \| 'week' \| 'week-day' \| 'quarter'` | `'day'` |
| renderLabel | 自定义渲染每列展示的内容。其中 `type` 参数为 `precision` 中的任意值或 `now`,`data` 参数为默认渲染的数字 | `(type: Precision \| 'now', data: number) => ReactNode` | - |
| tillNow | 是否展示“至今” | `boolean` | - | 5.27.0 |
| value | 选中值 | `PickerDate` | - |
Expand Down
Loading