Live Demo: https://seawind543.github.io/react-token-input/
React TokenInput (react-customize-token-input)
A react token (tag) controlled input component, which support:
- Accept customize data structure.
- Customize token (tag) Look & Feel on the
labelDemo,delete buttonDemo, or even overridethe whole Token componentDemo. - Customize separate characters to separate the end-user input string. Demo
- Inline editing on exist token.
- Paste values. Demo
- Preprocessing function to normalized user input value. It could be helpful to reproduce a single value into multiple values too. Demo
- Validate function.
- Install the latest version of react and react-customize-token-input:
yarn add react react-customize-token-input - At this point you can import
react-customize-token-inputand its styles in your application by:
import TokenInput from 'react-customize-token-input'; // Be sure to include styles at some point, probably during your bootstraping import 'react-customize-token-input/dist/react-customize-token-input.css'; // Could find the not minimize version to easily customize style from: // 'react-customize-token-input/dist/react-customize-token-input.original.css';- Run
yarn installto install required packages. - Run
yarn devto launchwebpack-dev-server. - After step 2, you will see following message output in command console.
「wds」: Project is running at http://0.0.0.0:8000/ 「wds」: webpack output is served from / 「wds」: Content not from webpack is served from ../docs Note: To stop the program, just type
ctrl + cin command console.
- After step 3 complete, you could access
http://localhost:8000/to see result.
See Live Examples: https://seawind543.github.io/react-token-input/
Note: Sources code of Examples in the folder examples/
/** * @template VT, ET * @typedef {Object} TokenInputProps */ interface TokenInputProps<VT = string, ET = string> { /** * @prop {CSSProperties} [style] * @description An optional prop, for assigning style to TokenInput */ style?: CSSProperties; /** * @prop {string} [className] * @description An optional prop, for assigning class name to TokenInput */ className?: string; /** * @prop {string} [placeholder] * @description An optional prop, for assigning placeholder to TokenInput */ placeholder?: string; /** * @prop {boolean} [readOnly = false] * @description An optional prop, to control TokenInput is `readOnly mode` */ readOnly?: boolean; /** * @prop {boolean} [disableCreateOnBlur] * @description An optional prop, to control TokenInput creates a new token when blurring on the creator */ disableCreateOnBlur?: boolean; /** * @prop {boolean} [autoFocus = false] * @description * An optional prop, to control TokenInput is `autoFocus mode`. * Will be deprecated in the next major release. Took ref.current.focus() instead. */ autoFocus?: boolean; /** * @template VT * @prop {VT[]} tokenValues * @description * The array of tokenValue of TokenInput. * This array will be used to render the tokens. * * Type: VT * Description: * Customize data structure data * Could be string | number | object | customized data structure...etc. */ tokenValues: VT[]; // TokenCreator props /** * @prop {TokenSeparator[]} [separators] * @description * An array of characters to split the user input string into array. * For example, * Split the user input string `abc;def` into `['abc', 'def']` * by separators `[';']` * * @see {@link TokenSeparator} * Note: * It take the `String.prototype.split(separators.join('|'))` * and `RegExp` to split the user input string. * * @example * ```js * value.split(separators.join('|')); * ``` * * Make sure your customized separators could be used with * (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp)[`RegExp`]}. */ separators?: TokenSeparator[]; /** * @prop {SpecialKeyDownConfig} [specialKeyDown=DEFAULT_SPECIAL_KEY_DOWN_CONFIG] * @description * [Beta; Might be change in the future version] * Current only apply to the `TokenCreator` * * The settings to control the behavior of specials keyDown's event handler. * Recommend to use the built-in constant `KEY_DOWN_HANDLER_CONFIG_OPTION` to config the setting. * * @see KEY_DOWN_HANDLER_CONFIG_OPTION for the accepted config values * @see DEFAULT_SPECIAL_KEY_DOWN_CONFIG for the default settings */ specialKeyDown?: SpecialKeyDownConfig; /** * @prop {OnInputValueChange} [onInputValueChange] * @description * A callback function invoked when end-user typing but not become token yet * * @example * ```js * onInputValueChange(newValue, previousValue) * ``` * * @param {InputString} newValue * The end-user's input string * * @param {InputString} previousValue * The previous input string * * @returns {void} */ onInputValueChange?: OnInputValueChange; /** * @prop {OnPreprocess} [onPreprocess] * @description * A callback function to `preprocessing` the user input string. * * Note: This function execute after `split by TokenSeparator[]` but before `onBuildTokenValue` * inputString -> spilt(inputString) -> preprocess(spilt(inputString)) -> onBuildTokenValue(preprocess(spilt(inputString))) * * [Use case 1] * Make your normalize process in this function, such as `String.prototype.trim()`. * * [Use case 2] * Sometimes, we will want to auto-fit the user input, this function could help with it. * For example, the user input string is `www.google.com`, * and we want to auto-fit it into `http://www.google.com` and `https://www.google.com`. * * @example * ```js * onPreprocess(inputStringValues) * ``` * * @param {InputString[]} inputStringValues * The user input string values * (An array of string, which split from the original input string via the `separators`) * * @returns {InputString[]} * An array of string */ onPreprocess?: OnPreprocess; /** * @template VT, ET * @prop {OnTokenValueValidate<VT, ET>} [onTokenValueValidate=defaultTokenValueValidate] * @description * A callback function to validate a tokenValue * (The returned result will be set into the TokenMeta & pass to `onGetTokenErrorMessage`) * * @example * ```js * onTokenValueValidate(tokenValue, index, tokenValues) * ``` * * @param {VT} tokenValue * The tokenValue built by `onBuildTokenValue` * * @param {Index} index * The array index of this tokenValue in tokenValues * * @param {VT[]} tokenValues * The array of tokenValue of TokenInput * * @returns {TokenMeta<ET>['error']} * The customized error. * Specific the token's validate status or errorMessage. * Could be `an error message` to display, or an error object for further operations. * * @see TokenMeta for more information about TokenMeta<ET>['error'] * * Note: Return `Nullish` types means the token is valid. * @see Nullish */ onTokenValueValidate?: OnTokenValueValidate<VT, ET>; // Token related props /** * @template VT * @prop {OnTokenValuesChange<VT>} [onTokenValuesChange] * @description * A callback function invoked when tokenValues update * * @example * ```js * onTokenValuesChange(modifiedTokenValues) * ``` * * @param {VT[]} modifiedTokenValues * The new tokenValues * * @returns {void} */ onTokenValuesChange?: OnTokenValuesChange<VT>; /** * @template VT * @prop {OnBuildTokenValue<VT>} [onBuildTokenValue=defaultBuildTokenValue] * @description * A callback function to build `user input string value` into * the `tokenValue` (customized data structure). * * Note: You could make your normalize process in this function too. * * @example * ```js * onBuildTokenValue(inputString) * ``` * * @param {InputString} inputString * The user input value // (A value split by TokenSeparator[]) * Example: * - Input string "ABC, DEF" and separators is `,` * - The `onBuildTokenValue` will be called twice as * ``` * onBuildTokenValue('ABC') and onBuildTokenValue('DEF') * ``` * * @returns {VT} * The customized data structure data * Could be string | number | object | customized data structure...etc. */ onBuildTokenValue?: OnBuildTokenValue<VT>; /** * @prop {Component} [customizeTokenComponent] * A customize react component to rendering a token * Apply this to customize all token function. * * @example * ```js * customizeTokenComponent={MyToken} * ``` * * @returns {ReactElement | null} */ customizeTokenComponent?: ( props: TokenProps<VT, ET> ) => ReactElement | null; /** * @template VT, ET * @prop {OnGetTokenClassName<VT, ET>} [onGetTokenClassName] * @description * A callback function to getting customizes `className` to set on a `token` * * ```js * onGetTokenClassName(tokenValue, tokenMeta) * ``` * * @param {VT} tokenValue * The tokenValue built by `onBuildTokenValue` * * @param {TokenMeta<ET>} tokenMeta * The token's meta data * * @returns {undefined | string} * The customizes className */ onGetTokenClassName?: OnGetTokenClassName<VT, ET>; /** * @template VT, ET * @prop {OnGetTokenDisplayLabel<VT, ET>} [onGetTokenDisplayLabel=defaultGetTokenEditableValue] * @description * A callback function to getting displayable `label` of a token * Apply this to customize the token's content * For example, render token with `icon` or `Additional text` * * @example * ```js * onGetTokenDisplayLabel(tokenValue, tokenMeta) * ``` * * @param {VT} tokenValue * The tokenValue built by `onBuildTokenValue` * * @param {TokenMeta<ET>} tokenMeta * The token's meta data * * @returns {InputString | ReactNode} * The token's display content. */ onGetTokenDisplayLabel?: OnGetTokenDisplayLabel<VT, ET>; /** * @prop {OnRenderTokenDeleteButtonContent} [onRenderTokenDeleteButtonContent] * @description * A callback function to render content of the delete button of token * Apply this to customize the token's content of the delete button. * For example, replace the built-in `x` by Google font material-icons * * @example * ```js * onRenderTokenDeleteButtonContent() * ``` * * @returns {ReactNode} * The content of the delete button of the token. * By default, TokenInput render a built-in `x` icon */ onRenderTokenDeleteButtonContent?: OnRenderTokenDeleteButtonContent; /** * @template VT, ET * @prop {OnGetIsTokenEditable<VT, ET>} [onGetIsTokenEditable=defaultGetIsTokenEditable] * @description * A callback function to determine whether the token is `inline editable`. * * @example * ```js * onGetIsTokenEditable(tokenValue, tokenMeta) * ``` * * @param {VT} tokenValue * The tokenValue built by `onBuildTokenValue` * * @param {TokenMeta<ET>} tokenMeta * The token's meta data * * @returns {boolean} * - `true`: Editable. * - `false`: Not editable. */ onGetIsTokenEditable?: OnGetIsTokenEditable<VT, ET>; /** * @template VT, ET * @prop {OnGetTokenEditableValue<VT, ET>} [onGetTokenEditableValue=defaultGetTokenEditableValue] * @description * A callback function to getting `string input value` * from `tokenValue` for the end-user to perform `inline edit` * * @example * ```js * onGetTokenEditableValue(tokenValue, tokenMeta) * ``` * * @param {VT} tokenValue * The tokenValue built by `onBuildTokenValue` * * @param {TokenMeta<ET>} tokenMeta * The token's meta data * * @returns {InputString} * The value for end-user to `edit` in an input field */ onGetTokenEditableValue?: OnGetTokenEditableValue<VT, ET>; /** * @template VT, ET * @prop {OnGetTokenErrorMessage<VT, ET>} [onGetTokenErrorMessage=defaultGetTokenErrorMessage] * @description * A callback function to getting the `Error Message` to * apply into the `title` attribute of the built-in Token Component * * @example * ```js * onGetTokenErrorMessage(tokenValue, tokenMeta) * ``` * * @param {VT} tokenValue * The tokenValue built by `onBuildTokenValue` * * @param {TokenMeta<ET>} tokenMeta * The token's meta data * * @returns {string | Nullish} * The `Error Message` of the token. * Return `string type` will let the built-in Token component apply the message * into the `title` attribute. Otherwise, will simply be ignored */ onGetTokenErrorMessage?: OnGetTokenErrorMessage<VT, ET>; /** * @prop {React.FocusEventHandler<HTMLInputElement>} [onCreatorFocus] * @description * A callback function invoked on TokenCreator focused * * @example * ```js * onCreatorFocus(e) * ``` * * @param {React.FocusEvent<HTMLInputElement>} event * The FocusEvent of the input of TokenCreator * * @returns {void} */ onCreatorFocus?: React.FocusEventHandler<HTMLInputElement>; /** * @prop {React.FocusEventHandler<HTMLInputElement>} [onCreatorBlur] * @description * A callback function invoked on TokenCreator blur * * @example * ```js * onCreatorBlur(e) * ``` * * @param {React.FocusEvent<HTMLInputElement>} event * The FocusEvent of the input of TokenCreator * * @returns {void} */ onCreatorBlur?: React.FocusEventHandler<HTMLInputElement>; /** * @prop {React.KeyboardEventHandler<HTMLInputElement>} [onCreatorKeyDown] * @description * A callback function invoked when keyDown on TokenCreator * * @example * ```js * onCreatorKeyDown(e) * ``` * * @param {React.KeyboardEvent<HTMLInputElement>} event * The KeyboardEvent of the input of TokenCreator * * @returns {void} */ onCreatorKeyDown?: React.KeyboardEventHandler<HTMLInputElement>; }TokenInput provide the following method in the ref of it.
| Method | Description | Parameter | Return |
|---|---|---|---|
| focus | Set focus on TokenInput. It will focus on the creator not the inline-editor | Same as HTMLElement.focus() | void |
| setCreatorValue | Set value of TokenCreator | value: string | void |
| getCreatorValue | Get value of TokenCreator | void | string |
| createTokens | Trigger tokens create. If param.value undefined, then apply the value of TokenCreator directly. | value?: string | void |
Could reference Demo, and its source code ExampleRefMethods in the folder examples/.
If you are using TypeScript, reference the code below for the typing of useRef.
import TokenInput, { type TokenInputRef } from 'react-customize-token-input'; const tokenInputRef = useRef<TokenInputRef>(null); // ... omit const handleFocusButtonClick = () => { tokenInputRef.current?.focus(); } // ... omit <TokenInput ref={tokenInputRef} tokenValues={values} onTokenValuesChange={setValues} />TokenInput has the following Predefined KeyDown event handlers.
| KeyDown | Description | Note |
|---|---|---|
| Backspace | In case the current inputValue is an empty string, the latest token in the list tail will be deleted. | |
| Escape | Clear the input-box's value. | A.K.A. Reset. |
| Enter | Create a token with the inputValue and continually focused on the inputBox for the next inputting. | |
| Tab | Same as onEnter. |
|
| KeyDown | Description | Note |
|---|---|---|
| Escape | End editing without change the value of the token. | A.K.A. Reset |
| Enter | End editing and apply the new value. In case the new value is an empty string, will perform the onEscape. |
style = undefined, className = undefined, placeholder = undefined, readOnly = false, disableCreateOnBlur = undefined, autoFocus = false, // TokenCreator separators = DEFAULT_SEPARATORS, /* [ ',', ';', '\n', // for copy and paste '\r', // for copy and paste '\r\n', // for copy and paste ]; */ specialKeyDown = DEFAULT_SPECIAL_KEY_DOWN_CONFIG, /* { onBackspace: KEY_DOWN_HANDLER_CONFIG_OPTION.ON, onTab: KEY_DOWN_HANDLER_CONFIG_OPTION.OFF, onEnter: KEY_DOWN_HANDLER_CONFIG_OPTION.ON, onEscape: KEY_DOWN_HANDLER_CONFIG_OPTION.ON, }, */ onInputValueChange = undefined, onPreprocess = undefined, onTokenValueValidate = defaultTokenValueValidate, onTokenValuesChange = undefined, // Token onBuildTokenValue = defaultBuildTokenValue, customizeTokenComponent = undefined, onGetTokenClassName = undefined, onGetTokenDisplayLabel = defaultGetTokenEditableValue, onRenderTokenDeleteButtonContent = undefined, onGetIsTokenEditable = defaultGetIsTokenEditable, onGetTokenEditableValue = defaultGetTokenEditableValue, onGetTokenErrorMessage = defaultGetTokenErrorMessage,Your CustomizeTokenComponent will receive these props from TokenInput. You could decide where & how to use them to customize your Token component.
Could also reference this Demo and its source code ExampleCustomizeToken in the folder examples/.
/** * @template VT, ET * @typedef {Object} TokenProps */ export interface TokenProps<VT = string, ET = string> { /** * @property {boolean} readOnly * @description * Same as TokenInputProps {@see 'TokenInputProps['readOnly']} */ readOnly: boolean; /** * @type {VT} * @description This token's tokenValue */ tokenValue: VT; /** * @template ET * @type {TokenMeta<ET>} tokenMeta * @description This token's meta data */ tokenMeta: TokenMeta<ET>; /** * @template VT, ET * @prop {OnGetTokenClassName<VT, ET>} [onGetClassName] * @description * Same as TokenInputProps {@see TokenInputProps['onGetTokenClassName']} */ onGetClassName?: OnGetTokenClassName<VT, ET>; /** * @template VT, ET * @prop {OnGetTokenDisplayLabel<VT, ET>} [onGetTokenDisplayLabel=defaultGetTokenEditableValue] * @description * Same as TokenInputProps {@see TokenInputProps['onGetTokenDisplayLabel']} */ onGetDisplayLabel: OnGetTokenDisplayLabel<VT, ET>; /** * @callback OnRenderTokenDeleteButtonContent * @description * Same as TokenInputProps {@see TokenInputProps['onRenderTokenDeleteButtonContent']} */ onRenderDeleteButtonContent?: OnRenderTokenDeleteButtonContent; /** * @template VT, ET * @callback OnGetIsTokenEditable * @description * Same as TokenInputProps {@see TokenInputProps['onGetIsTokenEditable']} */ onGetIsEditable: OnGetIsTokenEditable<VT, ET>; /** * @template VT, ET * @callback OnGetTokenEditableValue * @description * Same as TokenInputProps {@see TokenInputProps['onGetTokenEditableValue']} */ onGetEditableValue: OnGetTokenEditableValue<VT, ET>; /** * @template VT * @callback OnBuildTokenValue * @description * Same as TokenInputProps {@see TokenInputProps['onBuildTokenValue']} */ onBuildTokenValue: OnBuildTokenValue<VT>; /** * @template VT, ET * @callback OnGetTokenErrorMessage * @description * Same as TokenInputProps {@see TokenInputProps['onGetTokenErrorMessage']} */ onGetErrorMessage: OnGetTokenErrorMessage<VT, ET>; /** * @callback * @description * A callback function, which you should `call` * when the end-user `start editing` * * Note: * Call this function to tell TokenInput it is start to editing the token. * As result, TokenInput will set `tokenMeta.activate` to `true` * * @example * ```js * onEditStart() * ``` * * @returns {void} */ onEditStart: () => void; /** * @callback * @description * A callback function, which you should `call` * when end-user `end the edit` * * Note: * Call this function to tell TokenInput to finish the `editing` of the token. * As result, TokenInput will set `tokenMeta.activate` to `false`. * * Also, TokenInput will based on the value of the parameter newTokenValue to * update the tokenValue of the token, * and call the callback `onTokenValuesChange` * * @example * ```js * onEditEnd(newTokenValue); * // or * onEditEnd(); * ``` * * @param {VT} [newTokenValue] * The new tokenValue built by `onBuildTokenValue. * * Note: * if `newTokenValue` is `undefined`, * TokenInput will treat as `Cancel` (Edit will end without update the tokenValue). * The callback `onTokenValuesChange` will also not be called. * * @returns {void} */ onEditEnd: (newTokenValue?: VT) => void; /** * @callback * @description * A callback function, which you should `call` * when the end-user `delete` the token * * Note: * Call this function to tell TokenInput to delete the token. * As result, TokenInput will remove the token, * and call `onTokenValuesChange` to update tokenValues. * * @example * ```js * onDelete() * ``` * * @returns {void} */ onDelete: () => void; }