From 165a4cc469ab6222ac5c77dbbd388f49f21d58f0 Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 8 Jun 2022 12:05:41 -0400 Subject: [PATCH 1/3] Add new Datepicker component --- .../datepicker/__tests__/datepicker.test.tsx | 83 ++++++++++++++++ .../components/ui/datepicker/datepicker.tsx | 94 +++++++++++++++++++ app/soapbox/components/ui/index.ts | 1 + app/soapbox/locales/en.json | 3 + 4 files changed, 181 insertions(+) create mode 100644 app/soapbox/components/ui/datepicker/__tests__/datepicker.test.tsx create mode 100644 app/soapbox/components/ui/datepicker/datepicker.tsx diff --git a/app/soapbox/components/ui/datepicker/__tests__/datepicker.test.tsx b/app/soapbox/components/ui/datepicker/__tests__/datepicker.test.tsx new file mode 100644 index 000000000..5fe6ca9d6 --- /dev/null +++ b/app/soapbox/components/ui/datepicker/__tests__/datepicker.test.tsx @@ -0,0 +1,83 @@ +import userEvent from '@testing-library/user-event'; +import React from 'react'; + +import { queryAllByRole, render, screen } from '../../../../jest/test-helpers'; +import Datepicker from '../datepicker'; + +describe('', () => { + it('defaults to the current date', () => { + const handler = jest.fn(); + render(); + const today = new Date(); + + expect(screen.getByTestId('datepicker-month')).toHaveValue(String(today.getMonth())); + expect(screen.getByTestId('datepicker-day')).toHaveValue(String(today.getDate())); + expect(screen.getByTestId('datepicker-year')).toHaveValue(String(today.getFullYear())); + }); + + it('changes number of days based on selected month and year', async() => { + const handler = jest.fn(); + render(); + + await userEvent.selectOptions( + screen.getByTestId('datepicker-month'), + screen.getByRole('option', { name: 'February' }), + ); + + await userEvent.selectOptions( + screen.getByTestId('datepicker-year'), + screen.getByRole('option', { name: '2020' }), + ); + + let daySelect: HTMLElement; + daySelect = document.querySelector('[data-testid="datepicker-day"]'); + expect(queryAllByRole(daySelect, 'option')).toHaveLength(29); + + await userEvent.selectOptions( + screen.getByTestId('datepicker-year'), + screen.getByRole('option', { name: '2021' }), + ); + + daySelect = document.querySelector('[data-testid="datepicker-day"]') as HTMLElement; + expect(queryAllByRole(daySelect, 'option')).toHaveLength(28); + }); + + it('ranges from the current year to 120 years ago', () => { + const handler = jest.fn(); + render(); + const today = new Date(); + + const yearSelect = document.querySelector('[data-testid="datepicker-year"]') as HTMLElement; + expect(queryAllByRole(yearSelect, 'option')).toHaveLength(121); + expect(queryAllByRole(yearSelect, 'option')[0]).toHaveValue(String(today.getFullYear())); + expect(queryAllByRole(yearSelect, 'option')[120]).toHaveValue(String(today.getFullYear() - 120)); + }); + + it('calls the onChange function when the inputs change', async() => { + const handler = jest.fn(); + render(); + + expect(handler.mock.calls.length).toEqual(1); + + await userEvent.selectOptions( + screen.getByTestId('datepicker-month'), + screen.getByRole('option', { name: 'February' }), + ); + + expect(handler.mock.calls.length).toEqual(2); + + await userEvent.selectOptions( + screen.getByTestId('datepicker-year'), + screen.getByRole('option', { name: '2020' }), + ); + + expect(handler.mock.calls.length).toEqual(3); + + await userEvent.selectOptions( + screen.getByTestId('datepicker-day'), + screen.getByRole('option', { name: '5' }), + ); + + expect(handler.mock.calls.length).toEqual(4); + }); +}); diff --git a/app/soapbox/components/ui/datepicker/datepicker.tsx b/app/soapbox/components/ui/datepicker/datepicker.tsx new file mode 100644 index 000000000..3c3c9b8e2 --- /dev/null +++ b/app/soapbox/components/ui/datepicker/datepicker.tsx @@ -0,0 +1,94 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { FormattedMessage, useIntl } from 'react-intl'; + +import Select from '../select/select'; +import Stack from '../stack/stack'; +import Text from '../text/text'; + +const getDaysInMonth = (month: number, year: number) => new Date(year, month + 1, 0).getDate(); +const currentYear = new Date().getFullYear(); + +interface IDatepicker { + onChange(date: Date): void +} + +/** + * Datepicker that allows a user to select month, day, and year. + */ +const Datepicker = ({ onChange }: IDatepicker) => { + const intl = useIntl(); + + const [month, setMonth] = useState(new Date().getMonth()); + const [day, setDay] = useState(new Date().getDate()); + const [year, setYear] = useState(2022); + + const numberOfDays = useMemo(() => { + return getDaysInMonth(month, year); + }, [month, year]); + + useEffect(() => { + onChange(new Date(year, month, day)); + }, [month, day, year]); + + return ( +
+
+ + + + + + + +
+ +
+ + + + + + + +
+ +
+ + + + + + + +
+
+ ); +}; + +export default Datepicker; diff --git a/app/soapbox/components/ui/index.ts b/app/soapbox/components/ui/index.ts index 24324ee02..27acc184b 100644 --- a/app/soapbox/components/ui/index.ts +++ b/app/soapbox/components/ui/index.ts @@ -4,6 +4,7 @@ export { Card, CardBody, CardHeader, CardTitle } from './card/card'; export { default as Checkbox } from './checkbox/checkbox'; export { default as Column } from './column/column'; export { default as Counter } from './counter/counter'; +export { default as Datepicker } from './datepicker/datepicker'; export { default as Emoji } from './emoji/emoji'; export { default as EmojiSelector } from './emoji-selector/emoji-selector'; export { default as FileInput } from './file-input/file-input'; diff --git a/app/soapbox/locales/en.json b/app/soapbox/locales/en.json index e6267ac94..74957a487 100644 --- a/app/soapbox/locales/en.json +++ b/app/soapbox/locales/en.json @@ -344,6 +344,9 @@ "crypto_donate_panel.actions.view": "Click to see {count} {count, plural, one {wallet} other {wallets}}", "crypto_donate_panel.heading": "Donate Cryptocurrency", "crypto_donate_panel.intro.message": "{siteTitle} accepts cryptocurrency donations to fund our service. Thank you for your support!", + "datepicker.month": "Month", + "datepicker.day": "Day", + "datepicker.year": "Year", "datepicker.hint": "Scheduled to post at…", "datepicker.next_month": "Next month", "datepicker.next_year": "Next year", From 5900068144073f6fbbdab8df8d936b91dd7e5ed0 Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 8 Jun 2022 12:06:05 -0400 Subject: [PATCH 2/3] Use new Datepicker on AgeVerification step --- app/soapbox/components/ui/select/select.tsx | 8 ++++-- .../verification/steps/age-verification.js | 26 +++---------------- app/styles/forms.scss | 2 ++ 3 files changed, 11 insertions(+), 25 deletions(-) diff --git a/app/soapbox/components/ui/select/select.tsx b/app/soapbox/components/ui/select/select.tsx index 485b3238c..f79d71ddd 100644 --- a/app/soapbox/components/ui/select/select.tsx +++ b/app/soapbox/components/ui/select/select.tsx @@ -1,13 +1,17 @@ import * as React from 'react'; +interface ISelect extends React.SelectHTMLAttributes { + children: Iterable, +} + /** Multiple-select dropdown. */ -const Select = React.forwardRef((props, ref) => { +const Select = React.forwardRef((props, ref) => { const { children, ...filteredProps } = props; return (