Skip to content

Commit

Permalink
feat: support quarters, show callendar according to format (#164)
Browse files Browse the repository at this point in the history
  • Loading branch information
ValeraS authored Jan 27, 2025
1 parent 54450d6 commit 58ff5d8
Show file tree
Hide file tree
Showing 22 changed files with 285 additions and 119 deletions.
36 changes: 18 additions & 18 deletions src/components/DateField/hooks/useBaseDateFieldState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type {DateTime} from '@gravity-ui/date-utils';

import type {ValidationState} from '../../types';
import {createPlaceholderValue} from '../../utils/dates';
import type {DateFieldSection, DateFieldSectionType} from '../types';
import type {DateFieldSection, DateFieldSectionType, FormatInfo} from '../types';
import {
EDITABLE_SEGMENTS,
formatSections,
Expand All @@ -14,6 +14,7 @@ import {

const PAGE_STEP: Partial<Record<DateFieldSectionType, number>> = {
year: 5,
quarter: 2,
month: 2,
weekday: 3,
day: 7,
Expand All @@ -29,6 +30,7 @@ export type BaseDateFieldStateOptions<T = DateTime> = {
timeZone: string;
validationState?: ValidationState;
editableSections: DateFieldSection[];
formatInfo: FormatInfo;
readOnly?: boolean;
disabled?: boolean;
selectedSectionIndexes: {startIndex: number; endIndex: number} | null;
Expand Down Expand Up @@ -66,9 +68,17 @@ export type DateFieldState<T = DateTime> = {
disabled?: boolean;
/** A list of segments for the current value. */
sections: DateFieldSection[];
/** Whether the the format is containing date parts */
/** Some info about available sections */
formatInfo: FormatInfo;
/**
* @deprecated use formatInfo.hasDate instead.
* Whether the the format is containing date parts
*/
hasDate: boolean;
/** Whether the the format is containing time parts */
/**
* @deprecated use formatInfo.hasTime instead.
* Whether the the format is containing time parts
*/
hasTime: boolean;
/** Selected sections */
selectedSectionIndexes: {startIndex: number; endIndex: number} | null;
Expand Down Expand Up @@ -122,6 +132,7 @@ export function useBaseDateFieldState<T = DateTime>(
validationState,
displayValue,
editableSections,
formatInfo,
selectedSectionIndexes,
selectedSections,
isEmpty,
Expand All @@ -140,19 +151,6 @@ export function useBaseDateFieldState<T = DateTime>(

const enteredKeys = React.useRef('');

const {hasDate, hasTime} = React.useMemo(() => {
let hasDateInner = false;
let hasTimeInner = false;
for (const s of editableSections) {
hasTimeInner ||= ['hour', 'minute', 'second'].includes(s.type);
hasDateInner ||= ['day', 'month', 'year'].includes(s.type);
}
return {
hasTime: hasTimeInner,
hasDate: hasDateInner,
};
}, [editableSections]);

return {
value,
isEmpty,
Expand All @@ -163,8 +161,9 @@ export function useBaseDateFieldState<T = DateTime>(
readOnly: props.readOnly,
disabled: props.disabled,
sections: editableSections,
hasDate,
hasTime,
formatInfo,
hasDate: formatInfo.hasDate,
hasTime: formatInfo.hasTime,
selectedSectionIndexes,
validationState,
setSelectedSections(position) {
Expand Down Expand Up @@ -425,6 +424,7 @@ export function useBaseDateFieldState<T = DateTime>(
case 'hour':
case 'minute':
case 'second':
case 'quarter':
case 'year': {
if (!Number.isInteger(Number(newValue))) {
return;
Expand Down
34 changes: 15 additions & 19 deletions src/components/DateField/hooks/useDateFieldState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,18 @@ import {useControlledState} from '@gravity-ui/uikit';
import type {DateFieldBase} from '../../types/datePicker';
import {createPlaceholderValue, isInvalid} from '../../utils/dates';
import {useDefaultTimeZone} from '../../utils/useDefaultTimeZone';
import type {DateFieldSectionType, DateFieldSectionWithoutPosition} from '../types';
import type {
AvailableSections,
DateFieldSectionType,
DateFieldSectionWithoutPosition,
} from '../types';
import {
EDITABLE_SEGMENTS,
addSegment,
adjustDateToFormat,
getEditableSections,
getFormatInfo,
isAllSegmentsValid,
markValidSection,
parseDateFromString,
setSegment,
useFormatSections,
Expand Down Expand Up @@ -47,15 +53,10 @@ export function useDateFieldState(props: DateFieldStateOptions): DateFieldState

const format = props.format || 'L';
const sections = useFormatSections(format);
const allSegments: typeof EDITABLE_SEGMENTS = React.useMemo(
() =>
sections
.filter((seg) => EDITABLE_SEGMENTS[seg.type])
.reduce<typeof EDITABLE_SEGMENTS>((p, seg) => ({...p, [seg.type]: true}), {}),
[sections],
);
const formatInfo = React.useMemo(() => getFormatInfo(sections), [sections]);
const allSegments = formatInfo.availableUnits;

const validSegmentsState = React.useState<typeof EDITABLE_SEGMENTS>(() =>
const validSegmentsState = React.useState<AvailableSections>(() =>
value ? {...allSegments} : {},
);

Expand Down Expand Up @@ -127,7 +128,7 @@ export function useDateFieldState(props: DateFieldStateOptions): DateFieldState

if (isAllSegmentsValid(allSegments, validSegments)) {
if (!value || !newValue.isSame(value)) {
handleUpdateDate(newValue);
handleUpdateDate(adjustDateToFormat(newValue, formatInfo));
}
} else {
if (value) {
Expand All @@ -138,13 +139,7 @@ export function useDateFieldState(props: DateFieldStateOptions): DateFieldState
}

function markValid(part: DateFieldSectionType) {
validSegments[part] = true;
if (validSegments.day && validSegments.month && validSegments.year && allSegments.weekday) {
validSegments.weekday = true;
}
if (validSegments.hour && allSegments.dayPeriod) {
validSegments.dayPeriod = true;
}
validSegments = markValidSection(allSegments, validSegments, part);
setValidSegments({...validSegments});
}

Expand Down Expand Up @@ -219,6 +214,7 @@ export function useDateFieldState(props: DateFieldStateOptions): DateFieldState
timeZone,
validationState,
editableSections: sectionsState.editableSections,
formatInfo,
readOnly: props.readOnly,
disabled: props.disabled,
selectedSectionIndexes,
Expand All @@ -241,7 +237,7 @@ export function useDateFieldState(props: DateFieldStateOptions): DateFieldState
function useSectionsState(
sections: DateFieldSectionWithoutPosition[],
value: DateTime,
validSegments: typeof EDITABLE_SEGMENTS,
validSegments: AvailableSections,
) {
const [state, setState] = React.useState(() => {
return {
Expand Down
1 change: 1 addition & 0 deletions src/components/DateField/i18n/en.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"year_placeholder": "Y",
"quarter_placeholder": "Q",
"month_placeholder": "M",
"weekday_placeholder": "E",
"day_placeholder": "D",
Expand Down
1 change: 1 addition & 0 deletions src/components/DateField/i18n/ru.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"year_placeholder": "Г",
"quarter_placeholder": "K",
"month_placeholder": "М",
"weekday_placeholder": "ДН",
"day_placeholder": "Д",
Expand Down
40 changes: 26 additions & 14 deletions src/components/DateField/types.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
export type DateFieldSectionType = Extract<
Intl.DateTimeFormatPartTypes,
| 'day'
| 'dayPeriod'
| 'hour'
| 'literal'
| 'minute'
| 'month'
| 'second'
| 'timeZoneName'
| 'weekday'
| 'year'
| 'unknown'
>;
export type DateFieldSectionType =
| Extract<
Intl.DateTimeFormatPartTypes,
| 'day'
| 'dayPeriod'
| 'hour'
| 'literal'
| 'minute'
| 'month'
| 'second'
| 'timeZoneName'
| 'weekday'
| 'year'
| 'unknown'
>
| 'quarter';

export type DateFormatTokenMap = {
[formatToken: string]:
Expand Down Expand Up @@ -91,3 +93,13 @@ export type DateFieldSectionWithoutPosition<TSection extends DateFieldSection =
| 'previousEditableSection'
| 'nextEditableSection'
>;

export type AvailableSections = Partial<Record<DateFieldSectionType, boolean>>;

export interface FormatInfo {
hasTime: boolean;
hasDate: boolean;
availableUnits: AvailableSections;
minDateUnit: 'day' | 'month' | 'quarter' | 'year';
minTimeUnit: 'second' | 'minute' | 'hour';
}
Loading

0 comments on commit 58ff5d8

Please sign in to comment.