Skip to content

Commit c0e32d8

Browse files
authored
Port components to ts 6 (#2743)
* Toggle Prettier toggle files * validation-message * vertical-list * Prettier
1 parent 504f5cf commit c0e32d8

File tree

18 files changed

+266
-194
lines changed

18 files changed

+266
-194
lines changed

src/app/components/form-radiogroup/form-radiogroup.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, {useState} from 'react';
22
import {treatSpaceOrEnterAsClick} from '~/helpers/events';
3+
import type { ElementWithValidationMessage } from '../validation-message/validation-message';
34

45
type OptionItem = {
56
value: string;
@@ -37,10 +38,6 @@ function Option({
3738
);
3839
}
3940

40-
type InputElementWithValidationMessage = HTMLInputElement & {
41-
validationMessage: string;
42-
};
43-
4441
export default function FormRadioGroup({
4542
longLabel,
4643
name,
@@ -58,7 +55,7 @@ export default function FormRadioGroup({
5855
const [selectedValue, setSelectedValue] = useState(checkedValue);
5956
const validate = React.useCallback(() => {
6057
const invalid =
61-
ref.current?.querySelector<InputElementWithValidationMessage>(
58+
ref.current?.querySelector<ElementWithValidationMessage>(
6259
':invalid'
6360
);
6461

src/app/components/multiselect/book-tags/book-tags.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@ import React from 'react';
22
import Multiselect, {MultiselectContextProvider} from '../multiselect';
33
import useSFBookContext, {SFBookContextProvider} from './sf-book-context';
44
import useMultiselectContext from '../multiselect-context';
5-
import useToggleContext, {
6-
ToggleContextProvider
7-
} from '~/components/toggle/toggle-context';
8-
import {IfToggleIsOpen} from '~/components/toggle/toggle';
5+
import useToggleContext from '~/components/toggle/toggle-context';
6+
import Toggle, {IfToggleIsOpen} from '~/components/toggle/toggle';
97
import ToggleControlBar from '~/components/toggle/toggle-control-bar';
108
import ArrowToggle from '~/components/toggle/arrow-toggle';
119
import BookOptions from './book-options';
@@ -107,14 +105,14 @@ type MultiselectArgs = Parameters<typeof Multiselect>[0];
107105
export default function BookTagsMultiselect(passThruProps: MultiselectArgs) {
108106
return (
109107
<Multiselect {...passThruProps}>
110-
<ToggleContextProvider>
108+
<Toggle>
111109
<ToggleControlBar Indicator={ArrowToggle}>
112110
<TagList />
113111
</ToggleControlBar>
114112
<IfToggleIsOpen>
115113
<BookOptions />
116114
</IfToggleIsOpen>
117-
</ToggleContextProvider>
115+
</Toggle>
118116
</Multiselect>
119117
);
120118
}

src/app/components/multiselect/multiselect.tsx

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ import React from 'react';
22
import useMultiselectContext, {
33
MultiselectContextProvider
44
} from './multiselect-context';
5-
import ValidationMessage from '~/components/validation-message/validation-message';
5+
import ValidationMessage, { ElementWithValidationMessage }
6+
from '~/components/validation-message/validation-message';
67
import './multiselect.scss';
78

8-
type ElementRef = React.MutableRefObject<
9-
HTMLInputElement | HTMLSelectElement | null
10-
>;
9+
type ValidatableElementRef = React.MutableRefObject<ElementWithValidationMessage & HTMLInputElement>;
10+
type SelectRef = React.MutableRefObject<HTMLSelectElement>;
1111

1212
function HiddenSelect({
1313
name,
@@ -16,7 +16,7 @@ function HiddenSelect({
1616
}: {
1717
name: string;
1818
required?: boolean;
19-
elementRef: ElementRef;
19+
elementRef: SelectRef;
2020
}) {
2121
const {selectedItems} = useMultiselectContext();
2222

@@ -25,7 +25,7 @@ function HiddenSelect({
2525
multiple
2626
name={name}
2727
required={required}
28-
ref={elementRef as React.MutableRefObject<HTMLSelectElement>}
28+
ref={elementRef}
2929
hidden
3030
>
3131
{selectedItems.map((i) => (
@@ -42,7 +42,7 @@ function HiddenSingleField({
4242
}: {
4343
name: string;
4444
required?: boolean;
45-
elementRef: ElementRef;
45+
elementRef: ValidatableElementRef;
4646
}) {
4747
const {selectedItems} = useMultiselectContext();
4848
const value = selectedItems.map((i) => i.value).join(';');
@@ -53,13 +53,13 @@ function HiddenSingleField({
5353
className="hidden-input"
5454
name={name}
5555
required={required}
56-
ref={elementRef as React.MutableRefObject<HTMLInputElement>}
56+
ref={elementRef}
5757
value={value}
5858
/>
5959
);
6060
}
6161

62-
function MSValidationMessage({elementRef}: {elementRef: ElementRef}) {
62+
function MSValidationMessage({elementRef}: {elementRef: ValidatableElementRef}) {
6363
const {selectedItems} = useMultiselectContext();
6464

6565
return (
@@ -81,6 +81,17 @@ function useOnChangeHandler(onChange?: (items: unknown[]) => void) {
8181
// Exporting here for convenience
8282
export {MultiselectContextProvider, useMultiselectContext};
8383

84+
function HiddenField({oneField, name, required, elementRef}: {
85+
oneField?: boolean;
86+
name: string;
87+
required?: boolean;
88+
elementRef: React.MutableRefObject<HTMLElement | null>
89+
}) {
90+
return oneField ?
91+
<HiddenSingleField {...{name, required}} elementRef={elementRef as ValidatableElementRef} /> :
92+
<HiddenSelect name={name} required={required} elementRef={elementRef as SelectRef} />;
93+
}
94+
8495
export default function Multiselect({
8596
name,
8697
required,
@@ -93,17 +104,16 @@ export default function Multiselect({
93104
oneField?: boolean;
94105
onChange?: Parameters<typeof useOnChangeHandler>[0];
95106
}>) {
96-
const elementRef = React.useRef<HTMLInputElement | HTMLSelectElement>(null);
97-
const HiddenField = oneField ? HiddenSingleField : HiddenSelect;
107+
const elementRef = React.useRef<ElementWithValidationMessage | HTMLSelectElement>(null);
98108

99109
useOnChangeHandler(onChange);
100110
return (
101111
<React.Fragment>
102112
<div className="multiselect">
103-
{name && <HiddenField {...{name, required, elementRef}} />}
113+
{name && <HiddenField {...{oneField, name, required, elementRef}} />}
104114
{children}
105115
</div>
106-
<MSValidationMessage elementRef={elementRef} />
116+
<MSValidationMessage elementRef={elementRef as ValidatableElementRef} />
107117
</React.Fragment>
108118
);
109119
}

src/app/components/select/drop-down/drop-down.tsx

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,19 @@
11
import React from 'react';
22
import Select from '../select';
33
import useSelectContext, {SelectItem} from '../select-context';
4-
import useToggleContext, {
5-
ToggleContextProvider
6-
} from '~/components/toggle/toggle-context';
4+
import useToggleContext from '~/components/toggle/toggle-context';
5+
import Toggle from '~/components/toggle/toggle';
76
import ToggleControlBar from '~/components/toggle/toggle-control-bar';
87
import ArrowToggle from '~/components/toggle/arrow-toggle';
9-
import VerticalList from '~/components/vertical-list/vertical-list';
8+
import VerticalList, {RenderItemProps} from '~/components/vertical-list/vertical-list';
109
import './drop-down.scss';
1110

1211
function RenderItem({
1312
item,
1413
current,
1514
onMouseEnter,
1615
onClick
17-
}: {
18-
item: SelectItem;
19-
current: boolean;
20-
onMouseEnter: React.MouseEventHandler;
21-
onClick: React.MouseEventHandler;
22-
}) {
16+
}: RenderItemProps<SelectItem>) {
2317
const {item: selectedItem} = useSelectContext();
2418

2519
return (
@@ -95,12 +89,12 @@ export default function DropDownSelect({
9589
} & Omit<Parameters<typeof Select>[0], 'children'>) {
9690
return (
9791
<Select {...passThruProps}>
98-
<ToggleContextProvider>
92+
<Toggle>
9993
<ToggleControlBar Indicator={ArrowToggle}>
10094
<SelectedItem placeholder={placeholder} />
10195
</ToggleControlBar>
10296
<AutoFocusVerticalList options={options} />
103-
</ToggleContextProvider>
97+
</Toggle>
10498
</Select>
10599
);
106100
}

src/app/components/select/select.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react';
22
import useSelectContext, {SelectContextProvider} from './select-context';
3-
import ValidationMessage from '~/components/validation-message/validation-message';
3+
import ValidationMessage, { ElementWithValidationMessage } from '~/components/validation-message/validation-message';
44

55
function HiddenSelect({
66
name,
@@ -9,7 +9,7 @@ function HiddenSelect({
99
}: {
1010
name: string;
1111
required?: boolean;
12-
elementRef: React.RefObject<HTMLSelectElement>;
12+
elementRef: React.MutableRefObject<HTMLSelectElement | null>;
1313
}) {
1414
const {item} = useSelectContext();
1515

@@ -23,11 +23,17 @@ function HiddenSelect({
2323
function SValidationMessage({
2424
elementRef
2525
}: {
26-
elementRef: React.RefObject<HTMLSelectElement>;
26+
elementRef: React.MutableRefObject<HTMLSelectElement & ElementWithValidationMessage | null>;
2727
}) {
2828
const {item} = useSelectContext();
2929

30-
return <ValidationMessage watchValue={item} elementRef={elementRef} />;
30+
if (elementRef.current === null) {
31+
return null;
32+
}
33+
34+
return <ValidationMessage
35+
watchValue={item} elementRef={elementRef as React.MutableRefObject<ElementWithValidationMessage>}
36+
/>;
3137
}
3238

3339
export default function Select({

src/app/components/toggle/arrow-toggle.js

Lines changed: 0 additions & 11 deletions
This file was deleted.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import React from 'react';
2+
import cn from 'classnames';
3+
import './arrow-toggle.scss';
4+
5+
export default function ArrowToggle({isOpen}: {isOpen: boolean}) {
6+
return <span className={cn('arrow-toggle with-arrow', {open: isOpen})} />;
7+
}

src/app/components/toggle/toggle-context.js renamed to src/app/components/toggle/toggle-context.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ function useContextValue() {
88

99
// Multiple events might try to toggle the same direction and cancel each
1010
// other out. This throws away the later calls within 1/8 second.
11-
const dbToggle = useMemo(() => throttle(toggle, 125, {trailing: false}), [toggle]);
11+
const dbToggle = useMemo(
12+
() => throttle(toggle, 125, {trailing: false}),
13+
[toggle]
14+
);
1215

1316
return {isOpen, toggle: dbToggle, close: () => dbToggle(false)};
1417
}
1518

1619
const {useContext, ContextProvider} = buildContext({useContextValue});
1720

18-
export {
19-
useContext as default,
20-
ContextProvider as ToggleContextProvider
21-
};
21+
export {useContext as default, ContextProvider as ToggleContextProvider};

src/app/components/toggle/toggle-control-bar.js renamed to src/app/components/toggle/toggle-control-bar.tsx

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
11
import React from 'react';
22
import useToggleContext from './toggle-context';
3-
import { useRefToFocusAfterClose } from './toggle';
3+
import {useRefToFocusAfterClose} from './toggle';
44
import cn from 'classnames';
5-
import { treatKeydownAsClick } from '~/helpers/events';
5+
import {treatKeydownAsClick} from '~/helpers/events';
66
import './toggle-control-bar.scss';
77

8-
export default function ToggleControlBar({ Indicator, children }) {
9-
const { isOpen, toggle } = useToggleContext();
8+
export type ToggleFunction = ({isOpen}: {isOpen: boolean}) => JSX.Element;
9+
10+
export default function ToggleControlBar({
11+
Indicator,
12+
children
13+
}: React.PropsWithChildren<{
14+
Indicator: ToggleFunction;
15+
}>) {
16+
const {isOpen, toggle} = useToggleContext();
1017
const [hasBeenOpened, setHasBeenOpened] = React.useState(false);
1118
const onKeyDown = React.useCallback(
12-
(event) => {
19+
(event: React.KeyboardEvent) => {
1320
const keyList = isOpen ? ['Escape', 'Enter', ' '] : ['Enter', ' '];
1421

1522
treatKeydownAsClick(event, keyList);
@@ -25,28 +32,31 @@ export default function ToggleControlBar({ Indicator, children }) {
2532
}
2633
}, [isOpen]);
2734

35+
// eslint-disable-next-line complexity
2836
React.useEffect(() => {
2937
if (hasBeenOpened && !isOpen) {
3038
// Restore focus to an input if there is one, otherwise to focusRef
3139
const focusOn =
32-
focusRef.current.querySelector('input') || focusRef.current;
40+
focusRef?.current?.querySelector('input') || focusRef.current;
3341

34-
focusOn.focus();
42+
focusOn?.focus();
3543
}
3644
}, [hasBeenOpened, isOpen, focusRef]);
3745

3846
return (
3947
<div
40-
className={cn('toggle-control-bar', { open: isOpen })}
41-
tabIndex="0"
48+
className={cn('toggle-control-bar', {open: isOpen})}
49+
tabIndex={0}
4250
onClick={() => toggle()}
4351
onKeyDown={onKeyDown}
4452
ref={focusRef}
4553
role="combobox"
4654
aria-controls={listboxId}
47-
aria-haspopup={listboxId}
55+
aria-haspopup="listbox"
4856
>
49-
<div role="listbox" id={listboxId}>{children}</div>
57+
<div role="listbox" id={listboxId}>
58+
{children}
59+
</div>
5060
<Indicator isOpen={isOpen} />
5161
</div>
5262
);
Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,30 @@
11
import React from 'react';
2-
import useToggleContext, {ToggleContextProvider} from '~/components/toggle/toggle-context';
2+
import useToggleContext, {
3+
ToggleContextProvider
4+
} from '~/components/toggle/toggle-context';
35

46
export function useRefToFocusAfterClose() {
57
const {isOpen} = useToggleContext();
68
const [hasBeenOpened, setHasBeenOpened] = React.useState(false);
7-
const ref = React.useRef();
9+
const ref = React.useRef<HTMLDivElement>(null);
810

911
React.useEffect(() => {
1012
if (isOpen) {
1113
setHasBeenOpened(true);
1214
} else if (hasBeenOpened) {
13-
ref.current.focus();
15+
ref.current?.focus();
1416
}
1517
}, [isOpen, hasBeenOpened]);
1618

1719
return ref;
1820
}
1921

20-
export function IfToggleIsOpen({children}) {
22+
export function IfToggleIsOpen({children}: React.PropsWithChildren<object>) {
2123
const {isOpen} = useToggleContext();
2224

2325
return isOpen && children;
2426
}
2527

26-
export default function Toggle({children}) {
27-
return (
28-
<ToggleContextProvider>
29-
{children}
30-
</ToggleContextProvider>
31-
);
28+
export default function Toggle({children}: React.PropsWithChildren<object>) {
29+
return <ToggleContextProvider>{children}</ToggleContextProvider>;
3230
}

0 commit comments

Comments
 (0)