minty/src/app/features/settings/account/fields/ProfileTimezone.tsx

160 lines
5.5 KiB
TypeScript

import FocusTrap from 'focus-trap-react';
import { Text, Overlay, OverlayBackdrop, OverlayCenter, Dialog, Header, config, Box, IconButton, Icon, Icons, Input, toRem, MenuItem, Button } from 'folds';
import React, { useRef, useState, useMemo, useCallback, useEffect } from 'react';
import { CutoutCard } from '../../../../components/cutout-card';
import { SettingTile } from '../../../../components/setting-tile';
import { FieldContext } from '../Profile';
import { ProfileFieldElementProps } from './ProfileFieldContext';
export function ProfileTimezone({
value, setValue, busy,
}: ProfileFieldElementProps<'us.cloke.msc4175.tz', FieldContext>) {
const disabled = busy;
const inputRef = useRef<HTMLInputElement>(null);
const scrollRef = useRef<HTMLDivElement>(null);
const [overlayOpen, setOverlayOpen] = useState(false);
const [query, setQuery] = useState('');
// @ts-expect-error Intl.supportedValuesOf isn't in the types yet
const timezones = useMemo(() => Intl.supportedValuesOf('timeZone') as string[], []);
const filteredTimezones = timezones.filter(
(timezone) => query.length === 0 || timezone.toLowerCase().replace('_', ' ').includes(query.toLowerCase())
);
const handleSelect = useCallback(
(timezone: string) => {
setOverlayOpen(false);
setValue(timezone);
},
[setOverlayOpen, setValue]
);
useEffect(() => {
if (overlayOpen) {
const scrollView = scrollRef.current;
const focusedItem = scrollView?.querySelector(`[data-tz="${value}"]`);
if (value && focusedItem && scrollView) {
focusedItem.scrollIntoView({
block: 'center',
});
}
}
}, [scrollRef, value, overlayOpen]);
return (
<SettingTile
title={<Text as="span" size="L400">
Timezone
</Text>}
>
<Overlay open={overlayOpen} backdrop={<OverlayBackdrop />}>
<OverlayCenter>
<FocusTrap
focusTrapOptions={{
initialFocus: () => inputRef.current,
allowOutsideClick: true,
clickOutsideDeactivates: true,
onDeactivate: () => setOverlayOpen(false),
escapeDeactivates: (evt) => {
evt.stopPropagation();
return true;
},
}}
>
<Dialog variant="Surface">
<Header
style={{
padding: `0 ${config.space.S200} 0 ${config.space.S400}`,
borderBottomWidth: config.borderWidth.B300,
}}
variant="Surface"
size="500"
>
<Box grow="Yes">
<Text size="H4">Choose a Timezone</Text>
</Box>
<IconButton size="300" onClick={() => setOverlayOpen(false)} radii="300">
<Icon src={Icons.Cross} />
</IconButton>
</Header>
<Box style={{ padding: config.space.S400 }} direction="Column" gap="400">
<Input
ref={inputRef}
size="500"
variant="Background"
radii="400"
outlined
placeholder="Search"
before={<Icon size="200" src={Icons.Search} />}
value={query}
onChange={(evt) => setQuery(evt.currentTarget.value)} />
<CutoutCard ref={scrollRef} style={{ overflowY: 'scroll', height: toRem(300) }}>
{filteredTimezones.length === 0 && (
<Box
style={{ paddingTop: config.space.S700 }}
grow="Yes"
alignItems="Center"
justifyContent="Center"
direction="Column"
gap="100"
>
<Text size="H6" align="Center">
No Results
</Text>
</Box>
)}
{filteredTimezones.map((timezone) => (
<MenuItem
key={timezone}
data-tz={timezone}
variant={timezone === value ? 'Success' : 'Surface'}
fill={timezone === value ? 'Soft' : 'None'}
size="300"
radii="0"
after={<Icon size="50" src={Icons.ChevronRight} />}
onClick={() => handleSelect(timezone)}
>
<Box grow="Yes">
<Text size="T200" truncate>
{timezone}
</Text>
</Box>
</MenuItem>
))}
</CutoutCard>
</Box>
</Dialog>
</FocusTrap>
</OverlayCenter>
</Overlay>
<Box gap="200">
<Button
variant="Secondary"
fill="Soft"
size="300"
radii="300"
outlined
disabled={disabled}
onClick={() => setOverlayOpen(true)}
after={<Icon size="100" src={Icons.ChevronRight} />}
>
<Text size="B300">{value ?? 'Set Timezone'}</Text>
</Button>
{value && (
<Button
size="300"
variant="Critical"
fill="None"
radii="300"
disabled={disabled}
onClick={() => setValue(undefined)}
>
<Text size="B300">Remove Timezone</Text>
</Button>
)}
</Box>
</SettingTile>
);
}