diff --git a/docs/examples/datepicker/main.tsx b/docs/examples/datepicker/main.tsx index 65a361fe39..f98d079ccd 100644 --- a/docs/examples/datepicker/main.tsx +++ b/docs/examples/datepicker/main.tsx @@ -14,7 +14,7 @@ export default function Example() { onChange={({ value }) => setDateValue(value)} value={dateValue} /> - {' '} + ); } diff --git a/docs/examples/datepicker/mobile.tsx b/docs/examples/datepicker/mobile.tsx new file mode 100644 index 0000000000..5cb29cd524 --- /dev/null +++ b/docs/examples/datepicker/mobile.tsx @@ -0,0 +1,19 @@ +import { useState } from 'react'; +import { DeviceTypeProvider } from 'gestalt'; +import { DatePicker } from 'gestalt-datepicker'; + +export default function Example() { + const [dateValue, setDateValue] = useState(new Date(1985, 6, 4)); + return ( + + setDateValue(value)} + value={dateValue} + /> + + ); +} diff --git a/docs/examples/defaultlabelprovider/translations.tsx b/docs/examples/defaultlabelprovider/translations.tsx index 7f2a053cb5..6cfecc5968 100644 --- a/docs/examples/defaultlabelprovider/translations.tsx +++ b/docs/examples/defaultlabelprovider/translations.tsx @@ -23,6 +23,8 @@ const labels = { iconAccessibilityLabelSuccess: myI18nTranslator('Success'), }, DatePicker: { + accessibilityDismissButtonLabel: myI18nTranslator('Dismiss date picker'), + dismissButton: myI18nTranslator('Close'), openCalendar: myI18nTranslator('Open calendar'), previousMonth: myI18nTranslator('Navigate to previou month'), nextMonth: myI18nTranslator('Navigate to next month'), diff --git a/docs/pages/web/datepicker.tsx b/docs/pages/web/datepicker.tsx index 63b2044636..9f109f38a7 100644 --- a/docs/pages/web/datepicker.tsx +++ b/docs/pages/web/datepicker.tsx @@ -58,6 +58,7 @@ import enabled from '../../examples/datepicker/enabled'; import error from '../../examples/datepicker/error'; import helperText from '../../examples/datepicker/helperText'; import main from '../../examples/datepicker/main'; +import mobile from '../../examples/datepicker/mobile'; import preselected from '../../examples/datepicker/preselected'; import range from '../../examples/datepicker/range'; import readOnly from '../../examples/datepicker/readOnly'; @@ -391,6 +392,22 @@ Read-only TextFields are used to present information to the user without allowin )} + + + + } + /> + + (initialDate); return ( setDate(value)} selectLists={showMonthYearDropdown ? ['year', 'month'] : undefined} value={date} @@ -140,4 +151,18 @@ describe('DatePicker', () => { expect(screen.queryAllByRole('option', { name: 'January' })).toHaveLength(1); expect(screen.queryAllByRole('option', { name: '2017' })).toHaveLength(1); }); + + test('Mobile Datepicker renders', async () => { + const { baseElement } = render( + + + , + ); + + fireEvent.focus(screen.getByDisplayValue('12/14/2018')); + + expect(baseElement).toMatchSnapshot(); + + expect(screen.getByText('Close')).toBeInTheDocument(); + }); }); diff --git a/packages/gestalt-datepicker/src/DatePicker.tsx b/packages/gestalt-datepicker/src/DatePicker.tsx index 10db7565ea..87ada798be 100644 --- a/packages/gestalt-datepicker/src/DatePicker.tsx +++ b/packages/gestalt-datepicker/src/DatePicker.tsx @@ -1,9 +1,33 @@ -import { forwardRef, ReactElement, useEffect, useImperativeHandle, useRef } from 'react'; +import { + forwardRef, + Fragment, + ReactElement, + useEffect, + useImperativeHandle, + useRef, + useState, +} from 'react'; import { Locale } from 'date-fns/locale'; -import { useGlobalEventsHandler } from 'gestalt'; +import { + Button, + Flex, + Layer, + SheetMobile, + useDefaultLabel, + useDeviceType, + useGlobalEventsHandler, +} from 'gestalt'; import InternalDatePicker from './DatePicker/InternalDatePicker'; +interface Indexable { + index(): number; +} + export type Props = { + /** + * DatePicker can adapt to mobile devices to [SheetMobile](https://gestalt.pinterest.systems/web/sheetmobile). Mobile adaptation is disabled by default. Set to 'false' to enable SheetMobile in mobile devices. See the [mobile variant](https://gestalt.pinterest.systems/web/datepicker#Mobile) to learn more. + */ + disableMobileUI?: boolean; /** * When disabled, DatePicker looks inactive and cannot be interacted with. See the [disabled example](https://gestalt.pinterest.systems/web/datepicker#States) to learn more. */ @@ -66,6 +90,14 @@ export type Props = { * Placeholder text shown if the user has not yet input a value. The default placeholder value shows the date format for each locale, e.g. MM/DD/YYYY. */ placeholder?: string; + /** + * Callback fired when SheetMobile's in & out animations end. See [SheetMobile's animation variant](https://gestalt.pinterest.systems/web/sheetmobile#Animation) to learn more. + */ + mobileOnAnimationEnd?: (arg1: { animationState: 'in' | 'out' }) => void; + /** + * An object representing the zIndex value of the SheetMobile where DatePicker is built upon on mobile. Learn more about [zIndex classes](https://gestalt.pinterest.systems/web/zindex_classes) + */ + mobileZIndex?: Indexable; /** * Required for date range selection. End date on a date range selection. See the [date range example](https://gestalt.pinterest.systems/web/datepicker#Date-range) to learn more. */ @@ -107,6 +139,7 @@ export type Props = { const DatePickerWithForwardRef = forwardRef(function DatePicker( { disabled, + disableMobileUI = false, errorMessage, excludeDates, helperText, @@ -117,6 +150,8 @@ const DatePickerWithForwardRef = forwardRef(function Da localeData, maxDate, minDate, + mobileZIndex, + mobileOnAnimationEnd, name, nextRef, onChange, @@ -139,10 +174,115 @@ const DatePickerWithForwardRef = forwardRef(function Da datePickerHandlers: undefined, }; + const { accessibilityDismissButtonLabel, dismissButton } = useDefaultLabel('DatePicker'); + + const [showMobileCalendar, setShowMobileCalendar] = useState(false); + + const deviceType = useDeviceType(); + const isMobile = deviceType === 'mobile'; + useEffect(() => { if (datePickerHandlers?.onRender) datePickerHandlers?.onRender(); }, [datePickerHandlers]); + if (isMobile && !disableMobileUI) { + return ( + + setShowMobileCalendar(true)} + placeholder={placeholder} + rangeEndDate={rangeEndDate} + rangeSelector={rangeSelector} + rangeStartDate={rangeStartDate} + readOnly={readOnly} + selectLists={selectLists} + value={value} + /> + {showMobileCalendar ? ( + + + {({ onDismissStart }) => ( + + + +
+
+
+ December 2018 +
+
+
+
+ Su +
+
+ Mo +
+
+ Tu +
+
+ We +
+
+ Th +
+
+ Fr +
+
+ Sa +
+
+
+
+
+
+ 25 +
+
+ 26 +
+
+ 27 +
+
+ 28 +
+
+ 29 +
+
+ 30 +
+
+ 1 +
+
+
+
+ 2 +
+
+ 3 +
+
+ 4 +
+
+ 5 +
+
+ 6 +
+
+ 7 +
+
+ 8 +
+
+
+
+ 9 +
+
+ 10 +
+
+ 11 +
+
+ 12 +
+
+ 13 +
+
+ 14 +
+
+ 15 +
+
+
+
+ 16 +
+
+ 17 +
+
+ 18 +
+
+ 19 +
+
+ 20 +
+
+ 21 +
+
+ 22 +
+
+
+
+ 23 +
+
+ 24 +
+
+ 25 +
+
+ 26 +
+
+ 27 +
+
+ 28 +
+
+ 29 +
+
+
+
+ 30 +
+
+ 31 +
+
+ 1 +
+
+ 2 +
+
+ 3 +
+
+ 4 +
+
+ 5 +
+
+
+
+
+ + + + + +
+
+
+
+ +
+
+
+
+ + + + + + +`; diff --git a/packages/gestalt/src/contexts/DefaultLabelProvider.jsdom.test.tsx b/packages/gestalt/src/contexts/DefaultLabelProvider.jsdom.test.tsx index 7139ecdcd1..8e20787af5 100644 --- a/packages/gestalt/src/contexts/DefaultLabelProvider.jsdom.test.tsx +++ b/packages/gestalt/src/contexts/DefaultLabelProvider.jsdom.test.tsx @@ -50,6 +50,8 @@ describe('useDefaultLabelContext', () => { accessibilityClearButtonLabel: 'Clear input', }, DatePicker: { + accessibilityDismissButtonLabel: 'Dismiss date picker', + dismissButton: 'Close', openCalendar: 'Open calendar', previousMonth: 'Navigate to previou month', nextMonth: 'Navigate to next month', diff --git a/packages/gestalt/src/contexts/DefaultLabelProvider.tsx b/packages/gestalt/src/contexts/DefaultLabelProvider.tsx index 9cb0caac32..edc28018c3 100644 --- a/packages/gestalt/src/contexts/DefaultLabelProvider.tsx +++ b/packages/gestalt/src/contexts/DefaultLabelProvider.tsx @@ -48,6 +48,8 @@ export type DefaultLabelContextType = { accessibilityClearButtonLabel: string; }; DatePicker: { + accessibilityDismissButtonLabel: string; + dismissButton: string; openCalendar: string; previousMonth: string; nextMonth: string; @@ -173,6 +175,8 @@ export const fallbackLabels: DefaultLabelContextType = { accessibilityClearButtonLabel: 'Clear input', }, DatePicker: { + accessibilityDismissButtonLabel: 'Dismiss date picker', + dismissButton: 'Close', openCalendar: 'Open calendar', previousMonth: 'Navigate to previou month', nextMonth: 'Navigate to next month',