Skip to content

Commit

Permalink
ADM-171; (Feat) DateRangePicker (#104)
Browse files Browse the repository at this point in the history
* ADM-171; (Feat) Add DateRangePicker and DateRangePickerInput

---------

Co-authored-by: Viachaslau Biziukin <[email protected]>
Co-authored-by: Arthur Volokhin <[email protected]>
  • Loading branch information
3 people authored Dec 25, 2023
1 parent d9a1b58 commit 622289c
Show file tree
Hide file tree
Showing 37 changed files with 21,501 additions and 19,550 deletions.
8 changes: 8 additions & 0 deletions admiral/filters/AppliedFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ export const AppliedFilters: React.FC<AppliedFiltersProps> = () => {
return label ? `${label}: ${date}` : date
}

case 'DateRangePickerInput': {
const date = [
format(parseISO(value[0]), 'dd.MM.yyyy'),
format(parseISO(value[1]), 'dd.MM.yyyy'),
]
return label ? `${label}: ${date[0]} - ${date[1]}` : `${date[0]} - ${date[1]}`
}

default:
return null
}
Expand Down
4 changes: 4 additions & 0 deletions admiral/filters/QuickFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
AjaxSelectInput,
BooleanInput,
DatePickerInput,
DateRangePickerInput,
SelectInput,
TextInput,
TimePickerInput,
Expand Down Expand Up @@ -101,6 +102,9 @@ export const QuickFilters: React.FC<QuickFiltersProps> = ({ filters }) => {
case 'DatePickerInput': {
return <DatePickerInput {...inputProps} />
}
case 'DateRangePickerInput': {
return <DateRangePickerInput {...inputProps} />
}
default:
return <></>
}
Expand Down
61 changes: 61 additions & 0 deletions admiral/form/fields/DateRangePickerInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React, { useCallback } from 'react'
import { useForm } from '../FormContext'
import { Form } from '../Form'
import { RangePicker } from '../../ui'
import { PickerRangeProps } from '../../ui/DatePicker/generatePicker/interfaces'
import { FormItemProps } from '../Item'
import parseISO from 'date-fns/parseISO'
import { InputComponentWithName } from '../interfaces'
import { usePopupContainer } from '../../crud/PopupContainerContext'
import { RangeValue } from 'rc-picker/lib/interface'

export type DateRangePickerInputProps = FormItemProps & {
name: string
onChange?: (value: any) => void
showTime?: boolean
} & PickerRangeProps<Date>

export const DateRangePickerInput: InputComponentWithName<React.FC<DateRangePickerInputProps>> = ({
name,
label,
required,
columnSpan,
onChange,
...pickerProps
}) => {
const getPopupContainer = usePopupContainer()

const { values, errors, setValues, locale: formLocale } = useForm()
const locale = formLocale.fields.datePicker

let value = values[name]
? values[name].map((rangeValue: string | Date) =>
typeof rangeValue === 'string' ? parseISO(rangeValue) : rangeValue,
)
: undefined

const error = errors[name]?.[0]

const _onChange = useCallback(
(changeValues: RangeValue<Date>, formatString: [string, string]) => {
setValues((prevValues: any) => ({ ...prevValues, [name]: changeValues }))
onChange?.(changeValues)
},
[onChange],
)

return (
<Form.Item label={label} required={required} error={error} columnSpan={columnSpan}>
<RangePicker
getPopupContainer={getPopupContainer}
locale={locale}
{...pickerProps}
value={value}
onChange={_onChange}
alert={!!error}
/>
</Form.Item>
)
}

DateRangePickerInput.inputName = 'DateRangePickerInput'
1 change: 1 addition & 0 deletions admiral/form/fields/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * from './ArrayInput'
export * from './BooleanInput'
export * from './ColorPickerInput'
export * from './DatePickerInput'
export * from './DateRangePickerInput'
export * from './DraggerInput'
export * from './EditorInput'
export * from './FilePictureInput'
Expand Down
1 change: 1 addition & 0 deletions admiral/form/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const INPUT_NAMES = {
boolean: 'BooleanInput',
colorPicker: 'ColorPickerInput',
datePicker: 'DatePickerInput',
dateRangePicker: 'DateRangePickerInput',
dragger: 'DraggerInput',
editor: 'EditorInput',
filePicture: 'FilePictureInput',
Expand Down
2 changes: 2 additions & 0 deletions admiral/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ export type {
TimePickerLocale,
PickerLocale,
PickerDateProps,
PickerRangeProps,
PickerProps,
PickerRangeValue,
} from './ui/DatePicker/generatePicker/interfaces'
export type { DrawerProps } from './ui/Drawer/interfaces'
export type { InputProps, InputSizeType } from './ui/Input/interfaces'
Expand Down
7 changes: 6 additions & 1 deletion admiral/ui/DatePicker/DatePicker.scss
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ $font-size-xs: var(--control-text-size-xs);
background: var(--color-control-bg-default);
border: var(--picker-border-width) solid var(--color-control-bg-border-default);
border-radius: var(--control-radius);
transition: all 0.2s;
// transition: all 0.2s;

&__SizeL {
--picker-padding-horizontal: #{$padding-horizontal-l};
Expand Down Expand Up @@ -239,6 +239,10 @@ $font-size-xs: var(--control-text-size-xs);
}
}

&-range-wrapper {
min-width: initial !important;
}

&-panel-container {
overflow-y: hidden;
overflow-x: auto;
Expand All @@ -249,6 +253,7 @@ $font-size-xs: var(--control-text-size-xs);
border-radius: var(--control-radius);
outline: none;
box-shadow: var(--shadow-layer);
margin: 0 !important;

.#{$prefix-class}-panel {
vertical-align: top;
Expand Down
10 changes: 10 additions & 0 deletions admiral/ui/DatePicker/generatePicker/generateRangePicker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { GenerateConfig } from 'rc-picker/lib/generate/index'
import '../DatePicker.scss'
import generateSingleRangePicker from './generateSingleRangePicker'

function generateRangePicker<DateType>(generateConfig: GenerateConfig<DateType>) {
const { DateRangePicker } = generateSingleRangePicker(generateConfig)
return DateRangePicker
}

export default generateRangePicker
9 changes: 7 additions & 2 deletions admiral/ui/DatePicker/generatePicker/generateSinglePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ import { PickerMode } from 'rc-picker/lib/interface'
import { GenerateConfig } from 'rc-picker/lib/generate/index'
import enUs from '../locale/en_US'
import { getPlaceholder } from '../util'
import { PickerProps, getTimeProps, PickerComponentClass } from './interfaces'
import { PickerProps, PickerComponentClass } from './interfaces'
import PickerButton from '../PickerButton'
import PickerTag from '../PickerTag'
import { getTimeProps } from './getTimeProps'

const defaultLocale = enUs

Expand Down Expand Up @@ -84,7 +85,11 @@ export default function generatePicker<DateType>(generateConfig: GenerateConfig<
return (
<RCPicker<DateType>
ref={this.pickerRef}
placeholder={getPlaceholder(mergedPicker, pickerLocale, placeholder)}
placeholder={getPlaceholder(
mergedPicker,
pickerLocale,
placeholder as string,
)}
suffixIcon={mergedPicker === 'time' ? <FiClock /> : <FiCalendar />}
clearIcon={<AiFillCloseCircle />}
prevIcon={
Expand Down
163 changes: 163 additions & 0 deletions admiral/ui/DatePicker/generatePicker/generateSingleRangePicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import React from 'react'
import cn from 'classnames'
import { Button } from '../..'
import {
FiCalendar,
FiClock,
FiChevronLeft,
FiChevronRight,
FiChevronsLeft,
FiChevronsRight,
} from 'react-icons/fi'
import { AiFillCloseCircle } from 'react-icons/ai'
import { RangePicker } from 'rc-picker'
import { PickerMode } from 'rc-picker/lib/interface'
import { GenerateConfig } from 'rc-picker/lib/generate/index'
import enUs from '../locale/en_US'
import { getRangeTimeProps } from './getRangeTimeProps'
import PickerButton from '../PickerButton'
import PickerTag from '../PickerTag'
import { PickerComponentClass, PickerRangeProps } from './interfaces'

const defaultLocale = enUs

export default function generateSingleRangePicker<DateType>(
generateConfig: GenerateConfig<DateType>,
) {
type DateRangePickerProps = PickerRangeProps<DateType>

function getPicker<InnerPickerProps extends DateRangePickerProps>(
picker?: PickerMode,
displayName?: string,
) {
class Picker extends React.Component<InnerPickerProps> {
static displayName: string
pickerRef = React.createRef<RangePicker<DateType>>()

focus = () => {
if (this.pickerRef.current) {
this.pickerRef.current.focus()
}
}

blur = () => {
if (this.pickerRef.current) {
this.pickerRef.current.blur()
}
}

renderPicker = () => {
const {
getPopupContainer: customizeGetPopupContainer,
className,
size,
borderless = false,
alert,
locale,
separator = '',
...restProps
} = this.props
const pickerLocale = { ...defaultLocale, ...locale }
const { format, showTime } = this.props as any
const prefixCls = cn('admiral-picker')
const getPopupContainer = () =>
document.querySelector('#root > .Theme') as HTMLDivElement

const additionalProps = {
showToday: true,
}

let additionalOverrideProps: any = {}
if (picker) {
additionalOverrideProps.picker = picker
}
const mergedPicker = picker || this.props.picker

additionalOverrideProps = {
...additionalOverrideProps,
...(showTime
? getRangeTimeProps({ format, picker: mergedPicker, ...showTime })
: {}),
...(mergedPicker === 'time'
? getRangeTimeProps({ format, ...this.props, picker: mergedPicker })
: {}),
}

return (
<RangePicker<DateType>
ref={this.pickerRef}
placeholder={this.props.placeholder || ['from', 'to']}
suffixIcon={mergedPicker === 'time' ? <FiClock /> : <FiCalendar />}
clearIcon={<AiFillCloseCircle />}
separator={separator}
prevIcon={
<Button
component="span"
view="clear"
size="S"
iconLeft={<FiChevronLeft />}
/>
}
nextIcon={
<Button
component="span"
view="clear"
size="S"
iconLeft={<FiChevronRight />}
/>
}
superPrevIcon={
<Button
component="span"
view="clear"
size="S"
iconLeft={<FiChevronsLeft />}
/>
}
superNextIcon={
<Button
component="span"
view="clear"
size="S"
iconLeft={<FiChevronsRight />}
/>
}
allowClear
// transitionName="admiral-picker-dropdown-slide-up"
{...additionalProps}
{...restProps}
{...additionalOverrideProps}
locale={pickerLocale.lang}
className={cn(
{
[`${prefixCls}__SizeL`]: size === 'L',
[`${prefixCls}__SizeS`]: size === 'S',
[`${prefixCls}__SizeXS`]: size === 'XS',
[`${prefixCls}__Alert`]: alert,
[`${prefixCls}__Borderless`]: borderless,
},
className,
)}
prefixCls={prefixCls}
getPopupContainer={customizeGetPopupContainer || getPopupContainer}
generateConfig={generateConfig}
components={{ button: PickerButton, rangeItem: PickerTag }}
/>
)
}

render() {
return <>{this.renderPicker()}</>
}
}

if (displayName) {
Picker.displayName = displayName
}

return Picker as PickerComponentClass<InnerPickerProps>
}

const DateRangePicker = getPicker<DateRangePickerProps>()
return { DateRangePicker }
}
44 changes: 44 additions & 0 deletions admiral/ui/DatePicker/generatePicker/getRangeTimeProps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { PickerMode } from 'rc-picker/lib/interface'
import { SharedTimeProps } from 'rc-picker/lib/panels/TimePanel'
import { toArray } from './interfaces'

export function getRangeTimeProps<DateType>(
props: {
format?: string
picker?: PickerMode
} & Omit<SharedTimeProps<DateType>, 'defaultValue' | 'disabledTime'>,
) {
const { format, picker, showHour, showMinute, showSecond, use12Hours } = props

const firstFormat = toArray(format)[0]
const showTimeObj: SharedTimeProps<DateType> = { ...props }

if (firstFormat && typeof firstFormat === 'string') {
if (!firstFormat.includes('s') && showSecond === undefined) {
showTimeObj.showSecond = false
}
if (!firstFormat.includes('m') && showMinute === undefined) {
showTimeObj.showMinute = false
}
if (!firstFormat.includes('H') && !firstFormat.includes('h') && showHour === undefined) {
showTimeObj.showHour = false
}

if ((firstFormat.includes('a') || firstFormat.includes('A')) && use12Hours === undefined) {
showTimeObj.use12Hours = true
}
}

if (picker === 'time') {
return showTimeObj
}

if (typeof firstFormat === 'function') {
// format of showTime should use default when format is custom format function
delete showTimeObj.format
}

return {
showTime: showTimeObj,
}
}
Loading

0 comments on commit 622289c

Please sign in to comment.