@vtex/admin-ui
Version:
> VTEX admin component library
194 lines (171 loc) • 4.56 kB
text/typescript
import { useEffect, useMemo, useRef } from 'react'
import { useCompositeState } from 'reakit/Composite'
import type { AnyObject } from '@vtex/admin-ui-util'
import { get } from '@vtex/admin-ui-util'
import { useControllableState } from '@vtex/admin-ui-hooks'
import { useDateFormatter } from '../i18n'
import { add, getSegmentLimits, setSegment as setSegmentValue } from './util'
const EDITABLE_SEGMENTS = {
year: true,
month: true,
day: true,
hour: true,
minute: true,
second: true,
dayPeriod: true,
}
const PAGE_STEP = {
year: 5,
month: 2,
day: 7,
hour: 2,
minute: 15,
second: 15,
}
const RESET_PlACEHOLDER = {
day: false,
month: false,
year: false,
}
const DISPLAY_PlACEHOLDER = {
day: true,
month: true,
year: true,
}
const TYPO_MAPPING = {
dayperiod: 'dayPeriod',
}
export function useSegmentState(props: SegmentInitialState = {}) {
const {
value,
onChange,
defaultValue = new Date(),
formatOptions,
placeholder,
} = props
const segmentComposite = useCompositeState({ orientation: 'horizontal' })
const validSegments = useRef<AnyObject>(EDITABLE_SEGMENTS)
const dateFormatter = useDateFormatter(formatOptions)
const showPlaceholder = useRef<AnyObject>(
placeholder ? DISPLAY_PlACEHOLDER : RESET_PlACEHOLDER
)
const resolvedOptions = useMemo(
() => dateFormatter.resolvedOptions(),
[dateFormatter]
)
const numSegments = useMemo(
() =>
dateFormatter
.formatToParts(new Date())
.filter((seg) => get(EDITABLE_SEGMENTS, seg.type)).length,
[dateFormatter]
)
const [date, setDate] = useControllableState<Date>({
value,
defaultValue,
onChange,
})
const segments = useMemo(
() =>
dateFormatter.formatToParts(date).map(
(segment) =>
({
type: get(TYPO_MAPPING, segment.type) || segment.type,
text: segment.value,
placeholder: get(placeholder, segment.type),
...getSegmentLimits(date, segment.type, resolvedOptions),
} as DateSegment)
),
[resolvedOptions, dateFormatter, placeholder, date]
)
useEffect(() => {
if (value && Object.keys(validSegments.current).length < numSegments) {
validSegments.current = EDITABLE_SEGMENTS
}
}, [value, numSegments])
const adjustSegment = (
type: Intl.DateTimeFormatPartTypes,
amount: number
) => {
validSegments.current[type] = true
showPlaceholder.current[type] = false
setDate(add(date, type, amount, resolvedOptions) as Date)
}
const resetPlaceholder = () => {
showPlaceholder.current = RESET_PlACEHOLDER
}
const displayPlaceholder = () => {
showPlaceholder.current = DISPLAY_PlACEHOLDER
}
return {
...segmentComposite,
showPlaceholder,
fieldValue: date,
setFieldValue: setDate,
segments,
dateFormatter,
resetPlaceholder,
displayPlaceholder,
increment(part: Intl.DateTimeFormatPartTypes) {
adjustSegment(part, 1)
},
decrement(part: Intl.DateTimeFormatPartTypes) {
adjustSegment(part, -1)
},
incrementPage(part: Intl.DateTimeFormatPartTypes) {
adjustSegment(part, get(PAGE_STEP, part) || 1)
},
decrementPage(part: Intl.DateTimeFormatPartTypes) {
adjustSegment(part, -(get(PAGE_STEP, part) || 1))
},
setSegment(part: Intl.DateTimeFormatPartTypes, v: number) {
validSegments.current[part] = true
showPlaceholder.current[part] = false
setDate(setSegmentValue(date, part, v, resolvedOptions))
},
}
}
export interface ControllableState<T> {
/**
* value for uncontrolled mode
*/
defaultValue?: T
/**
* value for controlled mode
*/
value?: T
/**
* onChange for controlled mode
*/
onChange?: (value: T, ...args: any[]) => void
}
export interface SegmentInitialState extends ControllableState<Date> {
/**
* Sets formmating of date based on Intl.DateFormatOptions
*
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat
*
* @example
* {
* year: "numeric",
* month: "2-digit",
* day: "2-digit",
* weekday: "long",
* }
*/
formatOptions?: Intl.DateTimeFormatOptions
placeholder?: {
year: string
month: string
day: string
}
}
export interface DateSegment {
type: Intl.DateTimeFormatPartTypes
text: string
value?: number
minValue?: number
maxValue?: number
placeholder?: string
}
export type SegmentStateReturn = ReturnType<typeof useSegmentState>