Skip to content

Commit

Permalink
Consolidate types
Browse files Browse the repository at this point in the history
  • Loading branch information
ericgio committed Feb 4, 2025
1 parent 1d29d7a commit faa336e
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 194 deletions.
65 changes: 53 additions & 12 deletions src/components/Typeahead/Typeahead.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
import cx from 'classnames';
import React, { CSSProperties, forwardRef, ReactNode, useState } from 'react';
import React, {
CSSProperties,
forwardRef,
InputHTMLAttributes,
ReactNode,
useState,
} from 'react';

import { TypeaheadContext, TypeaheadRef, useTypeahead } from '../../core';
import {
TypeaheadContext,
TypeaheadProps,
TypeaheadRef,
useTypeahead,
} from '../../core';
import { useOverlay } from '../../hooks';

import ClearButton from '../ClearButton';
Expand All @@ -23,22 +34,32 @@ import {

import {
Align,
LabelKey,
Option,
RenderToken,
RenderTokenProps,
OptionHandler,
Size,
TypeaheadInputProps,
TypeaheadProps,
TypeaheadChildProps,
TypeaheadChildren,
SelectEvent,
} from '../../types';

export interface RenderMenuProps extends MenuProps {
newSelectionPrefix?: ReactNode;
onItemSelect: (option: Option) => void;
paginationText?: ReactNode;
renderMenuItemChildren?: RenderMenuItemChildren;
export type TypeaheadChildren =
| ReactNode
| ((props: TypeaheadChildProps) => ReactNode);

export interface TypeaheadChildProps {
getInputProps: (
props?: InputHTMLAttributes<HTMLInputElement>
) => Omit<TypeaheadInputProps, 'referenceElementRef'>;
hideMenu: () => void;
isMenuShown: boolean;
labelKey: LabelKey;
onClear: () => void;
onHide: () => void;
onRemove: OptionHandler;
results: Option[];
selected: Option[];
text: string;
toggleMenu: () => void;
}

export interface TypeaheadComponentProps extends TypeaheadProps {
Expand Down Expand Up @@ -113,6 +134,13 @@ export interface TypeaheadComponentProps extends TypeaheadProps {
style?: CSSProperties;
}

export interface RenderMenuProps extends MenuProps {
newSelectionPrefix?: ReactNode;
onItemSelect: (option: Option) => void;
paginationText?: ReactNode;
renderMenuItemChildren?: RenderMenuItemChildren;
}

const defaultRenderMenu = (
results: Option[],
menuProps: RenderMenuProps,
Expand All @@ -126,6 +154,19 @@ const defaultRenderMenu = (
/>
);

export interface RenderTokenProps {
disabled?: boolean;
labelKey: LabelKey;
onRemove?: OptionHandler;
tabIndex?: number;
}

export type RenderToken = (
option: Option,
props: RenderTokenProps,
idx: number
) => JSX.Element;

const defaultRenderToken = (
option: Option,
props: RenderTokenProps,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default {
component: TypeaheadInputSingle,
} as Meta;

interface Args extends Omit<TypeaheadInputProps, 'size'> {
interface Args extends TypeaheadInputProps {
hintText?: string;
isInvalid?: boolean;
isValid?: boolean;
Expand Down
132 changes: 130 additions & 2 deletions src/core/useTypeahead.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import isEqual from 'fast-deep-equal';
import React, {
ChangeEvent,
FocusEventHandler,
InputHTMLAttributes,
KeyboardEventHandler,
Ref,
useCallback,
useEffect,
Expand All @@ -21,10 +25,12 @@ import {
import useValidateProps from './useValidateProps';

import {
AllowNew,
FilterByCallback,
InternalProps,
Id,
LabelKey,
Option,
TypeaheadProps,
SelectHint,
TypeaheadState,
} from '../types';

Expand Down Expand Up @@ -147,6 +153,128 @@ export interface TypeaheadRef {
toggleMenu: () => void;
}

export interface TypeaheadProps {
/**
* Allows the creation of new selections on the fly. Note that any new items
* will be added to the list of selections, but not the list of original
* options unless handled as such by `Typeahead`'s parent.
*
* If a function is specified, it will be used to determine whether a custom
* option should be included. The return value should be true or false.
*/
allowNew?: AllowNew;
/**
* Autofocus the input when the component initially mounts.
*/
autoFocus?: boolean;
/**
* Whether or not filtering should be case-sensitive.
*/
caseSensitive?: boolean;
/**
* The initial value displayed in the text input.
*/
defaultInputValue?: string;
/**
* Whether or not the menu is displayed upon initial render.
*/
defaultOpen?: boolean;
/**
* Specify any pre-selected options. Use only if you want the component to
* be uncontrolled.
*/
defaultSelected?: Option[];
/**
* Either an array of fields in `option` to search, or a custom filtering
* callback.
*/
filterBy?: string[] | FilterByCallback;
/**
* Highlights the menu item if there is only one result and allows selecting
* that item by hitting enter. Does not work with `allowNew`.
*/
highlightOnlyResult?: boolean;
/**
* An html id attribute, required for assistive technologies such as screen
* readers.
*/
id?: Id;
/**
* Whether the filter should ignore accents and other diacritical marks.
*/
ignoreDiacritics?: boolean;
inputProps?: InputHTMLAttributes<HTMLInputElement>;
/**
* Specify the option key to use for display or a function returning the
* display string. By default, the selector will use the `label` key.
*/
labelKey?: LabelKey;
/**
* Number of input characters that must be entered before showing results.
*/
minLength?: number;
/**
* Whether or not multiple selections are allowed.
*/
multiple?: boolean;
/**
* Invoked when the input is blurred. Receives an event.
*/
onBlur?: FocusEventHandler<HTMLInputElement>;
/**
* Invoked whenever items are added or removed. Receives an array of the
* selected options.
*/
onChange?: (selected: Option[]) => void;
/**
* Invoked when the input is focused. Receives an event.
*/
onFocus?: FocusEventHandler<HTMLInputElement>;
/**
* Invoked when the input value changes. Receives the string value of the
* input.
*/
onInputChange?: (text: string, event: ChangeEvent<HTMLInputElement>) => void;
/**
* Invoked when a key is pressed. Receives an event.
*/
onKeyDown?: KeyboardEventHandler<HTMLInputElement>;
/**
* Invoked when menu visibility changes.
*/
onMenuToggle?: (isOpen: boolean) => void;

/**
* Whether or not the menu should be displayed. `undefined` allows the
* component to control visibility, while `true` and `false` show and hide
* the menu, respectively.
*/
open?: boolean;
/**
* Full set of options, including pre-selected options. Must either be an
* array of objects (recommended) or strings.
*/
options: Option[];
/**
* The selected option(s) displayed in the input. Use this prop if you want
* to control the component via its parent.
*/
selected?: Option[];
selectHint?: SelectHint;
}

type OptionalProps<T, K extends keyof T> = Partial<Pick<T, K>> &
Required<Omit<T, K>>;

/**
* Most props used internally become "required" since they're given default
* values.
*/
export type InternalProps = OptionalProps<
Required<Omit<TypeaheadProps, 'onChange'>>,
'id' | 'open' | 'selected' | 'selectHint'
>;

function useTypeahead(
{ onChange, ...partialProps }: TypeaheadProps,
ref?: Ref<TypeaheadRef>
Expand Down
7 changes: 4 additions & 3 deletions src/core/useValidateProps.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { useEffect } from 'react';
import { InputProps, TypeaheadProps } from '../types';
import { HTMLAttributes, useEffect } from 'react';

import { TypeaheadProps } from './useTypeahead';
import { isFunction, warn } from '../utils';

interface InputPropItem {
alt: string;
prop: keyof InputProps;
prop: keyof HTMLAttributes<HTMLInputElement>;
}

const INPUT_PROPS_BLACKLIST: InputPropItem[] = [
Expand Down
3 changes: 2 additions & 1 deletion src/hooks/useAsync.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import usePrevious from '@restart/hooks/usePrevious';

import { isFunction } from '../utils';

import type { Option, TypeaheadProps } from '../types';
import { TypeaheadProps } from '../core';
import type { Option } from '../types';

export interface UseAsyncProps extends TypeaheadProps {
/**
Expand Down
Loading

0 comments on commit faa336e

Please sign in to comment.