Migrate to a monorepo structure (#2909)

This commit is contained in:
Charles Bochet
2023-12-10 18:10:54 +01:00
committed by GitHub
parent a70a9281eb
commit 5bdca9de6c
2304 changed files with 37152 additions and 25869 deletions

View File

@ -0,0 +1,253 @@
import React from 'react';
import ReactDatePicker from 'react-datepicker';
import styled from '@emotion/styled';
import { overlayBackground } from '@/ui/theme/constants/effects';
import 'react-datepicker/dist/react-datepicker.css';
const StyledContainer = styled.div`
& .react-datepicker {
border-color: ${({ theme }) => theme.border.color.light};
background: transparent;
font-family: 'Inter';
font-size: ${({ theme }) => theme.font.size.md};
border: none;
display: block;
font-weight: ${({ theme }) => theme.font.weight.regular};
}
& .react-datepicker-popper {
position: relative !important;
inset: auto !important;
transform: none !important;
padding: 0 !important;
}
& .react-datepicker__triangle::after {
display: none;
}
& .react-datepicker__triangle::before {
display: none;
}
& .react-datepicker-wrapper {
display: none;
}
// Header
& .react-datepicker__header {
background: transparent;
border: none;
}
& .react-datepicker__header__dropdown {
display: flex;
color: ${({ theme }) => theme.font.color.primary};
margin-left: ${({ theme }) => theme.spacing(1)};
margin-bottom: ${({ theme }) => theme.spacing(1)};
}
& .react-datepicker__month-dropdown-container,
& .react-datepicker__year-dropdown-container {
text-align: left;
border-radius: ${({ theme }) => theme.border.radius.sm};
margin-left: ${({ theme }) => theme.spacing(1)};
margin-right: 0;
padding: ${({ theme }) => theme.spacing(2)};
padding-right: ${({ theme }) => theme.spacing(4)};
background-color: ${({ theme }) => theme.background.tertiary};
}
& .react-datepicker__month-read-view--down-arrow,
& .react-datepicker__year-read-view--down-arrow {
height: 5px;
width: 5px;
border-width: 1px 1px 0 0;
border-color: ${({ theme }) => theme.border.color.light};
top: 3px;
right: -6px;
}
& .react-datepicker__year-read-view,
& .react-datepicker__month-read-view {
padding-right: ${({ theme }) => theme.spacing(2)};
}
& .react-datepicker__month-dropdown-container {
width: 80px;
}
& .react-datepicker__year-dropdown-container {
width: 50px;
}
& .react-datepicker__month-dropdown,
& .react-datepicker__year-dropdown {
border: ${({ theme }) => theme.border.color.light};
${overlayBackground}
overflow-y: scroll;
top: ${({ theme }) => theme.spacing(2)};
}
& .react-datepicker__month-dropdown {
left: ${({ theme }) => theme.spacing(2)};
width: 160px;
height: 260px;
}
& .react-datepicker__year-dropdown {
left: calc(${({ theme }) => theme.spacing(9)} + 80px);
width: 100px;
height: 260px;
}
& .react-datepicker__navigation--years {
display: none;
}
& .react-datepicker__month-option--selected,
& .react-datepicker__year-option--selected {
display: none;
}
& .react-datepicker__year-option,
& .react-datepicker__month-option {
text-align: left;
padding: ${({ theme }) => theme.spacing(2)}
calc(${({ theme }) => theme.spacing(2)} - 2px);
width: calc(100% - ${({ theme }) => theme.spacing(4)});
border-radius: ${({ theme }) => theme.border.radius.xs};
color: ${({ theme }) => theme.font.color.secondary};
cursor: pointer;
margin: 2px;
&:hover {
background: ${({ theme }) => theme.background.transparent.light};
}
}
& .react-datepicker__year-option {
&:first-of-type,
&:last-of-type {
display: none;
}
}
& .react-datepicker__current-month {
display: none;
}
& .react-datepicker__day-name {
color: ${({ theme }) => theme.font.color.secondary};
width: 34px;
height: 40px;
line-height: 40px;
}
& .react-datepicker__month-container {
float: none;
}
// Days
& .react-datepicker__month {
margin-top: 0;
}
& .react-datepicker__day {
width: 34px;
height: 34px;
line-height: 34px;
}
& .react-datepicker__navigation--previous,
& .react-datepicker__navigation--next {
height: 34px;
border-radius: ${({ theme }) => theme.border.radius.sm};
padding-top: 6px;
&:hover {
background: ${({ theme }) => theme.background.transparent.light};
}
}
& .react-datepicker__navigation--previous {
right: 38px;
top: 8px;
left: auto;
& > span {
margin-left: -6px;
}
}
& .react-datepicker__navigation--next {
right: 6px;
top: 8px;
& > span {
margin-left: 6px;
}
}
& .react-datepicker__navigation-icon::before {
height: 7px;
width: 7px;
border-width: 1px 1px 0 0;
border-color: ${({ theme }) => theme.font.color.tertiary};
}
& .react-datepicker__day--keyboard-selected {
background-color: inherit;
}
& .react-datepicker__day,
.react-datepicker__time-name {
color: ${({ theme }) => theme.font.color.primary};
}
& .react-datepicker__day--selected {
background-color: ${({ theme }) => theme.color.blue};
color: ${({ theme }) => theme.font.color.inverted};
}
& .react-datepicker__day--outside-month {
color: ${({ theme }) => theme.font.color.tertiary};
}
& .react-datepicker__day:hover {
color: ${({ theme }) => theme.font.color.tertiary};
}
`;
export type InternalDatePickerProps = {
date: Date;
onMouseSelect?: (date: Date) => void;
onChange?: (date: Date) => void;
};
export const InternalDatePicker = ({
date,
onChange,
onMouseSelect,
}: InternalDatePickerProps) => (
<StyledContainer>
<ReactDatePicker
open={true}
selected={date}
showMonthDropdown
showYearDropdown
onChange={() => {
// We need to use onSelect here but onChange is almost redundant with onSelect but is required
}}
customInput={<></>}
onSelect={(date: Date, event) => {
if (event?.type === 'click') {
onMouseSelect?.(date);
} else {
onChange?.(date);
}
}}
/>
</StyledContainer>
);

View File

@ -0,0 +1,48 @@
import { Meta, StoryObj } from '@storybook/react';
import { expect, userEvent, within } from '@storybook/test';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { InternalDatePicker } from '../InternalDatePicker';
const meta: Meta<typeof InternalDatePicker> = {
title: 'UI/Input/Internal/InternalDatePicker',
component: InternalDatePicker,
decorators: [ComponentDecorator],
argTypes: {
date: { control: 'date' },
},
args: { date: new Date('January 1, 2023 00:00:00') },
};
export default meta;
type Story = StoryObj<typeof InternalDatePicker>;
export const Default: Story = {};
export const WithOpenMonthSelect: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const monthSelect = canvas.getByText('January');
await userEvent.click(monthSelect);
expect(canvas.getAllByText('January')).toHaveLength(2);
[
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
].forEach((monthLabel) =>
expect(canvas.getByText(monthLabel)).toBeInTheDocument(),
);
},
};

View File

@ -0,0 +1,148 @@
import { useEffect, useMemo, useState } from 'react';
import { getCountries, getCountryCallingCode } from 'react-phone-number-input';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { hasFlag } from 'country-flag-icons';
import * as Flags from 'country-flag-icons/react/3x2';
import { CountryCallingCode } from 'libphonenumber-js';
import { IconChevronDown } from '@/ui/display/icon';
import { IconWorld } from '@/ui/input/constants/icons';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
import { CountryPickerHotkeyScope } from '../types/CountryPickerHotkeyScope';
import { CountryPickerDropdownSelect } from './CountryPickerDropdownSelect';
import 'react-phone-number-input/style.css';
type StyledDropdownButtonProps = {
isUnfolded: boolean;
};
export const StyledDropdownButtonContainer = styled.div<StyledDropdownButtonProps>`
align-items: center;
background: ${({ theme }) => theme.background.primary};
border-radius: ${({ theme }) => theme.border.radius.xs};
color: ${({ color }) => color ?? 'none'};
cursor: pointer;
display: flex;
height: 32px;
padding-left: ${({ theme }) => theme.spacing(2)};
padding-right: ${({ theme }) => theme.spacing(2)};
padding-right: ${({ theme }) => theme.spacing(2)};
user-select: none;
&:hover {
filter: brightness(0.95);
}
`;
const StyledIconContainer = styled.div`
align-items: center;
color: ${({ theme }) => theme.font.color.tertiary};
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
justify-content: center;
svg {
align-items: center;
display: flex;
height: 16px;
justify-content: center;
}
`;
export type Country = {
countryCode: string;
countryName: string;
callingCode: CountryCallingCode;
Flag: Flags.FlagComponent;
};
export const CountryPickerDropdownButton = ({
value,
onChange,
}: {
value: string;
onChange: (countryCode: string) => void;
}) => {
const theme = useTheme();
const [selectedCountry, setSelectedCountry] = useState<Country>();
const { isDropdownOpen, closeDropdown } = useDropdown({
dropdownScopeId: 'country-picker',
});
const handleChange = (countryCode: string) => {
onChange(countryCode);
closeDropdown();
};
const countries = useMemo<Country[]>(() => {
const regionNamesInEnglish = new Intl.DisplayNames(['en'], {
type: 'region',
});
const countryCodes = getCountries();
return countryCodes.reduce<Country[]>((result, countryCode) => {
const countryName = regionNamesInEnglish.of(countryCode);
if (!countryName) return result;
if (!hasFlag(countryCode)) return result;
const Flag = Flags[countryCode];
const callingCode = getCountryCallingCode(countryCode);
result.push({
countryCode,
countryName,
callingCode,
Flag,
});
return result;
}, []);
}, []);
useEffect(() => {
const country = countries.find(({ countryCode }) => countryCode === value);
if (country) {
setSelectedCountry(country);
}
}, [countries, value]);
return (
<DropdownScope dropdownScopeId="country-picker">
<Dropdown
dropdownHotkeyScope={{ scope: CountryPickerHotkeyScope.CountryPicker }}
clickableComponent={
<StyledDropdownButtonContainer isUnfolded={isDropdownOpen}>
<StyledIconContainer>
{selectedCountry ? <selectedCountry.Flag /> : <IconWorld />}
<IconChevronDown size={theme.icon.size.sm} />
</StyledIconContainer>
</StyledDropdownButtonContainer>
}
dropdownComponents={
<CountryPickerDropdownSelect
countries={countries}
selectedCountry={selectedCountry}
onChange={handleChange}
/>
}
dropdownPlacement="bottom-start"
dropdownOffset={{ x: 0, y: 4 }}
/>
</DropdownScope>
);
};

View File

@ -0,0 +1,98 @@
import { useMemo, useState } from 'react';
import styled from '@emotion/styled';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { MenuItemSelectAvatar } from '@/ui/navigation/menu-item/components/MenuItemSelectAvatar';
import { Country } from './CountryPickerDropdownButton';
import 'react-phone-number-input/style.css';
const StyledIconContainer = styled.div`
align-items: center;
color: ${({ theme }) => theme.font.color.tertiary};
display: flex;
padding-right: ${({ theme }) => theme.spacing(1)};
svg {
align-items: center;
border-radius: ${({ theme }) => theme.border.radius.xs};
display: flex;
height: 12px;
justify-content: center;
}
`;
export const CountryPickerDropdownSelect = ({
countries,
selectedCountry,
onChange,
}: {
countries: Country[];
selectedCountry?: Country;
onChange: (countryCode: string) => void;
}) => {
const [searchFilter, setSearchFilter] = useState<string>('');
const filteredCountries = useMemo(
() =>
countries.filter(({ countryName }) =>
countryName
.toLocaleLowerCase()
.includes(searchFilter.toLocaleLowerCase()),
),
[countries, searchFilter],
);
return (
<DropdownMenu width="200px" disableBlur>
<DropdownMenuSearchInput
value={searchFilter}
onChange={(event) => setSearchFilter(event.currentTarget.value)}
autoFocus
/>
<DropdownMenuSeparator />
<DropdownMenuItemsContainer hasMaxHeight>
{filteredCountries?.length === 0 ? (
<MenuItem text="No result" />
) : (
<>
{selectedCountry && (
<MenuItemSelectAvatar
key={selectedCountry.countryCode}
selected={true}
onClick={() => onChange(selectedCountry.countryCode)}
text={`${selectedCountry.countryName} (+${selectedCountry.callingCode})`}
avatar={
<StyledIconContainer>
<selectedCountry.Flag />
</StyledIconContainer>
}
/>
)}
{filteredCountries.map(
({ countryCode, countryName, callingCode, Flag }) =>
selectedCountry?.countryCode === countryCode ? null : (
<MenuItemSelectAvatar
key={countryCode}
selected={selectedCountry?.countryCode === countryCode}
onClick={() => onChange(countryCode)}
text={`${countryName} (+${callingCode})`}
avatar={
<StyledIconContainer>
<Flag />
</StyledIconContainer>
}
/>
),
)}
</>
)}
</DropdownMenuItemsContainer>
</DropdownMenu>
);
};

View File

@ -0,0 +1,3 @@
export enum CountryPickerHotkeyScope {
CountryPicker = 'country-picker',
}