From 61f7a5e97716c484719d02fef553cd7d22a9241f Mon Sep 17 00:00:00 2001 From: Justin Firth Date: Sun, 11 Feb 2018 22:14:28 -0500 Subject: [PATCH] Add TypeScript definitions and usage examples --- gulpfile.js | 6 +- index.d.ts | 230 +++++++++++++++++++++++++++++++++ package.json | 8 +- test/ts/usage.tsx | 151 ++++++++++++++++++++++ test/{ => unit}/DualListBox.js | 2 +- test/{ => unit}/setup.js | 0 tsconfig.json | 15 +++ 7 files changed, 407 insertions(+), 5 deletions(-) create mode 100644 index.d.ts create mode 100644 test/ts/usage.tsx rename test/{ => unit}/DualListBox.js (99%) rename test/{ => unit}/setup.js (100%) create mode 100644 tsconfig.json diff --git a/gulpfile.js b/gulpfile.js index c7285e5..98accb7 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -18,7 +18,7 @@ gulp.task('test-script-format', () => ( gulp.src([ './examples/src/**.js', './src/**/*.js', - './test/**/*.js', + './test/unit/**/*.js', './*.js', ]) .pipe(eslint()) @@ -27,11 +27,11 @@ gulp.task('test-script-format', () => ( )); gulp.task('test-mocha', () => ( - gulp.src(['./test/**/*.js']) + gulp.src(['./test/unit/**/*.js']) .pipe(mocha({ require: [ 'babel-register', - './test/setup.js', + './test/unit/setup.js', ], })) )); diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..04e0805 --- /dev/null +++ b/index.d.ts @@ -0,0 +1,230 @@ +import * as React from 'react'; + +/** A value-based option. */ +export interface ValueOption { + /** The option label. */ + label: string; + /** The option value. */ + value: T; +} + +/** A category with other categories and values. */ +export interface CategoryOption { + /** The category label. */ + label: string; + /** The category child options. */ + options: Option[]; +} + +/** Valid options include values and categories. */ +export type Option = ValueOption | CategoryOption; + +/** A filter. */ +export interface Filter { + /** Available options. */ + available: T[]; + /** Selected options. */ + selected: T[]; +} + +/** Properties common to every `DualListBox`. */ +export interface CommonProperties { + /** + * Available options. + * + * @example + * const options = [ + * { value: 'one', label: 'One'}, + * { value: 'two', label: 'Two'}, + * ]; + * + */ + options: Option[]; + /** + * Selected options. + * + * @example + * + */ + selected?: T[]; + /** + * Override the default center alignment of action buttons. + * + * @default "center" + * + * @example + * + */ + alignActions?: 'top' | 'center'; + /** + * This flag will preserve the selection order. By default, `react-dual-listbox` + * orders selected items according to the order of the `options` property. + * + * @example + * + */ + preserveSelectOrder?: boolean; + /** + * Restrict available options. + * + * @example + * const available = ['io', 'europa', 'ganymede', 'callisto']; + * ; + */ + available?: T[]; + /** + * The display name for the hidden label for the available options control group. + * + * @default "Available" + * + * @example + * ; + */ + availableLabel?: string; + /** + * The key codes that will trigger a toggle of the selected options. + * + * @default [13, 32] + * + * @example + * + */ + moveKeyCodes?: number[]; + /** + * The display name for the hidden label for the selected options control group. + * + * @default "Selected" + * + * @example + * + */ + selectedLabel?: string; +} + +/** Additional `DualListBox` properties with filter. */ +export interface FilterProperties { + /** + * Flag that determines whether filtering is enabled. + * + * @default false + * + * @example + * + */ + canFilter?: F; + /** + * Override the default filtering function. + * + * @example + * !!(...)} + * /> + */ + filterCallback?: F extends true ? ((option: Option, filterInput: string) => boolean) : void; + /** + * Override the default filter placeholder. + * + * @example + * + */ + filterPlaceholder?: F extends true ? string : void; + /** + * Control the filter search text. + * + * @example + * const filter = { available: 'europa', selected: '' }; + * + */ + filter?: Filter; + /** + * Handle filter change. + * + * @example + * {...}} + * /> + */ + onFilterChange?: F extends true ? ((filter: string) => void) : void; +} + +/** Additional `DualListBox` properties with complex selected values. */ +export interface ValueProperties { + /** + * Handle selection changes. + * + * @example + * {...}} /> + */ + // onChange?: (selected: (T | Option)[]) => void; + onChange?: (selected: (V extends true ? T[] : Option[])) => void; + /** + * If true, the selected value passed in onChange is an array of string values. + * Otherwise, it is an array of options. + * + * @default true + * + * @example + * {...}} + * /> + * {...}} + * /> + */ + simpleValue?: V; +} + +/** `DualListBox` component properties. */ +// export type DualListBoxProperties

= CommonProperties

& FilterProperties

& ValueProperties

; +interface DualListBoxProperties extends CommonProperties

, FilterProperties, ValueProperties {} + +/** + * A feature-rich dual list box for `React`. + * + * The `DualListBox` is a controlled component, so you have to update the + * `selected` property in conjunction with the `onChange` handler if you + * want the selected values to change. + * + * @example + * // Example options (Option[]). + * const options = [ + * { label: 'One', value: 'one' }, + * { label: 'Two', value: 'two' }, + * ]; + * + * // Component state definition + * interface MinimalComponentState { selectedValues: string[]; } + * state: MinimalComponentState = { selectedValues: [] }; + * + * // Component handler + * handleChange = (selectedValues: string[]) => + * this.setState({ selectedValues }); + * + * // Usage example (`DualListBox` with options of + * // `Options[]` is a `DualListBox`): + * + */ +export default class DualListBox extends React.Component> {} diff --git a/package.json b/package.json index 9e0c565..96c1b42 100644 --- a/package.json +++ b/package.json @@ -19,17 +19,21 @@ }, "bugs": "https://github.com/jakezatecky/react-dual-listbox/issues", "main": "lib/index.js", + "typings": "./index.d.ts", "scripts": { "build": "gulp build", "examples": "gulp examples", "gh-deploy": "git subtree push --prefix examples/dist origin gh-pages", "prepublishOnly": "gulp build", - "test": "gulp test" + "test:unit": "gulp test", + "test:ts": "tsc --noEmit", + "test": "concurrently -n \"unit,ts\" -c \"green,blue\" \"npm run test:unit\" \"npm run test:ts\"" }, "peerDependencies": { "react": "^15.0.0 || ^16.0.0" }, "devDependencies": { + "@types/react": "^15", "babel-core": "^6.4.5", "babel-eslint": "^8.0.0", "babel-loader": "^7.0.0", @@ -38,6 +42,7 @@ "babel-preset-stage-2": "^6.3.13", "browser-sync": "^2.18.6", "chai": "^4.0.1", + "concurrently": "^3.5.1", "enzyme": "^2.7.1", "eslint": "^4.3.0", "eslint-config-takiyon-react": "^0.3.0", @@ -58,6 +63,7 @@ "react-addons-test-utils": "^15.4.2", "react-dom": "^15.0.0", "react-test-renderer": "^15.5.4", + "typescript": "^2.8.0-dev.20180211", "webpack": "^3.0.0", "webpack-stream": "^4.0.0" }, diff --git a/test/ts/usage.tsx b/test/ts/usage.tsx new file mode 100644 index 0000000..d23c6d6 --- /dev/null +++ b/test/ts/usage.tsx @@ -0,0 +1,151 @@ +import * as React from 'react'; +import DualListBox, { DualListBoxProperties, Option } from 'react-dual-listbox'; + +/** Example options */ +// Flat options (ValueOption[]) +const flatOptions = [ + { label: 'One', value: 'one' }, // ValueOption + { label: 'Two', value: 'two' }, // ValueOption +]; +// Nested options (Option[]) +const nestedOptions = [ + { label: 'Option 1', value: '1' }, // ValueOption + { + label: 'Category', + options: [ + { label: 'Option 2', value: '2' }, // ValueOption + { + label: 'Nested Category', + options: [ + { label: 'Option 3', value: '4' }, // ValueOption + ], + }, // CategoryOption + ], + }, // CategoryOption +]; // Option[] + +/** Example change handlers */ +// Simple value change handler. +const valuesChange = (selectedValues: string[]) => {}; +// Complex value change handler. +const optionsChange = (selectedValues: Option[]) => {}; + +/** Using flat and nested option structures. **/ +; +; + +/** Selection examples. */ +; +; +; +/** Selection error examples. */ +/* +// You can't use an options change handler when `simpleValues` is not `false` +; +; +// You can't use a values change handler when `simpleValues` is `false` +; +*/ + +/** Filtering examples. */ +; + o.value), + selected: [], + }} + onFilterChange={() => {}} + filterPlaceholder={''} + filterCallback={(option: Option) => true} +/>; +/** Filtering error examples. */ +/* +// You can not use filter properties when `canFilter` is not `true`: + o.value), + selected: [], + }} + onFilterChange={() => {}} + filterPlaceholder={''} + filterCallback={(option: Option) => true} +/>; + o.value), + selected: [], + }} + onFilterChange={() => {}} + filterPlaceholder={''} + filterCallback={(option: Option) => true} +/>; +*/ + +/** Section labels. */ + o.value)} + availableLabel={'Available'} + selectedLabel={'Selected'} +/>; + +/** Action alignment. */ +; + +/** Ppreserving select order. */ +; + +/** `moveKeyCodes` example. */ +; + +/** Kitchen sink. */ + o.value)} + availableLabel={'Available'} + selectedLabel={'Selected'} + moveKeyCodes={[13, 32]} + simpleValue={false} + onChange={optionsChange} + canFilter={true} + filter={{ + available: flatOptions.map(o => o.value), + selected: [], + }} + onFilterChange={() => {}} + filterPlaceholder={''} + filterCallback={(option: Option) => true} +/>; diff --git a/test/DualListBox.js b/test/unit/DualListBox.js similarity index 99% rename from test/DualListBox.js rename to test/unit/DualListBox.js index 4333f63..718534c 100644 --- a/test/DualListBox.js +++ b/test/unit/DualListBox.js @@ -2,7 +2,7 @@ import React from 'react'; import { shallow, mount } from 'enzyme'; import { assert } from 'chai'; -import DualListBox from '../src/js/DualListBox'; +import DualListBox from '../../src/js/DualListBox'; describe('', () => { describe('component', () => { diff --git a/test/setup.js b/test/unit/setup.js similarity index 100% rename from test/setup.js rename to test/unit/setup.js diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..9c8ea83 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "es2018" + ], + "jsx": "react", + "strict": true, + "baseUrl": "./", + "paths": { + "react-dual-listbox": ["index.d.ts"] + } + } +}