diff --git a/src/lib/kit/components/Inputs/TimeRangeSelector/TimeRangeSelector.tsx b/src/lib/kit/components/Inputs/TimeRangeSelector/TimeRangeSelector.tsx new file mode 100644 index 0000000..4c6a3e3 --- /dev/null +++ b/src/lib/kit/components/Inputs/TimeRangeSelector/TimeRangeSelector.tsx @@ -0,0 +1,135 @@ +import React from 'react'; + +import {Text} from '@gravity-ui/uikit'; + +import isString from 'lodash/isString'; +import set from 'lodash/set'; + +import { + FieldValue, + ObjectIndependentInput, + StringSpec, + ValidateError, + isStringSpec, +} from '../../../../core'; +import {END_TIME, START_TIME} from '../../../constants/common'; + +import {TimeRangeSelect} from './components'; +import {filterTimeArray, validateArray} from './utils'; + +export const TimeRangeSelector: ObjectIndependentInput = (props) => { + const {spec, input, name, Layout} = props; + + const [startTimeSpec, endTimeSpec] = React.useMemo( + () => + [START_TIME, END_TIME].map((key) => + isStringSpec(spec.properties?.[key]) + ? (spec.properties?.[key] as StringSpec) + : undefined, + ), + [spec.properties], + ); + + const {initialStartTimeOptions, initialEndTimeOptions, canBeFiltered} = React.useMemo(() => { + const [initialStartTimeOptions, initialEndTimeOptions] = [startTimeSpec, endTimeSpec].map( + (spec) => { + if (spec && spec.enum) { + return spec.enum.map((id) => ({ + id, + value: id, + content: spec.viewSpec.selectParams?.meta?.[id] ? ( +
+ {spec.description?.[id] || id} + + {spec.viewSpec.selectParams.meta[id]} + +
+ ) : ( + spec.description?.[id] || id + ), + key: id, + })); + } + + return undefined; + }, + ); + + const canBeFiltered = + initialStartTimeOptions && + initialEndTimeOptions && + validateArray(initialStartTimeOptions) && + validateArray(initialEndTimeOptions); + + return { + initialStartTimeOptions, + initialEndTimeOptions, + canBeFiltered, + }; + }, [endTimeSpec, startTimeSpec]); + + const {startTimeOptions, endTimeOptions} = React.useMemo(() => { + let startTimeOptions = initialStartTimeOptions; + let endTimeOptions = initialEndTimeOptions; + + [START_TIME, END_TIME].forEach((key) => { + const time = input.value?.[key]; + + if ( + isString(time) && + canBeFiltered && + initialEndTimeOptions && + initialStartTimeOptions + ) { + if (START_TIME === key) { + endTimeOptions = filterTimeArray(initialEndTimeOptions, time, 'greater'); + } else { + startTimeOptions = filterTimeArray(initialStartTimeOptions, time, 'less'); + } + } + }); + + return {startTimeOptions, endTimeOptions}; + }, [canBeFiltered, initialEndTimeOptions, initialStartTimeOptions, input.value]); + + const parentOnChange = React.useCallback( + (childName: string, childValue: FieldValue, childErrors?: Record) => + input.onChange( + (currentValue) => + set({...currentValue}, childName.split(`${name}.`).join(''), childValue), + childErrors, + ), + [input, name], + ); + + if (!startTimeSpec || !endTimeSpec || !startTimeOptions || !endTimeOptions) { + return null; + } + + const content = ( + + parentOnChange(START_TIME, value[0])} + props={props} + /> + parentOnChange(END_TIME, value[0])} + props={props} + /> + + ); + + if (Layout) { + return {content}; + } + + return {content}; +}; diff --git a/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-View-dark-chromium-linux.png b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-View-dark-chromium-linux.png new file mode 100644 index 0000000..ef28a8e Binary files /dev/null and b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-View-dark-chromium-linux.png differ diff --git a/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-View-dark-webkit-linux.png b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-View-dark-webkit-linux.png new file mode 100644 index 0000000..b87678f Binary files /dev/null and b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-View-dark-webkit-linux.png differ diff --git a/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-View-light-chromium-linux.png b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-View-light-chromium-linux.png new file mode 100644 index 0000000..ec03af3 Binary files /dev/null and b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-View-light-chromium-linux.png differ diff --git a/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-View-light-webkit-linux.png b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-View-light-webkit-linux.png new file mode 100644 index 0000000..e770a62 Binary files /dev/null and b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-View-light-webkit-linux.png differ diff --git a/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-default-dark-chromium-linux.png b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-default-dark-chromium-linux.png new file mode 100644 index 0000000..49dedd8 Binary files /dev/null and b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-default-dark-chromium-linux.png differ diff --git a/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-default-dark-webkit-linux.png b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-default-dark-webkit-linux.png new file mode 100644 index 0000000..de73206 Binary files /dev/null and b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-default-dark-webkit-linux.png differ diff --git a/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-default-light-chromium-linux.png b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-default-light-chromium-linux.png new file mode 100644 index 0000000..2fa5dda Binary files /dev/null and b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-default-light-chromium-linux.png differ diff --git a/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-default-light-webkit-linux.png b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-default-light-webkit-linux.png new file mode 100644 index 0000000..1209119 Binary files /dev/null and b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-default-light-webkit-linux.png differ diff --git a/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-default-value-dark-chromium-linux.png b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-default-value-dark-chromium-linux.png new file mode 100644 index 0000000..5669c6f Binary files /dev/null and b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-default-value-dark-chromium-linux.png differ diff --git a/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-default-value-dark-webkit-linux.png b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-default-value-dark-webkit-linux.png new file mode 100644 index 0000000..f559023 Binary files /dev/null and b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-default-value-dark-webkit-linux.png differ diff --git a/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-default-value-light-chromium-linux.png b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-default-value-light-chromium-linux.png new file mode 100644 index 0000000..58e68ea Binary files /dev/null and b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-default-value-light-chromium-linux.png differ diff --git a/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-default-value-light-webkit-linux.png b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-default-value-light-webkit-linux.png new file mode 100644 index 0000000..fd62606 Binary files /dev/null and b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-default-value-light-webkit-linux.png differ diff --git a/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-description-dark-chromium-linux.png b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-description-dark-chromium-linux.png new file mode 100644 index 0000000..9a02e58 Binary files /dev/null and b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-description-dark-chromium-linux.png differ diff --git a/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-description-dark-webkit-linux.png b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-description-dark-webkit-linux.png new file mode 100644 index 0000000..d32e403 Binary files /dev/null and b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-description-dark-webkit-linux.png differ diff --git a/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-description-light-chromium-linux.png b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-description-light-chromium-linux.png new file mode 100644 index 0000000..19653c8 Binary files /dev/null and b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-description-light-chromium-linux.png differ diff --git a/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-description-light-webkit-linux.png b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-description-light-webkit-linux.png new file mode 100644 index 0000000..790aa45 Binary files /dev/null and b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-description-light-webkit-linux.png differ diff --git a/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-required-dark-chromium-linux.png b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-required-dark-chromium-linux.png new file mode 100644 index 0000000..acba38f Binary files /dev/null and b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-required-dark-chromium-linux.png differ diff --git a/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-required-dark-webkit-linux.png b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-required-dark-webkit-linux.png new file mode 100644 index 0000000..b47658a Binary files /dev/null and b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-required-dark-webkit-linux.png differ diff --git a/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-required-light-chromium-linux.png b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-required-light-chromium-linux.png new file mode 100644 index 0000000..ee8c22b Binary files /dev/null and b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-required-light-chromium-linux.png differ diff --git a/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-required-light-webkit-linux.png b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-required-light-webkit-linux.png new file mode 100644 index 0000000..b2ff18b Binary files /dev/null and b/src/lib/kit/components/Inputs/TimeRangeSelector/__snapshots__/TimeRangeSelector.visual.test.tsx-snapshots/TimeRangeSelector-required-light-webkit-linux.png differ diff --git a/src/lib/kit/components/Inputs/TimeRangeSelector/__tests__/TimeRangeSelector.visual.test.tsx b/src/lib/kit/components/Inputs/TimeRangeSelector/__tests__/TimeRangeSelector.visual.test.tsx new file mode 100644 index 0000000..3559b42 --- /dev/null +++ b/src/lib/kit/components/Inputs/TimeRangeSelector/__tests__/TimeRangeSelector.visual.test.tsx @@ -0,0 +1,39 @@ +import React from 'react'; + +import {TIME_RANGE_SELECTOR, VALUE} from './helpers'; + +import {test} from '~playwright/core'; +import {DynamicForm} from '~playwright/core/DynamicForm'; +import {DynamicView} from '~playwright/core/DynamicView'; + +test.describe('TimeRangeSelector', () => { + test('default', async ({mount, expectScreenshot}) => { + await mount(); + + await expectScreenshot(); + }); + + test('default value', async ({mount, expectScreenshot}) => { + await mount(); + + await expectScreenshot(); + }); + + test('required', async ({mount, expectScreenshot}) => { + await mount(); + + await expectScreenshot(); + }); + + test('description', async ({mount, expectScreenshot}) => { + await mount(); + + await expectScreenshot(); + }); +}); + +test('TimeRangeSelector View', async ({mount, expectScreenshot}) => { + await mount(); + + await expectScreenshot(); +}); diff --git a/src/lib/kit/components/Inputs/TimeRangeSelector/__tests__/helpers.ts b/src/lib/kit/components/Inputs/TimeRangeSelector/__tests__/helpers.ts new file mode 100644 index 0000000..ca768aa --- /dev/null +++ b/src/lib/kit/components/Inputs/TimeRangeSelector/__tests__/helpers.ts @@ -0,0 +1,185 @@ +import {FormValue, ObjectSpec, SpecTypes} from '../../../../../core'; + +const ENUM_START = [ + '00:00', + '01:00', + '02:00', + '03:00', + '04:00', + '05:00', + '06:00', + '07:00', + '08:00', + '09:00', + '10:00', + '11:00', + '12:00', + '13:00', + '14:00', + '15:00', + '16:00', + '17:00', + '18:00', + '19:00', + '20:00', + '21:00', + '22:00', +]; + +const ENUM_END = [ + '01:00', + '02:00', + '03:00', + '04:00', + '05:00', + '06:00', + '07:00', + '08:00', + '09:00', + '10:00', + '11:00', + '12:00', + '13:00', + '14:00', + '15:00', + '16:00', + '17:00', + '18:00', + '19:00', + '20:00', + '21:00', + '22:00', + '23:00', +]; + +export const TIME_RANGE_SELECTOR: Record = { + default: { + type: SpecTypes.Object, + properties: { + start: { + enum: ENUM_START, + type: SpecTypes.String, + viewSpec: { + type: 'select', + layout: 'row', + layoutTitle: 'Start of launch', + placeholder: '00:00', + }, + }, + end: { + type: SpecTypes.String, + enum: ENUM_END, + viewSpec: { + type: 'select', + layout: 'row', + layoutTitle: 'End of launch', + placeholder: '01:00', + }, + }, + }, + viewSpec: { + type: 'time_range_selector', + layout: 'transparent', + }, + }, + defaultValue: { + defaultValue: { + start: '03:00', + end: '06:00', + }, + type: SpecTypes.Object, + properties: { + start: { + type: SpecTypes.String, + enum: ENUM_START, + viewSpec: { + type: 'select', + layout: 'row', + layoutTitle: 'Start of launch', + placeholder: '00:00', + }, + }, + end: { + type: SpecTypes.String, + enum: ENUM_END, + viewSpec: { + type: 'select', + layout: 'row', + layoutTitle: 'End of launch', + placeholder: '01:00', + }, + }, + }, + viewSpec: { + type: 'time_range_selector', + layout: 'transparent', + }, + }, + required: { + type: SpecTypes.Object, + properties: { + start: { + required: true, + type: SpecTypes.String, + enum: ENUM_START, + viewSpec: { + type: 'select', + layout: 'row', + layoutTitle: 'Start of launch', + placeholder: '00:00', + }, + }, + end: { + required: true, + type: SpecTypes.String, + enum: ENUM_END, + viewSpec: { + type: 'select', + layout: 'row', + layoutTitle: 'End of launch', + placeholder: '01:00', + }, + }, + }, + viewSpec: { + type: 'time_range_selector', + layout: 'transparent', + }, + }, + desription: { + type: SpecTypes.Object, + properties: { + start: { + type: SpecTypes.String, + enum: ENUM_START, + viewSpec: { + type: 'select', + layout: 'row', + layoutTitle: 'Start of launch', + layoutDescription: 'End of launch Description', + placeholder: '00:00', + }, + }, + end: { + type: SpecTypes.String, + enum: ENUM_END, + viewSpec: { + type: 'select', + layout: 'row', + layoutTitle: 'End of launch', + layoutDescription: 'End of launch Description', + placeholder: '01:00', + }, + }, + }, + viewSpec: { + type: 'time_range_selector', + layout: 'transparent', + }, + }, +}; + +export const VALUE: FormValue = { + start: '10:00', + end: '20:00', +}; diff --git a/src/lib/kit/components/Inputs/TimeRangeSelector/components/TimeRangeSelect/TimeRangeSelect.scss b/src/lib/kit/components/Inputs/TimeRangeSelector/components/TimeRangeSelect/TimeRangeSelect.scss new file mode 100644 index 0000000..cceae73 --- /dev/null +++ b/src/lib/kit/components/Inputs/TimeRangeSelector/components/TimeRangeSelect/TimeRangeSelect.scss @@ -0,0 +1,11 @@ +@import '../../../../../styles/variables.scss'; + +.#{$ns}time-range-select { + &__select { + min-width: 100px; + } + + &__select-popup { + max-height: 250px; + } +} diff --git a/src/lib/kit/components/Inputs/TimeRangeSelector/components/TimeRangeSelect/TimeRangeSelect.tsx b/src/lib/kit/components/Inputs/TimeRangeSelector/components/TimeRangeSelect/TimeRangeSelect.tsx new file mode 100644 index 0000000..61ff9e8 --- /dev/null +++ b/src/lib/kit/components/Inputs/TimeRangeSelector/components/TimeRangeSelect/TimeRangeSelect.tsx @@ -0,0 +1,85 @@ +import React from 'react'; + +import isString from 'lodash/isString'; + +import {Select, type SelectOption} from '@gravity-ui/uikit'; + +import { + FieldObjectValue, + FieldValue, + IndependentInputProps, + ObjectSpec, + StringSpec, +} from '../../../../../../core'; + +import {block} from '../../../../../utils'; + +import {Row} from '../../../../Layouts/Row'; + +import './TimeRangeSelect.scss'; + +const b = block('time-range-select'); + +interface TimeRangeSelectProps { + spec: StringSpec; + name: string; + options: SelectOption[]; + value?: FieldValue; + handleChange: (value: string[]) => void; + props: IndependentInputProps< + FieldObjectValue, + undefined, + undefined, + ObjectSpec + >; +} + +export const TimeRangeSelect: React.FC = ({ + name, + spec, + options, + value, + props, + handleChange, +}) => { + const filterable = React.useMemo(() => (options?.length || 0) > 9, [options?.length]); + + const _value = React.useMemo(() => { + if (isString(value)) { + return [value]; + } + + return undefined; + }, [value]); + + const renderOption = React.useCallback((option: {value: string; content?: React.ReactNode}) => { + return {option.content || option.value}; + }, []); + + const getOptionHeight = React.useCallback(() => { + if (spec.viewSpec.selectParams?.meta) { + return 44; + } + + return 28; + }, [spec.viewSpec.selectParams?.meta]); + + return ( + +