From 44c9b5c96d30e7ef3b28e0a8f9c5ded626fb7f58 Mon Sep 17 00:00:00 2001 From: "Massimiliano D." <126668030+mdeliatf@users.noreply.github.com> Date: Fri, 9 Aug 2024 11:14:03 +0200 Subject: [PATCH] feat: add datetime picker --- .../DateTimePicker/DateTimePicker.stories.tsx | 53 ++++ components/DateTimePicker/DateTimePicker.tsx | 268 ++++++++++++++++++ components/DateTimePicker/index.ts | 1 + index.ts | 1 + package.json | 1 + vite.config.ts | 2 +- yarn.lock | 81 ++++++ 7 files changed, 406 insertions(+), 1 deletion(-) create mode 100644 components/DateTimePicker/DateTimePicker.stories.tsx create mode 100644 components/DateTimePicker/DateTimePicker.tsx create mode 100644 components/DateTimePicker/index.ts diff --git a/components/DateTimePicker/DateTimePicker.stories.tsx b/components/DateTimePicker/DateTimePicker.stories.tsx new file mode 100644 index 00000000..230746d3 --- /dev/null +++ b/components/DateTimePicker/DateTimePicker.stories.tsx @@ -0,0 +1,53 @@ +import { Meta, StoryFn } from '@storybook/react'; +import React, { useState } from 'react'; + +import { modifyVariantsForStory } from '../../utils/modifyVariantsForStory'; +import { Box } from '../Box'; +import { Flex } from '../Flex'; +import { Label } from '../Label'; +import { Text } from '../Text'; +import { DateTimePicker, DateTimePickerProps, DateTimePickerVariants } from './DateTimePicker'; + +const BaseDateTimePicker = (props: DateTimePickerProps): JSX.Element => ( + +); + +const DateTimePickerForStory = modifyVariantsForStory( + BaseDateTimePicker, +); + +const Component: Meta = { + title: 'Components/DateTimePicker', + component: DateTimePickerForStory, +}; + +const Template: StoryFn = (args) => { + const [date, setDate] = useState(new Date()); + + return ( +
+ + + + setDate(date)} + popperPlacement="bottom-start" + selected={date} + showIcon + /> + + + + {date.toISOString()} + + +
+ ); +}; + +export const Basic: StoryFn = Template.bind({}); + +export default Component; diff --git a/components/DateTimePicker/DateTimePicker.tsx b/components/DateTimePicker/DateTimePicker.tsx new file mode 100644 index 00000000..97d57b2d --- /dev/null +++ b/components/DateTimePicker/DateTimePicker.tsx @@ -0,0 +1,268 @@ +import 'react-datepicker/dist/react-datepicker.css'; + +import { CalendarIcon } from '@radix-ui/react-icons'; +import { addMonths, addYears } from 'date-fns'; +import React, { useEffect, useRef, useState } from 'react'; +import DatePicker, { CalendarContainer, DatePickerProps } from 'react-datepicker'; + +import { CSS, styled, VariantProps } from '../../stitches.config'; +import { Button } from '../Button'; +import { Card } from '../Card'; +import { Flex } from '../Flex'; +import { Input } from '../Input'; + +const StyledWrapper = styled('div', { + display: 'flex', + width: '100%', + + // Reset + outline: 'none', + lineHeight: 0, + + position: 'relative', + backgroundColor: '$inputBg', + color: '$inputPlaceholder', + + '&::before': { + boxSizing: 'border-box', + content: '""', + position: 'absolute', + inset: 0, + pointerEvents: 'none', + }, + '&::after': { + boxSizing: 'border-box', + content: '""', + position: 'absolute', + inset: 0, + pointerEvents: 'none', + }, + + '&:focus-visible': { + '&::before': { + backgroundColor: '$inputFocusBg', + }, + '&::after': { + backgroundColor: '$primary', + opacity: 0.15, + }, + }, + + '@hover': { + '&:hover': { + '&::before': { + backgroundColor: '$inputHoverBg', + }, + '&::after': { + backgroundColor: '$primary', + opacity: 0.05, + }, + }, + }, + + '> .react-datepicker-wrapper': { + width: '100%', + }, + + '.react-datepicker': { + backgroundColor: 'transparent', + border: '2px solid $colors$buttonSecondaryBorder', + borderRadius: '$3', + // border: 'none', + // boxShadow: 'inset 0 0 0 2px $colors$buttonSecondaryBorder', + color: '$textDefault', + fontFamily: '$rubik', + + '.react-datepicker__header': { + backgroundColor: 'transparent', + + '&::before': { + boxSizing: 'border-box', + content: '""', + position: 'absolute', + top: 0, + right: 0, + bottom: 0, + left: 0, + borderRadius: '$3', + backgroundColor: '$navButtonActiveBg', + }, + + '&::after': { + boxSizing: 'border-box', + content: '""', + position: 'absolute', + top: 0, + right: 0, + bottom: 0, + left: 0, + borderRadius: '$3', + backgroundColor: '$navButtonActiveBg2', + opacity: 0.05, + }, + + '.react-datepicker__current-month, .react-datepicker-time__header': { + color: '$textDefault', + }, + }, + + '.react-datepicker__navigation': { + '.react-datepicker__navigation-icon::before': { + borderColor: '$gray11', + }, + }, + + '.react-datepicker__month-container': { + '.react-datepicker__day': { + border: '1px solid transparent', + borderRadius: '$3', + boxSizing: 'border-box', + color: '$textDefault', + + '&--outside-month': { + color: '$textSubtle', + }, + + '&--today': { + border: '1px solid $buttonSecondaryBorder', + borderRadius: '$3', + fontWeight: 'normal', + }, + + '&:hover': { + backgroundColor: '$gray6', + }, + + '&--keyboard-selected': { + backgroundColor: 'inherit', + border: '1px solid $primary', + borderRadius: '$3', + }, + + '&--selected': { + backgroundColor: '$primary', + borderRadius: '$3', + color: '$buttonPrimaryText', + + '&:hover': { + backgroundColor: '$primary', + }, + }, + }, + + '.react-datepicker__day-name': { + color: '$textDefault', + }, + }, + + '.react-datepicker__time-container': { + '.react-datepicker__time': { + backgroundColor: 'transparent', + color: '$textDefault', + + '.react-datepicker__time-box': { + scrollbarColor: 'var(--colors-gray7) var(--colors-gray2)', + }, + + '.react-datepicker__time-list .react-datepicker__time-list-item': { + '&:hover': { + backgroundColor: '$gray6', + }, + + '&.react-datepicker__time-list-item--selected, &.react-datepicker__time-list-item--selected:hover': + { + backgroundColor: '$primary', + color: '$buttonPrimaryText', + fontWeight: 'normal', + }, + }, + }, + }, + }, + + '.react-datepicker-popper[data-placement] .react-datepicker__triangle': { + left: '50% !important', + transform: 'rotate(180deg) translateY(-1px) translateX(50%) !important', + color: '$01dp', + fill: '$01dp', + stroke: '$01dp', + }, +}); + +export type DateTimePickerProps = DatePickerProps & { + css?: CSS; +}; + +export type DateTimePickerVariants = VariantProps; + +export const DateTimePicker = React.forwardRef< + React.ElementRef, + DateTimePickerProps +>(({ css, onChange, selected, showIcon, ...props }, fowardedRef) => { + const datePickerRef = useRef(null); + + const [selectedDate, setSelectedDate] = useState(selected || new Date()); + + const CalendarContainerWrapper = ({ className, children }) => { + return ( + + +
{children}
+
+ + + + + +
+ ); + }; + + useEffect(() => { + if (onChange) onChange(selectedDate as Date & [Date | null, Date | null] & Date[]); + }, [onChange, selectedDate]); + + return ( + + : undefined} />} + ref={datePickerRef} + showTimeSelect + {...props} + selected={selectedDate} + onChange={(date: any) => setSelectedDate(date)} + /> + + ); +}); diff --git a/components/DateTimePicker/index.ts b/components/DateTimePicker/index.ts new file mode 100644 index 00000000..2ed355b3 --- /dev/null +++ b/components/DateTimePicker/index.ts @@ -0,0 +1 @@ +export * from './DateTimePicker'; diff --git a/index.ts b/index.ts index ab55bec4..4aaa7d4e 100644 --- a/index.ts +++ b/index.ts @@ -21,6 +21,7 @@ export { Checkbox } from './components/Checkbox'; export { Container } from './components/Container'; export { Elevation, elevationVariants } from './components/Elevation'; export { FaencyProvider } from './components/FaencyProvider'; +export { DateTimePicker } from './components/DateTimePicker'; export { Dialog, DialogCloseIconButton, diff --git a/package.json b/package.json index b2d39514..1a5788d8 100644 --- a/package.json +++ b/package.json @@ -117,6 +117,7 @@ "patch-package": "^8.0.0", "prettier": "^3.3.3", "react": "18.2.0", + "react-datepicker": "^7.3.0", "react-dom": "18.2.0", "rollup": "^2.70.1", "rollup-plugin-typescript2": "^0.36.0", diff --git a/vite.config.ts b/vite.config.ts index 6566ad1b..ed1f779d 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -3,7 +3,7 @@ import { defineConfig } from 'vite'; export default defineConfig({ optimizeDeps: { - exclude: ['storybook'], + exclude: ['react-datepicker', 'storybook'], }, plugins: [react()], }); diff --git a/yarn.lock b/yarn.lock index 6146c1e8..ce575f6a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3095,6 +3095,32 @@ __metadata: languageName: node linkType: hard +"@floating-ui/react-dom@npm:^2.1.1": + version: 2.1.1 + resolution: "@floating-ui/react-dom@npm:2.1.1" + dependencies: + "@floating-ui/dom": "npm:^1.0.0" + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: 10c0/732ab64600c511ceb0563b87bc557aa61789fec4f416a3f092bab89e508fa1d3ee5ade0f42051cc56eb5e4db867b87ab7fd48ce82db9fd4c01d94ffa08f60115 + languageName: node + linkType: hard + +"@floating-ui/react@npm:^0.26.2": + version: 0.26.20 + resolution: "@floating-ui/react@npm:0.26.20" + dependencies: + "@floating-ui/react-dom": "npm:^2.1.1" + "@floating-ui/utils": "npm:^0.2.5" + tabbable: "npm:^6.0.0" + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: 10c0/2fe96b8123734a9facb560b235bbb9a21849890f7a84a48c4e1d496dde045aafecb4575c3e035413f2d88a8324651c85b018bfbf8d57eb1fb9e469cdd211ca86 + languageName: node + linkType: hard + "@floating-ui/utils@npm:^0.2.0": version: 0.2.2 resolution: "@floating-ui/utils@npm:0.2.2" @@ -3102,6 +3128,13 @@ __metadata: languageName: node linkType: hard +"@floating-ui/utils@npm:^0.2.5": + version: 0.2.5 + resolution: "@floating-ui/utils@npm:0.2.5" + checksum: 10c0/9e1c7330433c3a8f226c5a44ed1dcdda13b313c4126ce3281f970d1e471b1c9fd9e1559cc76a0592af25d55a3f81afe1a5778aa7b80e51c9fa01930cd1d5557e + languageName: node + linkType: hard + "@gar/promisify@npm:^1.1.3": version: 1.1.3 resolution: "@gar/promisify@npm:1.1.3" @@ -6216,6 +6249,7 @@ __metadata: patch-package: "npm:^8.0.0" prettier: "npm:^3.3.3" react: "npm:18.2.0" + react-datepicker: "npm:^7.3.0" react-dom: "npm:18.2.0" rollup: "npm:^2.70.1" rollup-plugin-typescript2: "npm:^0.36.0" @@ -8388,6 +8422,13 @@ __metadata: languageName: node linkType: hard +"clsx@npm:^2.1.0": + version: 2.1.1 + resolution: "clsx@npm:2.1.1" + checksum: 10c0/c4c8eb865f8c82baab07e71bfa8897c73454881c4f99d6bc81585aecd7c441746c1399d08363dc096c550cceaf97bd4ce1e8854e1771e9998d9f94c4fe075839 + languageName: node + linkType: hard + "cmd-shim@npm:^5.0.0": version: 5.0.0 resolution: "cmd-shim@npm:5.0.0" @@ -8876,6 +8917,13 @@ __metadata: languageName: node linkType: hard +"date-fns@npm:^3.3.1": + version: 3.6.0 + resolution: "date-fns@npm:3.6.0" + checksum: 10c0/0b5fb981590ef2f8e5a3ba6cd6d77faece0ea7f7158948f2eaae7bbb7c80a8f63ae30b01236c2923cf89bb3719c33aeb150c715ea4fe4e86e37dcf06bed42fb6 + languageName: node + linkType: hard + "dateformat@npm:^3.0.0": version: 3.0.3 resolution: "dateformat@npm:3.0.3" @@ -15937,6 +15985,22 @@ __metadata: languageName: node linkType: hard +"react-datepicker@npm:^7.3.0": + version: 7.3.0 + resolution: "react-datepicker@npm:7.3.0" + dependencies: + "@floating-ui/react": "npm:^0.26.2" + clsx: "npm:^2.1.0" + date-fns: "npm:^3.3.1" + prop-types: "npm:^15.7.2" + react-onclickoutside: "npm:^6.13.0" + peerDependencies: + react: ^16.9.0 || ^17 || ^18 + react-dom: ^16.9.0 || ^17 || ^18 + checksum: 10c0/a926c896fbd827bd6195e5fdbf98bf48fc86a076112bcff38dcbd580da24a98c0c8f5ed1a1dfb2d91076134e41b209e118ccd41cbc3b2036e94c54af25e1788c + languageName: node + linkType: hard + "react-docgen-typescript@npm:^2.2.2": version: 2.2.2 resolution: "react-docgen-typescript@npm:2.2.2" @@ -16030,6 +16094,16 @@ __metadata: languageName: node linkType: hard +"react-onclickoutside@npm:^6.13.0": + version: 6.13.1 + resolution: "react-onclickoutside@npm:6.13.1" + peerDependencies: + react: ^15.5.x || ^16.x || ^17.x || ^18.x + react-dom: ^15.5.x || ^16.x || ^17.x || ^18.x + checksum: 10c0/93b02c493332ca7f8b480a1e185386c4af55d1daf33af2f68ddf440e4fc9b22ad13f59d7e204694aa31c1c2e332f0766c7ee302fe3b0cecbd0e0b45d55905c96 + languageName: node + linkType: hard + "react-refresh@npm:^0.14.2": version: 0.14.2 resolution: "react-refresh@npm:0.14.2" @@ -17762,6 +17836,13 @@ __metadata: languageName: node linkType: hard +"tabbable@npm:^6.0.0": + version: 6.2.0 + resolution: "tabbable@npm:6.2.0" + checksum: 10c0/ced8b38f05f2de62cd46836d77c2646c42b8c9713f5bd265daf0e78ff5ac73d3ba48a7ca45f348bafeef29b23da7187c72250742d37627883ef89cbd7fa76898 + languageName: node + linkType: hard + "tar@npm:^6.1.0, tar@npm:^6.1.11, tar@npm:^6.1.2, tar@npm:^6.2.0, tar@npm:^6.2.1": version: 6.2.1 resolution: "tar@npm:6.2.1"