From 5c649c16325bea07a6adf5493b9a04bf324f8db0 Mon Sep 17 00:00:00 2001 From: Krivonos Aleksandr Date: Fri, 31 May 2024 16:23:30 +0300 Subject: [PATCH] feat(plasma-new-hope): add DatePicker --- packages/plasma-new-hope/package-lock.json | 11 + packages/plasma-new-hope/package.json | 3 +- .../src/components/Calendar/hooks/useDays.ts | 2 +- .../src/components/Combobox/Combobox.tsx | 3 +- .../DatePicker/DatePicker.tokens.ts | 137 ++++++++++++ .../SingleDate/DatePicker.styles.ts | 148 +++++++++++++ .../DatePicker/SingleDate/DatePicker.tsx | 196 ++++++++++++++++++ .../DatePicker/SingleDate/DatePicker.types.ts | 138 ++++++++++++ .../SingleDate/variations/_disabled/base.ts | 10 + .../variations/_disabled/tokens.json | 4 + .../SingleDate/variations/_readonly/base.ts | 19 ++ .../variations/_readonly/tokens.json | 4 + .../SingleDate/variations/_size/base.ts | 28 +++ .../SingleDate/variations/_size/tokens.json | 16 ++ .../SingleDate/variations/_view/base.ts | 15 ++ .../SingleDate/variations/_view/tokens.json | 1 + .../src/components/DatePicker/index.ts | 3 + .../components/DatePicker/utils/dateHelper.ts | 45 ++++ .../src/components/Dropdown/Dropdown.tsx | 4 +- .../src/components/Dropdown/utils/index.tsx | 19 +- .../src/components/Select/Select.tsx | 3 +- .../DatePicker/DatePicker.config.ts | 130 ++++++++++++ .../DatePicker/DatePicker.stories.tsx | 104 ++++++++++ .../components/DatePicker/DatePicker.ts | 7 + packages/plasma-new-hope/src/index.ts | 1 + packages/plasma-new-hope/src/utils/datejs.ts | 6 + .../src/utils/getPopoverPlacement.ts | 20 ++ packages/plasma-new-hope/src/utils/index.ts | 1 + 28 files changed, 1052 insertions(+), 26 deletions(-) create mode 100644 packages/plasma-new-hope/src/components/DatePicker/DatePicker.tokens.ts create mode 100644 packages/plasma-new-hope/src/components/DatePicker/SingleDate/DatePicker.styles.ts create mode 100644 packages/plasma-new-hope/src/components/DatePicker/SingleDate/DatePicker.tsx create mode 100644 packages/plasma-new-hope/src/components/DatePicker/SingleDate/DatePicker.types.ts create mode 100644 packages/plasma-new-hope/src/components/DatePicker/SingleDate/variations/_disabled/base.ts create mode 100644 packages/plasma-new-hope/src/components/DatePicker/SingleDate/variations/_disabled/tokens.json create mode 100644 packages/plasma-new-hope/src/components/DatePicker/SingleDate/variations/_readonly/base.ts create mode 100644 packages/plasma-new-hope/src/components/DatePicker/SingleDate/variations/_readonly/tokens.json create mode 100644 packages/plasma-new-hope/src/components/DatePicker/SingleDate/variations/_size/base.ts create mode 100644 packages/plasma-new-hope/src/components/DatePicker/SingleDate/variations/_size/tokens.json create mode 100644 packages/plasma-new-hope/src/components/DatePicker/SingleDate/variations/_view/base.ts create mode 100644 packages/plasma-new-hope/src/components/DatePicker/SingleDate/variations/_view/tokens.json create mode 100644 packages/plasma-new-hope/src/components/DatePicker/index.ts create mode 100644 packages/plasma-new-hope/src/components/DatePicker/utils/dateHelper.ts create mode 100644 packages/plasma-new-hope/src/examples/plasma_b2c/components/DatePicker/DatePicker.config.ts create mode 100644 packages/plasma-new-hope/src/examples/plasma_b2c/components/DatePicker/DatePicker.stories.tsx create mode 100644 packages/plasma-new-hope/src/examples/plasma_b2c/components/DatePicker/DatePicker.ts create mode 100644 packages/plasma-new-hope/src/utils/datejs.ts create mode 100644 packages/plasma-new-hope/src/utils/getPopoverPlacement.ts diff --git a/packages/plasma-new-hope/package-lock.json b/packages/plasma-new-hope/package-lock.json index 49ba48ab12..cbe1ae1512 100644 --- a/packages/plasma-new-hope/package-lock.json +++ b/packages/plasma-new-hope/package-lock.json @@ -13,6 +13,7 @@ "@linaria/react": "5.0.3", "@popperjs/core": "2.11.8", "@salutejs/plasma-core": "1.160.0-dev.0", + "dayjs": "1.11.11", "focus-visible": "5.2.0", "react-draggable": "4.4.3", "react-popper": "2.3.0", @@ -7744,6 +7745,11 @@ "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", "dev": true }, + "node_modules/dayjs": { + "version": "1.11.11", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz", + "integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -20047,6 +20053,11 @@ "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", "dev": true }, + "dayjs": { + "version": "1.11.11", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz", + "integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==" + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", diff --git a/packages/plasma-new-hope/package.json b/packages/plasma-new-hope/package.json index 6d5b3aeb60..1456f84c0a 100644 --- a/packages/plasma-new-hope/package.json +++ b/packages/plasma-new-hope/package.json @@ -98,9 +98,10 @@ "@linaria/react": "5.0.3", "@popperjs/core": "2.11.8", "@salutejs/plasma-core": "1.160.0-dev.0", + "dayjs": "1.11.11", "focus-visible": "5.2.0", "react-draggable": "4.4.3", "react-popper": "2.3.0", "storeon": "3.1.5" } -} \ No newline at end of file +} diff --git a/packages/plasma-new-hope/src/components/Calendar/hooks/useDays.ts b/packages/plasma-new-hope/src/components/Calendar/hooks/useDays.ts index 543213661b..fe0d5fddf5 100644 --- a/packages/plasma-new-hope/src/components/Calendar/hooks/useDays.ts +++ b/packages/plasma-new-hope/src/components/Calendar/hooks/useDays.ts @@ -272,4 +272,4 @@ export const useDays = ( } return getMatrix(days); - }, [date, value, eventList, disabledList, max, min]); + }, [date, value, eventList, disabledList, max, min, includeEdgeDates]); diff --git a/packages/plasma-new-hope/src/components/Combobox/Combobox.tsx b/packages/plasma-new-hope/src/components/Combobox/Combobox.tsx index 2cf472fba6..5faa89e50a 100644 --- a/packages/plasma-new-hope/src/components/Combobox/Combobox.tsx +++ b/packages/plasma-new-hope/src/components/Combobox/Combobox.tsx @@ -2,8 +2,7 @@ import React, { Children, forwardRef, useEffect, useRef, useState } from 'react' import { safeUseId, useForkRef } from '@salutejs/plasma-core'; import { RootProps } from '../../engines'; -import { cx } from '../../utils'; -import { getPlacements } from '../Dropdown/utils'; +import { cx, getPlacements } from '../../utils'; import { getChildren, getNewSelected, getValues } from '../Select/utils'; import { useKeyNavigation } from '../Select/hooks'; import { useDidMountEffect, useForceUpdate } from '../../hooks'; diff --git a/packages/plasma-new-hope/src/components/DatePicker/DatePicker.tokens.ts b/packages/plasma-new-hope/src/components/DatePicker/DatePicker.tokens.ts new file mode 100644 index 0000000000..e7daea0957 --- /dev/null +++ b/packages/plasma-new-hope/src/components/DatePicker/DatePicker.tokens.ts @@ -0,0 +1,137 @@ +export const classes = { + datePickerError: 'date-picker-error', + datePickerSuccess: 'date-picker-success', +}; + +export const tokens = { + /** Токены лейбла */ + labelColor: '--plasma-date-picker__label-color', + labelColorReadOnly: '--plasma-date-picker__label-color-readonly', + labelOffset: '--plasma-date-picker__label-offset', + + labelFontFamily: '--plasma-date-picker__label-font-family', + labelFontStyle: '--plasma-date-picker__label-font-style', + labelFontSize: '--plasma-date-picker__label-font-size', + labelFontWeight: '--plasma-date-picker__label-font-weight', + labelLetterSpacing: '--plasma-date-picker__label-letter-spacing', + labelLineHeight: '--plasma-date-picker__label-line-height', + + /** Токены вспомогательного текста */ + leftHelperColor: '--plasma-date-picker__left-helper-color', + leftHelperColorReadOnly: '--plasma-date-picker__left-helper-color-readonly', + leftHelperOffset: '--plasma-date-picker__left-helper-offset', + + leftHelperFontFamily: '--plasma-date-picker__left-helper-font-family', + leftHelperFontStyle: '--plasma-date-picker__left-helper-font-style', + leftHelperFontSize: '--plasma-date-picker__left-helper-font-size', + leftHelperFontWeight: '--plasma-date-picker__left-helper-font-weight', + leftHelperLetterSpacing: '--plasma-date-picker__left-helper-letter-spacing', + leftHelperLineHeight: '--plasma-date-picker__left-helper-line-height', + + /** Прозрачность для всего компонента в состоянии disabled */ + disabledOpacity: '--plasma-date-picker-disabled-opacity', + + /** Цвет обводки поля ввода при фокусе */ + focusColor: '--plasma-date-picker-focus-color', + + /** Токены полей ввода */ + textFieldColor: '--plasma-date-picker-textfield-color', + textFieldFocusColor: '--plasma-date-picker-textfield-focus-color', + textFieldPlaceholderColor: '--plasma-date-picker-textfield-placeholder-color', + textFieldCaretColor: '--plasma-date-picker-textfield-caret-color', + + textFieldBackgroundColor: '--plasma-date-picker-textfield-background-color', + textFieldBackgroundColorHover: '--plasma-date-picker-textfield-background-color-hover', + textFieldBackgroundColorFocus: '--plasma-date-picker-textfield-background-color-focus', + textFieldBackgroundErrorColor: '--plasma-date-picker-textfield-background-color-error', + textFieldBackgroundErrorColorHover: '--plasma-date-picker-textfield-background-color-error-hover', + textFieldBackgroundErrorColorFocus: '--plasma-date-picker-textfield-background-color-error-focus', + textFieldBackgroundSuccessColor: '--plasma-date-picker-textfield-background-color-success', + textFieldBackgroundSuccessColorHover: '--plasma-date-picker-textfield-background-color-success-hover', + textFieldBackgroundSuccessColorFocus: '--plasma-date-picker-textfield-background-color-success-focus', + + textFieldBorderColor: '--plasma-date-picker-textfield-border-color', + textFieldBorderColorHover: '--plasma-date-picker-textfield-border-color-hover', + textFieldBorderColorFocus: '--plasma-date-picker-textfield-border-color-focus', + textFieldBorderColorError: '--plasma-date-picker-textfield-border-color-error', + textFieldBorderColorErrorHover: '--plasma-date-picker-textfield-border-color-error-hover', + textFieldBorderColorErrorFocus: '--plasma-date-picker-textfield-border-color-error-focus', + textFieldBorderColorSuccess: '--plasma-date-picker-textfield-border-color-success', + textFieldBorderColorSuccessHover: '--plasma-date-picker-textfield-border-color-success-hover', + textFieldBorderColorSuccessFocus: '--plasma-date-picker-textfield-border-color-success-focus', + + textFieldColorReadOnly: '--plasma-date-picker-textfield-color-readonly', + textFieldBackgroundColorReadOnly: '--plasma-date-picker-textfield-background-color-readonly', + textFieldBorderColorReadOnly: '--plasma-date-picker-textfield-border-color-readonly', + textFieldPlaceholderColorReadOnly: '--plasma-date-picker-textfield-placeholder-color-readonly', + + textFieldHeight: '--plasma-date-picker-textfield-height', + textFieldBorderWidth: '--plasma-date-picker-textfield-border-width', + textFieldBorderRadius: '--plasma-date-picker-textfield-border-radius', + textFieldPadding: '--plasma-date-picker-textfield-padding', + textFieldLeftContentMargin: '--plasma-date-picker-textfield__left-content-margin', + textFieldRightContentMargin: '--plasma-date-picker-textfield__right-content-margin', + textFieldFontFamily: '--plasma-date-picker-textfield-font-family', + textFieldFontStyle: '--plasma-date-picker-textfield-font-style', + textFieldFontSize: '--plasma-date-picker-textfield-font-size', + textFieldFontWeight: '--plasma-date-picker-textfield-font-weight', + textFieldLetterSpacing: '--plasma-date-picker-textfield-letter-spacing', + textFieldLineHeight: '--plasma-date-picker-textfield-line-height', + + textFieldTextBeforeColor: '--plasma-date-picker-textfield__before-text-color', + textFieldTextAfterColor: '--plasma-date-picker-textfield__after-text-color', + textFieldTextBeforeMargin: '--plasma-date-picker-textfield__before-text-margin', + textFieldTextAfterMargin: '--plasma-date-picker-textfield__after-text-margin', + + /** Токены календаря */ + calendarShadow: '--plasma-date-picker-calendar-shadow', + calendarBackgroundColor: '--plasma-date-picker-calendar-background', + calendarItemBorderRadius: '--plasma-date-picker-calendar-item-border-radius', + calendarSelectedItemBackground: '--plasma-date-picker-calendar-selected-item-background', + calendarSelectedItemColor: '--plasma-date-picker-calendar-selected-item-color', + calendarSelectableItemBackgroundHover: '--plasma-date-picker-calendar-selectable-item-bg-hover', + calendarCurrentItemBorderColor: '--plasma-date-picker-calendar-current-item-border-color', + calendarCurrentItemBackgroundHover: '--plasma-date-picker-calendar-current-item-bg-hover', + calendarCurrentItemColorHover: '--plasma-date-picker-calendar-current-item-color-hover', + calendarCurrentItemChildBackgroundHover: '--plasma-date-picker-calendar-current-item-child-bg-hover', + calendarActiveItemBackground: '--plasma-date-picker-calendar-active-item-bg', + calendarActiveItemColor: '--plasma-date-picker-calendar-active-item-color', + calendarHoveredItemBackground: '--plasma-date-picker-calendar-hovered-item-bg', + calendarHoveredItemColor: '--plasma-date-picker-calendar-hovered-item-color', + calendarSeparatorBackground: '--plasma-date-picker-calendar-separator-background', + calendarRangeBackground: '--plasma-date-picker-calendar-range-background', + calendarOutlineFocusColor: '--plasma-date-picker-calendar-outline-focus-color', + calendarContentPrimaryColor: '--plasma-date-picker-calendar-content-primary-color', + calendarContentSecondaryColor: '--plasma-date-picker-calendar-content-secondary-color', + + calendarHeaderArrowContainerWidth: '--plasma-date-picker-calendar-arrow-container-width', + + calendarHeaderFontFamily: '--plasma-date-picker-calendar-header-font-family', + calendarHeaderFontSize: '--plasma-date-picker-calendar-header-font-size', + calendarHeaderFontStyle: '--plasma-date-picker-calendar-header-font-style', + calendarHeaderFontLetterSpacing: '--plasma-date-picker-calendar-header-font-letter-spacing', + calendarHeaderFontLineHeight: '--plasma-date-picker-calendar-header-line-height', + calendarHeaderFontWeight: '--plasma-date-picker-calendar-header-font-weight', + calendarHeaderFontWeightBold: '--plasma-date-picker-calendar-header-font-weight-bold', + + calendarYearFontFamily: '--plasma-date-picker-calendar-year-font-family', + calendarYearFontSize: '--plasma-date-picker-calendar-year-font-size', + calendarYearFontStyle: '--plasma-date-picker-calendar-year-font-style', + calendarYearFontLetterSpacing: '--plasma-date-picker-calendar-year-font-letter-spacing', + calendarYearFontLineHeight: '--plasma-date-picker-calendar-year-line-height', + calendarYearFontWeight: '--plasma-date-picker-calendar-year-font-weight', + + calendarMonthFontFamily: '--plasma-date-picker-calendar-month-font-family', + calendarMonthFontSize: '--plasma-date-picker-calendar-month-font-size', + calendarMonthFontStyle: '--plasma-date-picker-calendar-month-font-style', + calendarMonthFontLetterSpacing: '--plasma-date-picker-calendar-month-font-letter-spacing', + calendarMonthFontLineHeight: '--plasma-date-picker-calendar-month-line-height', + calendarMonthFontWeight: '--plasma-date-picker-calendar-month-font-weight', + + calendarDayFontFamily: '--plasma-date-picker-calendar-day-font-family', + calendarDayFontSize: '--plasma-date-picker-calendar-day-font-size', + calendarDayFontStyle: '--plasma-date-picker-calendar-day-font-style', + calendarDayFontLetterSpacing: '--plasma-date-picker-calendar-day-font-letter-spacing', + calendarDayFontLineHeight: '--plasma-date-picker-calendar-day-line-height', + calendarDayFontWeight: '--plasma-date-picker-calendar-day-font-weight', +}; diff --git a/packages/plasma-new-hope/src/components/DatePicker/SingleDate/DatePicker.styles.ts b/packages/plasma-new-hope/src/components/DatePicker/SingleDate/DatePicker.styles.ts new file mode 100644 index 0000000000..d44d72afe5 --- /dev/null +++ b/packages/plasma-new-hope/src/components/DatePicker/SingleDate/DatePicker.styles.ts @@ -0,0 +1,148 @@ +import { styled } from '@linaria/react'; +import { css } from '@linaria/core'; + +import { component, mergeConfig } from '../../../engines'; +import { textFieldConfig, textFieldTokens } from '../../TextField'; +import { calendarBaseConfig, calendarBaseTokens } from '../../Calendar'; +import { classes, tokens } from '../DatePicker.tokens'; +import { popoverClasses, popoverConfig } from '../../Popover'; + +const mergedTextFieldConfig = mergeConfig(textFieldConfig); +const TextField = component(mergedTextFieldConfig); + +const mergedCalendarConfig = mergeConfig(calendarBaseConfig); +const Calendar = component(mergedCalendarConfig); + +const mergedPopoverConfig = mergeConfig(popoverConfig); +const Popover = component(mergedPopoverConfig); + +export const StyledPopover = styled(Popover)``; + +// NOTE: переопределение токенов TextField +export const StyledInput = styled(TextField)` + ${textFieldTokens.color}: var(${tokens.textFieldColor}); + ${textFieldTokens.placeholderColor}: var(${tokens.textFieldPlaceholderColor}); + ${textFieldTokens.caretColor}: var(${tokens.textFieldCaretColor}); + ${textFieldTokens.focusColor}: var(${tokens.textFieldFocusColor}); + + ${textFieldTokens.backgroundColor}: var(${tokens.textFieldBackgroundColor}); + ${textFieldTokens.backgroundColorHover}: var(${tokens.textFieldBackgroundColorHover}); + ${textFieldTokens.backgroundColorFocus}: var(${tokens.textFieldBackgroundColorFocus}); + + ${textFieldTokens.borderColor}: var(${tokens.textFieldBorderColor}); + ${textFieldTokens.borderColorHover}: var(${tokens.textFieldBorderColorHover}); + ${textFieldTokens.borderColorFocus}: var(${tokens.textFieldBorderColorFocus}); + + ${textFieldTokens.colorReadOnly}: var(${tokens.textFieldColorReadOnly}); + ${textFieldTokens.backgroundColorReadOnly}: var(${tokens.textFieldBackgroundColorReadOnly}); + ${textFieldTokens.borderColorReadOnly}: var(${tokens.textFieldBorderColorReadOnly}); + ${textFieldTokens.placeholderColorReadOnly}: var(${tokens.textFieldPlaceholderColorReadOnly}); + + ${textFieldTokens.height}: var(${tokens.textFieldHeight}); + ${textFieldTokens.borderWidth}: var(${tokens.textFieldBorderWidth}); + ${textFieldTokens.borderRadius}: var(${tokens.textFieldBorderRadius}); + + ${textFieldTokens.padding}: var(${tokens.textFieldPadding}); + + ${textFieldTokens.leftContentMargin}: var(${tokens.textFieldLeftContentMargin}); + ${textFieldTokens.rightContentMargin}: var(${tokens.textFieldRightContentMargin}); + + ${textFieldTokens.fontFamily}: var(${tokens.textFieldFontFamily}); + ${textFieldTokens.fontSize}: var(${tokens.textFieldFontSize}); + ${textFieldTokens.fontStyle}: var(${tokens.textFieldFontStyle}); + ${textFieldTokens.fontWeight}: var(${tokens.textFieldFontWeight}); + ${textFieldTokens.letterSpacing}: var(${tokens.textFieldLetterSpacing}); + ${textFieldTokens.lineHeight}: var(${tokens.textFieldLineHeight}); + + ${textFieldTokens.disabledOpacity}: var(${tokens.disabledOpacity}); + + ${textFieldTokens.textBeforeColor}: var(${tokens.textFieldTextBeforeColor}); + ${textFieldTokens.textAfterColor}: var(${tokens.textFieldTextAfterColor}); + ${textFieldTokens.textBeforeMargin}: var(${tokens.textFieldTextBeforeMargin}); + ${textFieldTokens.textAfterMargin}: var(${tokens.textFieldTextAfterMargin}); + + &.${classes.datePickerError} { + ${textFieldTokens.backgroundColor}: var(${tokens.textFieldBackgroundErrorColor}); + ${textFieldTokens.backgroundColorHover}: var(${tokens.textFieldBackgroundErrorColorHover}); + ${textFieldTokens.backgroundColorFocus}: var(${tokens.textFieldBackgroundErrorColorFocus}); + + ${textFieldTokens.borderColor}: var(${tokens.textFieldBorderColorError}); + ${textFieldTokens.borderColorHover}: var(${tokens.textFieldBorderColorErrorHover}); + ${textFieldTokens.borderColorFocus}: var(${tokens.textFieldBorderColorErrorFocus}); + } + + &.${classes.datePickerSuccess} { + ${textFieldTokens.backgroundColor}: var(${tokens.textFieldBackgroundSuccessColor}); + ${textFieldTokens.backgroundColorHover}: var(${tokens.textFieldBackgroundSuccessColorHover}); + ${textFieldTokens.backgroundColorFocus}: var(${tokens.textFieldBackgroundSuccessColorFocus}); + + ${textFieldTokens.borderColor}: var(${tokens.textFieldBorderColorSuccess}); + ${textFieldTokens.borderColorHover}: var(${tokens.textFieldBorderColorSuccessHover}); + ${textFieldTokens.borderColorFocus}: var(${tokens.textFieldBorderColorSuccessFocus}); + } +`; + +// NOTE: переопределение токенов Calendar +export const StyledCalendar = styled(Calendar)` + box-shadow: var(${tokens.calendarShadow}); + + ${calendarBaseTokens.calendarBackgroundColor}: var(${tokens.calendarBackgroundColor}); + ${calendarBaseTokens.calendarSelectedItemBackground}: var(${tokens.calendarSelectedItemBackground}); + ${calendarBaseTokens.calendarSelectedItemColor}: var(${tokens.calendarSelectedItemColor}); + ${calendarBaseTokens.calendarSelectableItemBackgroundHover}: var(${tokens.calendarSelectableItemBackgroundHover}); + ${calendarBaseTokens.calendarCurrentItemBorderColor}: var(${tokens.calendarCurrentItemBorderColor}); + ${calendarBaseTokens.calendarCurrentItemBackgroundHover}: var(${tokens.calendarCurrentItemBackgroundHover}); + ${calendarBaseTokens.calendarCurrentItemColorHover}: var(${tokens.calendarCurrentItemColorHover}); + ${calendarBaseTokens.calendarCurrentItemChildBackgroundHover}: var(${tokens.calendarCurrentItemChildBackgroundHover}); + ${calendarBaseTokens.calendarActiveItemBackground}: var(${tokens.calendarActiveItemBackground}); + ${calendarBaseTokens.calendarActiveItemColor}: var(${tokens.calendarActiveItemColor}); + ${calendarBaseTokens.calendarHoveredItemBackground}: var(${tokens.calendarHoveredItemBackground}); + ${calendarBaseTokens.calendarHoveredItemColor}: var(${tokens.calendarHoveredItemColor}); + ${calendarBaseTokens.calendarRangeBackground}: var(${tokens.calendarRangeBackground}); + ${calendarBaseTokens.calendarOutlineFocusColor}: var(${tokens.calendarOutlineFocusColor}); + ${calendarBaseTokens.calendarContentPrimaryColor}: var(${tokens.calendarContentPrimaryColor}); + ${calendarBaseTokens.calendarContentSecondaryColor}: var(${tokens.calendarContentSecondaryColor}); + + ${calendarBaseTokens.calendarHeaderArrowContainerWidth}: var(${tokens.calendarHeaderArrowContainerWidth}); + ${calendarBaseTokens.calendarItemBorderRadius}: var(${tokens.calendarItemBorderRadius}); + ${calendarBaseTokens.calendarHeaderFontFamily}: var(${tokens.calendarHeaderFontFamily}); + ${calendarBaseTokens.calendarHeaderFontSize}: var(${tokens.calendarHeaderFontSize}); + ${calendarBaseTokens.calendarHeaderFontStyle}: var(${tokens.calendarHeaderFontStyle}); + ${calendarBaseTokens.calendarHeaderFontLetterSpacing}: var(${tokens.calendarHeaderFontLetterSpacing}); + ${calendarBaseTokens.calendarHeaderFontLineHeight}: var(${tokens.calendarHeaderFontLineHeight}); + ${calendarBaseTokens.calendarHeaderFontWeight}: var(${tokens.calendarHeaderFontWeight}); + ${calendarBaseTokens.calendarHeaderFontWeightBold}: var(${tokens.calendarHeaderFontWeightBold}); + ${calendarBaseTokens.calendarYearFontFamily}: var(${tokens.calendarYearFontFamily}); + ${calendarBaseTokens.calendarYearFontSize}: var(${tokens.calendarYearFontSize}); + ${calendarBaseTokens.calendarYearFontStyle}: var(${tokens.calendarYearFontStyle}); + ${calendarBaseTokens.calendarYearFontLetterSpacing}: var(${tokens.calendarYearFontLetterSpacing}); + ${calendarBaseTokens.calendarYearFontLineHeight}: var(${tokens.calendarYearFontLineHeight}); + ${calendarBaseTokens.calendarYearFontWeight}: var(${tokens.calendarYearFontWeight}); + ${calendarBaseTokens.calendarMonthFontFamily}: var(${tokens.calendarMonthFontFamily}); + ${calendarBaseTokens.calendarMonthFontSize}: var(${tokens.calendarMonthFontSize}); + ${calendarBaseTokens.calendarMonthFontStyle}: var(${tokens.calendarMonthFontStyle}); + ${calendarBaseTokens.calendarMonthFontLetterSpacing}: var(${tokens.calendarMonthFontLetterSpacing}); + ${calendarBaseTokens.calendarMonthFontLineHeight}: var(${tokens.calendarMonthFontLineHeight}); + ${calendarBaseTokens.calendarMonthFontWeight}: var(${tokens.calendarMonthFontWeight}); + ${calendarBaseTokens.calendarDayFontFamily}: var(${tokens.calendarDayFontFamily}); + ${calendarBaseTokens.calendarDayFontSize}: var(${tokens.calendarDayFontSize}); + ${calendarBaseTokens.calendarDayFontStyle}: var(${tokens.calendarDayFontStyle}); + ${calendarBaseTokens.calendarDayFontLetterSpacing}: var(${tokens.calendarDayFontLetterSpacing}); + ${calendarBaseTokens.calendarDayFontLineHeight}: var(${tokens.calendarDayFontLineHeight}); + ${calendarBaseTokens.calendarDayFontWeight}: var(${tokens.calendarDayFontWeight}); +`; + +export const base = css` + .${String(popoverClasses.wrapper)} { + display: block; + } + + .${String(popoverClasses.target)} { + display: flex; + flex-direction: column; + } +`; + +export const StyledLabel = styled.label``; + +export const LeftHelper = styled.div``; diff --git a/packages/plasma-new-hope/src/components/DatePicker/SingleDate/DatePicker.tsx b/packages/plasma-new-hope/src/components/DatePicker/SingleDate/DatePicker.tsx new file mode 100644 index 0000000000..788ad620aa --- /dev/null +++ b/packages/plasma-new-hope/src/components/DatePicker/SingleDate/DatePicker.tsx @@ -0,0 +1,196 @@ +import React, { ChangeEvent, SyntheticEvent, forwardRef, useEffect, useMemo, useRef, useState } from 'react'; + +import type { RootProps } from '../../../engines'; +import { cx, getPlacements } from '../../../utils'; +import { classes } from '../DatePicker.tokens'; +import { formatCalendarValue, formatInputValue, getDateFromFormat } from '../utils/dateHelper'; + +import type { DatePickerProps } from './DatePicker.types'; +import { base as sizeCSS } from './variations/_size/base'; +import { base as viewCSS } from './variations/_view/base'; +import { base as disabledCSS } from './variations/_disabled/base'; +import { base as readOnlyCSS } from './variations/_readonly/base'; +import { LeftHelper, StyledCalendar, StyledInput, StyledLabel, StyledPopover, base } from './DatePicker.styles'; + +export const datePickerRoot = ( + Root: RootProps>, +) => + forwardRef( + ( + { + isOpen = false, + + label, + placeholder, + leftHelper, + contentLeft, + contentRight, + textBefore, + textAfter, + view, + size, + readOnly = false, + disabled = false, + + defaultDate = '', + valueError, + valueSuccess, + format = 'DD.MM.YYYY', + min, + max, + includeEdgeDates = false, + eventList, + disabledList, + type = 'Days', + + placement = ['top', 'bottom'], + closeOnOverlayClick = true, + offset, + + onChangeValue, + onCommitDate, + onToggle, + onFocus, + onBlur, + + ...rest + }, + ref, + ) => { + const inputRef = useRef(null); + const [isInnerOpen, setIsInnerOpen] = useState(isOpen); + + const [calendarValue, setCalendarValue] = useState(formatCalendarValue(defaultDate, format)); + const [inputValue, setInputValue] = useState(formatInputValue(defaultDate, format)); + + const datePickerErrorClass = valueError ? classes.datePickerError : undefined; + const datePickerSuccessClass = valueSuccess ? classes.datePickerSuccess : undefined; + + const handleToggle = (opened: boolean, event: SyntheticEvent | Event) => { + if (disabled || readOnly) { + return; + } + + const isCalendarOpen = event.target === inputRef?.current ? true : opened; + + if (onToggle) { + return onToggle(isCalendarOpen, event); + } + + setIsInnerOpen(isCalendarOpen); + }; + + const handleChangeValue = (event: ChangeEvent) => { + if (disabled || readOnly) { + return; + } + + const newValue = event.target.value; + + setCalendarValue(formatCalendarValue(newValue, format)); + setInputValue(formatInputValue(newValue, format)); + + onChangeValue?.(event, newValue); + }; + + const handleCommitDate = (date: Date | string, applyFormat?: boolean, isCalendarValue?: boolean) => { + if (disabled || readOnly) { + return; + } + + if (!date) { + return onCommitDate?.('', false, true); + } + + if (isCalendarValue) { + return onCommitDate?.(date, false, true); + } + + const formatString = applyFormat ? format : undefined; + + const { value: newDate, isError, isSuccess } = getDateFromFormat(date, formatString); + + setCalendarValue(formatCalendarValue(newDate, format)); + setInputValue(formatInputValue(newDate, format)); + + onCommitDate?.(newDate, isError, isSuccess); + }; + + const DatePickerInput = ( + handleCommitDate(date, true)} + onFocus={onFocus} + onBlur={onBlur} + /> + ); + + return ( + + {label && {label}} + + + + {leftHelper && {leftHelper}} + + ); + }, + ); + +export const datePickerConfig = { + name: 'DatePicker', + tag: 'div', + layout: datePickerRoot, + base, + variations: { + view: { + css: viewCSS, + }, + size: { + css: sizeCSS, + }, + disabled: { + css: disabledCSS, + attrs: true, + }, + readOnly: { + css: readOnlyCSS, + attrs: true, + }, + }, + defaults: { + size: 'm', + view: 'default', + }, +}; diff --git a/packages/plasma-new-hope/src/components/DatePicker/SingleDate/DatePicker.types.ts b/packages/plasma-new-hope/src/components/DatePicker/SingleDate/DatePicker.types.ts new file mode 100644 index 0000000000..50c4b5a052 --- /dev/null +++ b/packages/plasma-new-hope/src/components/DatePicker/SingleDate/DatePicker.types.ts @@ -0,0 +1,138 @@ +import type { HTMLAttributes, SyntheticEvent } from 'react'; + +import type { CalendarStateType } from '../../Calendar'; +import type { DisabledDay, EventDay } from '../../Calendar/Calendar.types'; + +export type DatePickerPlacementBasic = 'top' | 'bottom' | 'right' | 'left'; +export type DatePickerPlacement = DatePickerPlacementBasic | 'auto'; + +export type DatePickerdVariationProps = { + /** + * Размер контрола. + */ + size?: string; + /** + * Вид контрола. + */ + view?: string; + /** + * Компонент доступен только для чтения. + */ + readOnly?: boolean; + /** + * Компонент неактивен. + */ + disabled?: boolean; +}; + +export type DatePickerTextFieldProps = { + /** + * Выбранное значение. + */ + defaultDate?: Date; + /** + * Некорректное значение даты + */ + valueError?: boolean; + /** + * Корректное значение даты + */ + valueSuccess?: boolean; + /** + * Метка-подпись к элементу + */ + label?: string; + /** + * Вспомогательный текст снизу слева для поля ввода. + */ + leftHelper?: string; + /** + * Слот для контента слева. + */ + contentLeft?: React.ReactElement; + /** + * Слот для контента справа. + */ + contentRight?: React.ReactElement; + /** + * Слот для вспомогательного текста справа. + */ + textBefore?: string; + /** + * Слот для вспомогательного текста слева. + */ + textAfter?: string; + /** + * Callback по нажатию Enter в поле ввода или выборе дня на календаре. + */ + onCommitDate?: (value: Date | string, error?: boolean, success?: boolean) => void; + /** + * Обработчик изменения значения. + */ + onChangeValue?: (event: SyntheticEvent, value?: string) => void; +}; + +export type DatePickerCalendarProps = { + /** + * Формат даты. + * @default `DD.MM.YYYY` + */ + format?: string; + /** + * Минимальное значение даты. + */ + min?: Date; + /** + * Максимальное значение даты. + */ + max?: Date; + /** + * Должны ли значения минимального и максимального дня включаться в диапазон. + */ + includeEdgeDates?: boolean; + /** + * Список событий. + */ + eventList?: EventDay[]; + /** + * Список отключенных дней. + */ + disabledList?: DisabledDay[]; + /** + * Тип отображения календаря: дни, месяца, года. + */ + type?: CalendarStateType; +}; + +export type DatePickerPopoverProps = { + /** + * Видимость календаря. + */ + isOpen?: boolean; + /** + * Сторона открытия календаря относительно поля ввода. + * @default + * auto + */ + placement?: DatePickerPlacement | Array; + /** + * Отступ календаря относительно поля ввода. + * @default [0, 0] + */ + offset?: [number, number]; + /** + * Закрывать календарь при нажатии вне области элемента. + * @default true + */ + closeOnOverlayClick?: boolean; + /** + * Событие сворачивания/разворачивания календаря. + */ + onToggle?: (isOpen: boolean, event: SyntheticEvent | Event) => void; +}; + +export type DatePickerProps = DatePickerdVariationProps & + DatePickerTextFieldProps & + DatePickerCalendarProps & + DatePickerPopoverProps & + Omit, 'defaultValue'>; diff --git a/packages/plasma-new-hope/src/components/DatePicker/SingleDate/variations/_disabled/base.ts b/packages/plasma-new-hope/src/components/DatePicker/SingleDate/variations/_disabled/base.ts new file mode 100644 index 0000000000..4868755868 --- /dev/null +++ b/packages/plasma-new-hope/src/components/DatePicker/SingleDate/variations/_disabled/base.ts @@ -0,0 +1,10 @@ +import { css } from '@linaria/core'; + +import { tokens } from '../../../DatePicker.tokens'; + +export const base = css` + &[disabled] { + opacity: var(${tokens.disabledOpacity}); + cursor: not-allowed; + } +`; diff --git a/packages/plasma-new-hope/src/components/DatePicker/SingleDate/variations/_disabled/tokens.json b/packages/plasma-new-hope/src/components/DatePicker/SingleDate/variations/_disabled/tokens.json new file mode 100644 index 0000000000..e013e492ea --- /dev/null +++ b/packages/plasma-new-hope/src/components/DatePicker/SingleDate/variations/_disabled/tokens.json @@ -0,0 +1,4 @@ +{ + "type": "boolean", + "tokens": ["--plasma-date-picker-disabled-opacity"] +} diff --git a/packages/plasma-new-hope/src/components/DatePicker/SingleDate/variations/_readonly/base.ts b/packages/plasma-new-hope/src/components/DatePicker/SingleDate/variations/_readonly/base.ts new file mode 100644 index 0000000000..809a721890 --- /dev/null +++ b/packages/plasma-new-hope/src/components/DatePicker/SingleDate/variations/_readonly/base.ts @@ -0,0 +1,19 @@ +import { css } from '@linaria/core'; + +import { tokens } from '../../../DatePicker.tokens'; +import { LeftHelper, StyledLabel } from '../../DatePicker.styles'; + +export const base = css` + &[readonly] { + cursor: default; + + ${StyledLabel} { + display: block; + color: var(${tokens.labelColorReadOnly}); + } + + ${LeftHelper} { + color: var(${tokens.leftHelperColorReadOnly}); + } + } +`; diff --git a/packages/plasma-new-hope/src/components/DatePicker/SingleDate/variations/_readonly/tokens.json b/packages/plasma-new-hope/src/components/DatePicker/SingleDate/variations/_readonly/tokens.json new file mode 100644 index 0000000000..698f64ae5b --- /dev/null +++ b/packages/plasma-new-hope/src/components/DatePicker/SingleDate/variations/_readonly/tokens.json @@ -0,0 +1,4 @@ +{ + "type": "boolean", + "tokens": ["--plasma-date-picker__label-color-readonly", "--plasma-date-picker__left-helper-color-readonly"] +} diff --git a/packages/plasma-new-hope/src/components/DatePicker/SingleDate/variations/_size/base.ts b/packages/plasma-new-hope/src/components/DatePicker/SingleDate/variations/_size/base.ts new file mode 100644 index 0000000000..aed04f65dc --- /dev/null +++ b/packages/plasma-new-hope/src/components/DatePicker/SingleDate/variations/_size/base.ts @@ -0,0 +1,28 @@ +import { css } from '@linaria/core'; + +import { tokens } from '../../../DatePicker.tokens'; +import { StyledLabel, LeftHelper } from '../../DatePicker.styles'; + +export const base = css` + ${StyledLabel} { + margin: var(${tokens.labelOffset}); + + font-family: var(${tokens.labelFontFamily}); + font-size: var(${tokens.labelFontSize}); + font-style: var(${tokens.labelFontStyle}); + font-weight: var(${tokens.labelFontWeight}); + letter-spacing: var(${tokens.labelLetterSpacing}); + line-height: var(${tokens.labelLineHeight}); + } + + ${LeftHelper} { + margin: var(${tokens.leftHelperOffset}); + + font-family: var(${tokens.leftHelperFontFamily}); + font-size: var(${tokens.leftHelperFontSize}); + font-style: var(${tokens.leftHelperFontStyle}); + font-weight: var(${tokens.leftHelperFontWeight}); + letter-spacing: var(${tokens.leftHelperLetterSpacing}); + line-height: var(${tokens.leftHelperLineHeight}); + } +`; diff --git a/packages/plasma-new-hope/src/components/DatePicker/SingleDate/variations/_size/tokens.json b/packages/plasma-new-hope/src/components/DatePicker/SingleDate/variations/_size/tokens.json new file mode 100644 index 0000000000..6fb74382c7 --- /dev/null +++ b/packages/plasma-new-hope/src/components/DatePicker/SingleDate/variations/_size/tokens.json @@ -0,0 +1,16 @@ +[ + "--plasma-date-picker__label-offset", + "--plasma-date-picker__label-font-family", + "--plasma-date-picker__label-font-style", + "--plasma-date-picker__label-font-size", + "--plasma-date-picker__label-font-weight", + "--plasma-date-picker__label-letter-spacing", + "--plasma-date-picker__label-line-height", + "--plasma-date-picker__left-helper-offset", + "--plasma-date-picker__left-helper-font-family", + "--plasma-date-picker__left-helper-font-style", + "--plasma-date-picker__left-helper-font-size", + "--plasma-date-picker__left-helper-font-weight", + "--plasma-date-picker__left-helper-letter-spacing", + "--plasma-date-picker__left-helper-line-height" +] diff --git a/packages/plasma-new-hope/src/components/DatePicker/SingleDate/variations/_view/base.ts b/packages/plasma-new-hope/src/components/DatePicker/SingleDate/variations/_view/base.ts new file mode 100644 index 0000000000..4f0a74d93b --- /dev/null +++ b/packages/plasma-new-hope/src/components/DatePicker/SingleDate/variations/_view/base.ts @@ -0,0 +1,15 @@ +import { css } from '@linaria/core'; + +import { LeftHelper, StyledLabel } from '../../DatePicker.styles'; +import { tokens } from '../../../DatePicker.tokens'; + +export const base = css` + ${StyledLabel} { + display: block; + color: var(${tokens.labelColor}); + } + + ${LeftHelper} { + color: var(${tokens.leftHelperColor}); + } +`; diff --git a/packages/plasma-new-hope/src/components/DatePicker/SingleDate/variations/_view/tokens.json b/packages/plasma-new-hope/src/components/DatePicker/SingleDate/variations/_view/tokens.json new file mode 100644 index 0000000000..6fa6155b54 --- /dev/null +++ b/packages/plasma-new-hope/src/components/DatePicker/SingleDate/variations/_view/tokens.json @@ -0,0 +1 @@ +["--plasma-datepicker__label-color", "--plasma-datepicker__left-helper-color"] diff --git a/packages/plasma-new-hope/src/components/DatePicker/index.ts b/packages/plasma-new-hope/src/components/DatePicker/index.ts new file mode 100644 index 0000000000..35843157fe --- /dev/null +++ b/packages/plasma-new-hope/src/components/DatePicker/index.ts @@ -0,0 +1,3 @@ +export { datePickerRoot, datePickerConfig } from './SingleDate/DatePicker'; +export { classes as datePickerClasses, tokens as datePickerTokens } from './DatePicker.tokens'; +export type { DatePickerProps, DatePickerPlacementBasic, DatePickerPlacement } from './SingleDate/DatePicker.types'; diff --git a/packages/plasma-new-hope/src/components/DatePicker/utils/dateHelper.ts b/packages/plasma-new-hope/src/components/DatePicker/utils/dateHelper.ts new file mode 100644 index 0000000000..d85d17f1a7 --- /dev/null +++ b/packages/plasma-new-hope/src/components/DatePicker/utils/dateHelper.ts @@ -0,0 +1,45 @@ +import { customDayjs } from '../../../utils/datejs'; + +export const formatInputValue = (value?: Date | string, format?: string) => { + if (!value) { + return ''; + } + + if (format && customDayjs(value, format, true).isValid()) { + return customDayjs(value, format).format(format); + } + + if (format && String(value).length >= 10 && String(new Date(value)) !== 'Invalid Date') { + return customDayjs(value).format(format); + } + + return String(value); +}; + +export const formatCalendarValue = (value?: Date | string, format?: string) => { + if (!value) { + return undefined; + } + + if (format && customDayjs(value, format, true).isValid()) { + return customDayjs(value, format, true).toDate(); + } + + if (String(new Date(value)) !== 'Invalid Date') { + return customDayjs(value).toDate(); + } + + return undefined; +}; + +export const getDateFromFormat = (value: Date | string, format?: string) => { + if (format && customDayjs(value, format, true).isValid()) { + return { value: customDayjs(value, format, true).toDate(), isError: false, isSuccess: true }; + } + + if (!format && String(new Date(value)) !== 'Invalid Date') { + return { value: customDayjs(value).toDate(), isError: false, isSuccess: true }; + } + + return { value, isError: true, isSuccess: false }; +}; diff --git a/packages/plasma-new-hope/src/components/Dropdown/Dropdown.tsx b/packages/plasma-new-hope/src/components/Dropdown/Dropdown.tsx index 76fdaebbf0..468b98f721 100644 --- a/packages/plasma-new-hope/src/components/Dropdown/Dropdown.tsx +++ b/packages/plasma-new-hope/src/components/Dropdown/Dropdown.tsx @@ -1,7 +1,7 @@ import React, { forwardRef, useReducer } from 'react'; import { RootProps } from '../../engines'; -import { cx } from '../../utils'; +import { cx, getPlacements } from '../../utils'; import { pathReducer } from './reducers/pathReducer'; import { focusedPathReducer } from './reducers/focusedPathReducer'; @@ -9,7 +9,7 @@ import { DropdownInner } from './ui'; import { base as viewCSS } from './variations/_view/base'; import { base as sizeCSS } from './variations/_size/base'; import { Ul, StyledPopover, base } from './Dropdown.styles'; -import { getPlacements, childrenWithProps } from './utils'; +import { childrenWithProps } from './utils'; import type { DropdownProps, HandleGlobalToggleType } from './Dropdown.types'; import { classes } from './Dropdown.tokens'; import { useKeyNavigation } from './hooks/useKeyboardNavigation'; diff --git a/packages/plasma-new-hope/src/components/Dropdown/utils/index.tsx b/packages/plasma-new-hope/src/components/Dropdown/utils/index.tsx index aa3c438cf4..c62d2eb493 100644 --- a/packages/plasma-new-hope/src/components/Dropdown/utils/index.tsx +++ b/packages/plasma-new-hope/src/components/Dropdown/utils/index.tsx @@ -1,24 +1,7 @@ import type { ReactNode } from 'react'; import React, { Children, HTMLAttributes, ElementType, cloneElement, isValidElement } from 'react'; -import type { PopoverPlacementBasic } from '../../Popover'; -import type { DropdownPlacement, DropdownPlacementBasic, DropdownProps } from '../Dropdown.types'; - -export const getPlacement = (placement: DropdownPlacement) => { - return `${placement}-start` as PopoverPlacementBasic; -}; - -export const getPlacements = (placements?: DropdownPlacement | DropdownPlacementBasic[]) => { - if (!placements) { - return; - } - const isArray = Array.isArray(placements); - - if (!isArray) { - return getPlacement(placements as DropdownPlacement); - } - return ((placements || []) as DropdownPlacementBasic[]).map((placement) => getPlacement(placement)); -}; +import type { DropdownProps } from '../Dropdown.types'; export const getCorrectHeight = (listHeight: Required['listHeight']): string => { if (Number.isNaN(Number(listHeight))) { diff --git a/packages/plasma-new-hope/src/components/Select/Select.tsx b/packages/plasma-new-hope/src/components/Select/Select.tsx index 6d92f713cc..ce62edb38a 100644 --- a/packages/plasma-new-hope/src/components/Select/Select.tsx +++ b/packages/plasma-new-hope/src/components/Select/Select.tsx @@ -2,8 +2,7 @@ import React, { Children, forwardRef, useCallback, useEffect, useMemo, useRef, u import { safeUseId, useForkRef } from '@salutejs/plasma-core'; import { RootProps } from '../../engines'; -import { cx } from '../../utils'; -import { getPlacements } from '../Dropdown/utils'; +import { cx, getPlacements } from '../../utils'; import { useDidMountEffect, useForceUpdate } from '../../hooks'; import { base as targetCSS } from './variations/_target/base'; diff --git a/packages/plasma-new-hope/src/examples/plasma_b2c/components/DatePicker/DatePicker.config.ts b/packages/plasma-new-hope/src/examples/plasma_b2c/components/DatePicker/DatePicker.config.ts new file mode 100644 index 0000000000..4af4e0c70f --- /dev/null +++ b/packages/plasma-new-hope/src/examples/plasma_b2c/components/DatePicker/DatePicker.config.ts @@ -0,0 +1,130 @@ +import { css } from '@linaria/core'; + +import { datePickerTokens as tokens } from '../../../../components/DatePicker'; + +export const config = { + defaults: { + view: 'default', + size: 'l', + }, + variations: { + view: { + default: css` + ${tokens.labelColor}: var(--text-primary); + ${tokens.leftHelperColor}: var(--text-secondary); + + ${tokens.textFieldColor}: var(--text-primary); + ${tokens.textFieldPlaceholderColor}: var(--text-secondary); + ${tokens.textFieldCaretColor}: var(--text-accent); + + ${tokens.textFieldBackgroundColor}: var(--surface-transparent-secondary); + ${tokens.textFieldBackgroundColorFocus}: var(--surface-transparent-secondary); + ${tokens.textFieldBackgroundErrorColor}: var(--surface-transparent-negative); + ${tokens.textFieldBackgroundErrorColorFocus}: var(--surface-transparent-negative-active); + ${tokens.textFieldBackgroundSuccessColor}: var(--surface-transparent-positive); + ${tokens.textFieldBackgroundSuccessColorFocus}: var(--surface-transparent-positive-active); + + ${tokens.textFieldTextBeforeColor}: var(--text-tertiary); + ${tokens.textFieldTextAfterColor}: var(--text-tertiary); + + ${tokens.focusColor}: var(--text-accent); + + ${tokens.calendarShadow}: var(--shadow-down-soft-s); + ${tokens.calendarBackgroundColor}: var(--background-primary); + ${tokens.calendarSelectedItemBackground}: var(--plasma-colors-primary); + ${tokens.calendarSelectedItemColor}: var(--background-primary); + ${tokens.calendarSelectableItemBackgroundHover}: var(--surface-liquid02); + ${tokens.calendarCurrentItemBorderColor}: var(--plasma-colors-primary); + ${tokens.calendarCurrentItemBackgroundHover}: transparent; + ${tokens.calendarCurrentItemColorHover}: var(--plasma-colors-primary); + ${tokens.calendarCurrentItemChildBackgroundHover}: var(--surface-liquid02); + ${tokens.calendarActiveItemBackground}: var(--plasma-colors-primary); + ${tokens.calendarActiveItemColor}: var(--surface-solid03); + ${tokens.calendarHoveredItemBackground}: var(--plasma-colors-accent); + ${tokens.calendarHoveredItemColor}: var(--background-primary); + ${tokens.calendarRangeBackground}: var(--surface-liquid02); + ${tokens.calendarOutlineFocusColor}: var(--button-focused); + ${tokens.calendarContentPrimaryColor}: var(--plasma-colors-primary); + ${tokens.calendarContentSecondaryColor}: var(--plasma-colors-secondary); + `, + }, + size: { + l: css` + ${tokens.labelOffset}: 0 0 0.75rem 0; + + ${tokens.labelFontFamily}: var(--plasma-typo-body-l-font-family); + ${tokens.labelFontStyle}: var(--plasma-typo-body-l-font-style); + ${tokens.labelFontSize}: var(--plasma-typo-body-l-font-size); + ${tokens.labelFontWeight}: var(--plasma-typo-body-l-font-weight); + ${tokens.labelLetterSpacing}: var(--plasma-typo-body-l-letter-spacing); + ${tokens.labelLineHeight}: var(--plasma-typo-body-l-line-height); + + ${tokens.textFieldHeight}: 3.5rem; + ${tokens.textFieldBorderRadius}: 0.875rem; + ${tokens.textFieldPadding}: 1.0625rem 1.125rem 1.0625rem 1.125rem; + ${tokens.textFieldFontFamily}: var(--plasma-typo-body-l-font-family); + ${tokens.textFieldFontStyle}: var(--plasma-typo-body-l-font-style); + ${tokens.textFieldFontSize}: var(--plasma-typo-body-l-font-size); + ${tokens.textFieldFontWeight}: var(--plasma-typo-body-l-font-weight); + ${tokens.textFieldLetterSpacing}: var(--plasma-typo-body-l-letter-spacing); + ${tokens.textFieldLineHeight}: var(--plasma-typo-body-l-line-height); + + ${tokens.leftHelperOffset}: 0.25rem 0 0 0; + ${tokens.leftHelperFontFamily}: var(--plasma-typo-body-xs-font-family); + ${tokens.leftHelperFontStyle}: var(--plasma-typo-body-xs-font-style); + ${tokens.leftHelperFontSize}: var(--plasma-typo-body-xs-font-size); + ${tokens.leftHelperFontWeight}: var(--plasma-typo-body-xs-font-weight); + ${tokens.leftHelperLetterSpacing}: var(--plasma-typo-body-xs-letter-spacing); + ${tokens.leftHelperLineHeight}: var(--plasma-typo-body-xs-line-height); + + ${tokens.textFieldLeftContentMargin}: -0.0625rem 0.5rem -0.0625rem -0.125rem; + ${tokens.textFieldRightContentMargin}: -0.0625rem -0.125rem -0.0625rem 0.75rem; + ${tokens.textFieldTextBeforeMargin}: 0 0.25rem 0 0; + ${tokens.textFieldTextAfterMargin}: 0 0 0 0.25rem; + + ${tokens.calendarHeaderArrowContainerWidth}: 5.5rem; + ${tokens.calendarItemBorderRadius}: 0.5rem; + ${tokens.calendarHeaderFontFamily}: var(--plasma-typo-h4-font-family); + ${tokens.calendarHeaderFontSize}: var(--plasma-typo-h4-font-size); + ${tokens.calendarHeaderFontStyle}: var(--plasma-typo-h4-font-style); + ${tokens.calendarHeaderFontLetterSpacing}: var(--plasma-typo-h4-letter-spacing); + ${tokens.calendarHeaderFontLineHeight}: var(--plasma-typo-h4-line-height); + ${tokens.calendarHeaderFontWeight}: var(--plasma-typo-h4-font-weight); + ${tokens.calendarHeaderFontWeightBold}: var(--plasma-typo-h4-bold-font-weight); + ${tokens.calendarYearFontFamily}: var(--plasma-typo-body-s-font-family); + ${tokens.calendarYearFontSize}: var(--plasma-typo-body-s-font-size); + ${tokens.calendarYearFontStyle}: var(--plasma-typo-body-s-font-style); + ${tokens.calendarYearFontLetterSpacing}: var(--plasma-typo-body-s-letter-spacing); + ${tokens.calendarYearFontLineHeight}: var(--plasma-typo-body-s-line-height); + ${tokens.calendarYearFontWeight}: var(--plasma-typo-body-s-font-weight); + ${tokens.calendarMonthFontFamily}: var(--plasma-typo-body-s-font-family); + ${tokens.calendarMonthFontSize}: var(--plasma-typo-body-s-font-size); + ${tokens.calendarMonthFontStyle}: var(--plasma-typo-body-s-font-style); + ${tokens.calendarMonthFontLetterSpacing}: var(--plasma-typo-body-s-letter-spacing); + ${tokens.calendarMonthFontLineHeight}: var(--plasma-typo-body-s-line-height); + ${tokens.calendarMonthFontWeight}: var(--plasma-typo-body-s-font-weight); + ${tokens.calendarDayFontFamily}: var(--plasma-typo-body-s-font-family); + ${tokens.calendarDayFontSize}: var(--plasma-typo-body-s-font-size); + ${tokens.calendarDayFontStyle}: var(--plasma-typo-body-s-font-style); + ${tokens.calendarDayFontLetterSpacing}: var(--plasma-typo-body-s-letter-spacing); + ${tokens.calendarDayFontLineHeight}: var(--plasma-typo-body-s-line-height); + ${tokens.calendarDayFontWeight}: var(--plasma-typo-body-s-font-weight); + `, + }, + disabled: { + true: css` + ${tokens.disabledOpacity}: 0.4; + `, + }, + readOnly: { + true: css` + ${tokens.labelColorReadOnly}: var(--text-secondary); + ${tokens.leftHelperColorReadOnly}: var(--text-secondary); + + ${tokens.textFieldColorReadOnly}: var(--text-secondary); + ${tokens.textFieldBackgroundColorReadOnly}: var(--surface-transparent-primary); + ${tokens.textFieldPlaceholderColorReadOnly}: var(--text-secondary); + `, + }, + }, +}; diff --git a/packages/plasma-new-hope/src/examples/plasma_b2c/components/DatePicker/DatePicker.stories.tsx b/packages/plasma-new-hope/src/examples/plasma_b2c/components/DatePicker/DatePicker.stories.tsx new file mode 100644 index 0000000000..e6cc25ff33 --- /dev/null +++ b/packages/plasma-new-hope/src/examples/plasma_b2c/components/DatePicker/DatePicker.stories.tsx @@ -0,0 +1,104 @@ +import React, { ComponentProps, useState } from 'react'; +import type { StoryObj, Meta } from '@storybook/react'; +import { action } from '@storybook/addon-actions'; +import { IconPlaceholder } from '@salutejs/plasma-sb-utils'; + +import { WithTheme } from '../../../_helpers'; + +import { DatePicker } from './DatePicker'; + +const onChangeValue = action('onChangeValue'); +const onBlur = action('onBlur'); +const onFocus = action('onFocus'); + +const sizes = ['l', 'm', 's', 'xs']; +const views = ['default']; + +const meta: Meta = { + title: 'plasma_b2c/DatePicker', + component: DatePicker, + decorators: [WithTheme], + argTypes: { + view: { + options: views, + control: { + type: 'inline-radio', + }, + }, + size: { + options: sizes, + control: { + type: 'inline-radio', + }, + }, + }, +}; + +export default meta; + +type StoryPropsDefault = ComponentProps & { + enableContentLeft: boolean; + enableContentRight: boolean; +}; + +const StoryDefault = ({ enableContentLeft, enableContentRight, size, ...rest }: StoryPropsDefault) => { + const [value, setValue] = useState(''); + const [error, setError] = useState(false); + const [success, setSuccess] = useState(false); + const defaultValue = new Date(2024, 5, 14); + + const iconSize = 's'; + // size === 'xs' ? 'xs' : 's'; + + const handleCommitDate = (newDate: Date | string, dateError?: boolean, dateSuccess?: boolean) => { + if (dateError) { + setError(true); + } + + if (dateSuccess) { + setSuccess(true); + } + + setValue(newDate); + }; + + return ( + : undefined} + contentRight={enableContentRight ? : undefined} + onBlur={onBlur} + onFocus={onFocus} + onChangeValue={(e, currentValue) => { + setValue(currentValue); + setError(false); + setSuccess(false); + onChangeValue(e, currentValue); + }} + onCommitDate={handleCommitDate} + {...rest} + /> + ); +}; + +export const Default: StoryObj = { + args: { + label: 'Лейбл', + leftHelper: 'Подсказка к полю', + placeholder: '30.05.2024', + size: 'l', + view: 'default', + disabled: false, + readOnly: false, + textBefore: '', + enableContentLeft: true, + enableContentRight: true, + valueError: false, + valueSuccess: false, + }, + render: (args) => , +}; diff --git a/packages/plasma-new-hope/src/examples/plasma_b2c/components/DatePicker/DatePicker.ts b/packages/plasma-new-hope/src/examples/plasma_b2c/components/DatePicker/DatePicker.ts new file mode 100644 index 0000000000..adfa8a9fc2 --- /dev/null +++ b/packages/plasma-new-hope/src/examples/plasma_b2c/components/DatePicker/DatePicker.ts @@ -0,0 +1,7 @@ +import { component, mergeConfig } from '../../../../engines'; +import { datePickerConfig } from '../../../../components/DatePicker'; + +import { config } from './DatePicker.config'; + +const mergedConfig = mergeConfig(datePickerConfig, config); +export const DatePicker = component(mergedConfig); diff --git a/packages/plasma-new-hope/src/index.ts b/packages/plasma-new-hope/src/index.ts index 79922ab55b..755bef2310 100644 --- a/packages/plasma-new-hope/src/index.ts +++ b/packages/plasma-new-hope/src/index.ts @@ -48,3 +48,4 @@ export * from './components/Divider'; export * from './components/Toolbar'; export * from './components/Slider'; export * from './components/Range'; +export * from './components/DatePicker'; diff --git a/packages/plasma-new-hope/src/utils/datejs.ts b/packages/plasma-new-hope/src/utils/datejs.ts new file mode 100644 index 0000000000..ad70e58833 --- /dev/null +++ b/packages/plasma-new-hope/src/utils/datejs.ts @@ -0,0 +1,6 @@ +import dayjs from 'dayjs'; +import customParseFormat from 'dayjs/plugin/customParseFormat'; + +dayjs.extend(customParseFormat); + +export const customDayjs = dayjs; diff --git a/packages/plasma-new-hope/src/utils/getPopoverPlacement.ts b/packages/plasma-new-hope/src/utils/getPopoverPlacement.ts new file mode 100644 index 0000000000..588c3308e4 --- /dev/null +++ b/packages/plasma-new-hope/src/utils/getPopoverPlacement.ts @@ -0,0 +1,20 @@ +import { PopoverPlacementBasic } from '../components/Popover'; + +export type PlacementBasic = 'top' | 'bottom' | 'right' | 'left'; +export type Placement = PlacementBasic | 'auto'; + +export const getPlacement = (placement: Placement) => { + return `${placement}-start` as PopoverPlacementBasic; +}; + +export const getPlacements = (placements?: Placement | PlacementBasic[]) => { + if (!placements) { + return; + } + const isArray = Array.isArray(placements); + + if (!isArray) { + return getPlacement(placements as Placement); + } + return ((placements || []) as PlacementBasic[]).map((placement) => getPlacement(placement)); +}; diff --git a/packages/plasma-new-hope/src/utils/index.ts b/packages/plasma-new-hope/src/utils/index.ts index 6b9dfc2bfc..acac721b86 100644 --- a/packages/plasma-new-hope/src/utils/index.ts +++ b/packages/plasma-new-hope/src/utils/index.ts @@ -5,6 +5,7 @@ export { extractTextFrom } from './extractTextFrom'; export { getSizeValueFromProp } from './getSizeValueFromProp'; export { IS_REACT_18, safeUseId } from './react'; export { isNumber } from './isNumber'; +export * from './getPopoverPlacement'; export const cx = (...classes: (string | undefined)[]) => classes.filter((classItem) => classItem).join(' ');