UNPKG

react-customize-token-input

Version:

A react token (tag) input component. Allow customize data structure and Look & Feel

763 lines (674 loc) 23.1 kB
# React TokenInput [![build status](https://travis-ci.org/seawind543/react-token-input.svg?branch=master)](https://travis-ci.org/seawind543/react-token-input) [![Coverage Status](https://coveralls.io/repos/github/seawind543/react-token-input/badge.svg?branch=master)](https://coveralls.io/github/seawind543/react-token-input?branch=master) [![NPM](https://nodei.co/npm/react-customize-token-input.png?downloads=true&stars=true)](https://www.npmjs.com/package/react-customize-token-input/) 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 `label` [Demo](https://seawind543.github.io/react-token-input/#example-customize-label), `delete button` [Demo](https://seawind543.github.io/react-token-input/#example-customize-delete-button), or even override `the whole Token component` [Demo](https://seawind543.github.io/react-token-input/#example-customize-token-component). - Customize **separate characters** to separate the end-user input string. [Demo](https://seawind543.github.io/react-token-input/#example-customize-separators) - **Inline editing** on exist token. - **Paste** values. [Demo](https://seawind543.github.io/react-token-input/#example-customize-separators) - **Preprocessing** function to **normalized** user input value. It could be helpful to reproduce a single value into multiple values too. [Demo](https://seawind543.github.io/react-token-input/#example-preprocess) - **Validate** function. ## Installation 1. Install the latest version of [react](https://github.com/facebook/react) and [react-customize-token-input](https://github.com/seawind543/react-token-input): ``` yarn add react react-customize-token-input ``` 2. At this point you can import `react-customize-token-input` and its styles in your application by: ```JavaScript 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'; ``` ## Dev 1. Run `yarn install` to install required packages. 2. Run `yarn dev` to launch `webpack-dev-server`. 3. 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 + c``` in command console. 4. After step 3 complete, you could access `http://localhost:8000/` to see result. ## Usage See Live Examples: https://seawind543.github.io/react-token-input/ Note: Sources code of Examples in the folder `examples/` ## Props ```JavaScript /** * @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>; } ``` ## Methods in ref of TokenInput 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()](https://developer.mozilla.org/en-US/docs/Web/API/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](https://seawind543.github.io/react-token-input/#example-ref-methods), and its source code `ExampleRefMethods` in the folder `examples/`. If you are using TypeScript, reference the code below for the typing of useRef. ```TypeScript 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} /> ``` ## Predefined KeyDown Event Handlers TokenInput has the following **Predefined** KeyDown event handlers. ### For Token Create 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. | <ul> <li>Default not apply</li> <li>Under Beta</li> </ul> ### For Inline editing 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`. | ## Default value of the optional Props ```JavaScript 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, ``` ## Props of customizeTokenComponent 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](https://seawind543.github.io/react-token-input/#example-customize-token-component) and its source code `ExampleCustomizeToken` in the folder `examples/`. ```JavaScript /** * @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; } ``` ## License [MIT](./LICENSE)