Skip to content

Commit 79f74b9

Browse files
Add generic option type
1 parent bcbef91 commit 79f74b9

29 files changed

+166
-145
lines changed

src/behaviors/async.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { optionType } from '../propTypes';
1818
import { getDisplayName, isFunction } from '../utils';
1919

2020
import { TypeaheadComponentProps } from '../components/Typeahead';
21-
import type { Option } from '../types';
21+
import type { OptionType } from '../types';
2222

2323
const propTypes = {
2424
/**
@@ -57,7 +57,7 @@ const propTypes = {
5757
useCache: PropTypes.bool,
5858
};
5959

60-
export interface UseAsyncProps extends TypeaheadComponentProps {
60+
export interface UseAsyncProps<Option extends OptionType> extends TypeaheadComponentProps<Option> {
6161
delay?: number;
6262
isLoading: boolean;
6363
onSearch: (query: string) => void;
@@ -66,7 +66,7 @@ export interface UseAsyncProps extends TypeaheadComponentProps {
6666
useCache?: boolean;
6767
}
6868

69-
type Cache = Record<string, Option[]>;
69+
type Cache = Record<string, OptionType[]>;
7070

7171
interface DebouncedFunction extends Function {
7272
cancel(): void;
@@ -80,7 +80,7 @@ interface DebouncedFunction extends Function {
8080
* - Optional query caching
8181
* - Search prompt and empty results behaviors
8282
*/
83-
export function useAsync(props: UseAsyncProps) {
83+
export function useAsync<Option extends OptionType>(props: UseAsyncProps<Option>) {
8484
const {
8585
allowNew,
8686
delay = 200,

src/behaviors/item.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { useTypeaheadContext } from '../core/Context';
1414
import { getDisplayName, getMenuItemId, preventInputBlur } from '../utils';
1515

1616
import { optionType } from '../propTypes';
17-
import { Option } from '../types';
17+
import { OptionType } from '../types';
1818

1919
const propTypes = {
2020
option: optionType.isRequired,
@@ -23,7 +23,7 @@ const propTypes = {
2323

2424
export interface UseItemProps<T> extends HTMLProps<T> {
2525
onClick?: MouseEventHandler<T>;
26-
option: Option;
26+
option: OptionType;
2727
position: number;
2828
}
2929

src/behaviors/token.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@ import { useRootClose } from 'react-overlays';
1414
import { getDisplayName, isFunction } from '../utils';
1515

1616
import { optionType } from '../propTypes';
17-
import { Option, OptionHandler } from '../types';
17+
import { OptionType, OptionHandler } from '../types';
1818

19-
export interface UseTokenProps<T> extends Omit<HTMLProps<T>, 'onBlur'> {
19+
export interface UseTokenProps<T, Option extends OptionType> extends Omit<HTMLProps<T>, 'onBlur'> {
2020
// `onBlur` is typed more generically because it's passed to `useRootClose`,
2121
// which passes a generic event to the callback.
2222
onBlur?: (event: Event) => void;
2323
onClick?: MouseEventHandler<T>;
2424
onFocus?: FocusEventHandler<T>;
25-
onRemove?: OptionHandler;
25+
onRemove?: OptionHandler<Option>;
2626
option: Option;
2727
}
2828

@@ -34,14 +34,14 @@ const propTypes = {
3434
option: optionType.isRequired,
3535
};
3636

37-
export function useToken<T extends HTMLElement>({
37+
export function useToken<T extends HTMLElement, Option extends OptionType>({
3838
onBlur,
3939
onClick,
4040
onFocus,
4141
onRemove,
4242
option,
4343
...props
44-
}: UseTokenProps<T>) {
44+
}: UseTokenProps<T, Option>) {
4545
const [active, setActive] = useState<boolean>(false);
4646
const [rootElement, attachRef] = useState<T | null>(null);
4747

src/components/MenuItem/MenuItem.stories.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,15 @@ import {
99
TypeaheadContext,
1010
TypeaheadContextType,
1111
} from '../../core/Context';
12+
import {OptionType} from "../../types";
1213

1314
export default {
1415
title: 'Components/MenuItem/MenuItem',
1516
component: MenuItem,
1617
} as Meta;
1718

1819
interface Args {
19-
context: Partial<TypeaheadContextType>;
20+
context: Partial<TypeaheadContextType<OptionType>>;
2021
props: MenuItemProps;
2122
}
2223

src/components/Token/Token.stories.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@ import { Story, Meta } from '@storybook/react';
55

66
import Token, { TokenProps } from './Token';
77
import { noop } from '../../utils';
8+
import {OptionType} from "../../types";
89

910
export default {
1011
title: 'Components/Token',
1112
component: Token,
1213
} as Meta;
1314

14-
const Template: Story<TokenProps<HTMLElement>> = (args) => <Token {...args} />;
15+
const Template: Story<TokenProps<HTMLElement, OptionType>> = (args) => <Token {...args} />;
1516

1617
export const Interactive = Template.bind({});
1718
Interactive.args = {

src/components/Token/Token.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import ClearButton from '../ClearButton';
55

66
import { useToken, UseTokenProps } from '../../behaviors/token';
77
import { isFunction } from '../../utils';
8+
import {OptionType} from "../../types";
89

910
type HTMLElementProps = Omit<HTMLProps<HTMLDivElement>, 'onBlur' | 'ref'>;
1011

@@ -68,7 +69,7 @@ const StaticToken = ({
6869
return <div className={classnames}>{children}</div>;
6970
};
7071

71-
export interface TokenProps<T> extends UseTokenProps<T> {
72+
export interface TokenProps<T, Option extends OptionType> extends UseTokenProps<T, Option> {
7273
disabled?: boolean;
7374
readOnly?: boolean;
7475
}
@@ -77,12 +78,12 @@ export interface TokenProps<T> extends UseTokenProps<T> {
7778
* Individual token component, generally displayed within the
7879
* `TypeaheadInputMulti` component, but can also be rendered on its own.
7980
*/
80-
const Token = ({
81+
const Token = <Option extends OptionType>({
8182
children,
8283
option,
8384
readOnly,
8485
...props
85-
}: TokenProps<HTMLElement>) => {
86+
}: TokenProps<HTMLElement, Option>) => {
8687
const { ref, ...tokenProps } = useToken({ ...props, option });
8788
const child = <div className="rbt-token-label">{children}</div>;
8889

src/components/Typeahead/Typeahead.stories.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ import Hint from '../Hint';
99
import Menu from '../Menu';
1010
import MenuItem from '../MenuItem';
1111

12-
import options, { Option } from '../../tests/data';
12+
import options, { TestOption } from '../../tests/data';
1313
import { noop } from '../../tests/helpers';
14+
import {OptionType} from "../../types";
1415

1516
export default {
1617
title: 'Components/Typeahead',
@@ -53,7 +54,7 @@ const defaultProps = {
5354
positionFixed: true,
5455
};
5556

56-
const Template: Story<TypeaheadComponentProps> = (args) => (
57+
const Template: Story<TypeaheadComponentProps<TestOption>> = (args) => (
5758
<Typeahead {...args} />
5859
);
5960

@@ -122,7 +123,7 @@ CustomMenu.args = {
122123
renderMenu: (results, menuProps) => (
123124
<Menu {...menuProps}>
124125
{/* Use `slice` to avoid mutating the original array */}
125-
{(results as Option[])
126+
{(results as TestOption[])
126127
.slice()
127128
.reverse()
128129
.map((r, index) => (
@@ -134,7 +135,7 @@ CustomMenu.args = {
134135
),
135136
};
136137

137-
export const InputGrouping = (args: TypeaheadComponentProps) => (
138+
export const InputGrouping = <Option extends OptionType>(args: TypeaheadComponentProps<Option>) => (
138139
<div
139140
className={cx('input-group', {
140141
'input-group-sm': args.size === 'sm',
@@ -149,7 +150,7 @@ InputGrouping.args = {
149150
...defaultProps,
150151
};
151152

152-
export const Controlled = (args: TypeaheadComponentProps) => {
153+
export const Controlled = <Option extends OptionType>(args: TypeaheadComponentProps<Option>) => {
153154
const [selected, setSelected] = useState(args.selected || []);
154155

155156
return <Typeahead {...args} onChange={setSelected} selected={selected} />;

src/components/Typeahead/Typeahead.test.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
waitFor,
2424
} from '../../tests/helpers';
2525

26-
import states, { Option } from '../../tests/data';
26+
import states, {TestOption} from '../../tests/data';
2727

2828
const ID = 'rbt-id';
2929

@@ -37,7 +37,7 @@ const inputProps = {
3737
type: 'number',
3838
};
3939

40-
const TestComponent = forwardRef<Typeahead, Partial<TypeaheadComponentProps>>(
40+
const TestComponent = forwardRef<Typeahead<TestOption>, Partial<TypeaheadComponentProps<TestOption>>>(
4141
(props, ref) => (
4242
<TypeaheadComponent
4343
id={ID}
@@ -115,7 +115,7 @@ describe('<Typeahead>', () => {
115115
});
116116

117117
it('truncates selections when using `defaultSelected`', () => {
118-
let selected: Option[] = states.slice(0, 4);
118+
let selected: TestOption[] = states.slice(0, 4);
119119
render(
120120
<Default defaultSelected={selected}>
121121
{(state) => {
@@ -410,7 +410,7 @@ describe('<Typeahead>', () => {
410410

411411
describe('updates when re-rendering with new props', () => {
412412
it('acts as a controlled input in single-select mode', () => {
413-
let selected: Option[] = [];
413+
let selected: TestOption[] = [];
414414

415415
const children = (state) => {
416416
selected = state.selected;
@@ -458,7 +458,7 @@ describe('<Typeahead>', () => {
458458
});
459459

460460
it('updates the selections and input value in single-select mode', async () => {
461-
let selected: Option[] = [];
461+
let selected: TestOption[] = [];
462462
const user = userEvent.setup();
463463

464464
render(
@@ -1121,7 +1121,7 @@ describe('<Typeahead>', () => {
11211121
});
11221122

11231123
it('adds the custom option when `allowNew` is set to `true`', async () => {
1124-
let selected: Option[] = [];
1124+
let selected: TestOption[] = [];
11251125
const user = userEvent.setup();
11261126

11271127
render(

0 commit comments

Comments
 (0)