From f65c98e8dd90594b10cd453e68272c7237f9a27a Mon Sep 17 00:00:00 2001 From: musa Date: Thu, 7 Dec 2023 19:59:25 +0300 Subject: [PATCH] Feat: 303 added closeOnClickInput support --- README.md | 3 +- docs/src/pages/demo.js | 13 ++ src/components/Content.js | 15 +- src/index.js | 263 +----------------------------- src/models/SelectMethodsModel.js | 75 +++++++++ src/models/SelectPropsModel.js | 266 +++++++++++++++++++++++++++++++ src/models/SelectStateModel.js | 30 ++++ types.d.ts | 1 + 8 files changed, 404 insertions(+), 262 deletions(-) create mode 100644 src/models/SelectMethodsModel.js create mode 100644 src/models/SelectPropsModel.js create mode 100644 src/models/SelectStateModel.js diff --git a/README.md b/README.md index b295c4da..715b6991 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,8 @@ const options = [ | valueField | string | "value" | Field in data to use for value | | color | string | "#0074D9" | Base color to use in component, also can be overwritten via CSS | | closeOnScroll | bool | false | If true, scrolling the page will close the dropdown | -| closeOnSelect | bool | false | If true, selecting option will close the dropdown | +| closeOnSelect | bool | false | If true, selecting option will close the dropdown +| closeOnClickInput | bool | false | If true, clicking input will close the dropdown if you are not searching. | | [dropdownPosition](https://sanusart.github.io/react-dropdown-select/prop/dropdown-position) | string | "bottom" | Available options are "auto", "top" and "bottom" defaults to "bottom". Auto will adjust itself according Select's position on the page | | keepSelectedInList | bool | true | If false, selected item will not appear in a list | | portal | DOM element | false | If valid dom element specified - dropdown will break out to render inside the specified element | diff --git a/docs/src/pages/demo.js b/docs/src/pages/demo.js index 0de71bc8..3eb8d5ba 100644 --- a/docs/src/pages/demo.js +++ b/docs/src/pages/demo.js @@ -39,6 +39,7 @@ export class Demo extends React.Component { color: '#0074D9', keepSelectedInList: true, closeOnSelect: false, + closeOnClickInput: false, dropdownPosition: 'bottom', direction: 'ltr', dropdownHeight: '300px' @@ -198,6 +199,7 @@ export class Demo extends React.Component { onChange={(values) => this.setValues(values)} noDataLabel="No matches found" closeOnSelect={this.state.closeOnSelect} + closeOnClickInput={this.state.closeOnClickInput} noDataRenderer={this.state.noDataRenderer ? () => this.noDataRenderer() : undefined} dropdownPosition={this.state.dropdownPosition} itemRenderer={ @@ -385,6 +387,17 @@ export class Demo extends React.Component { />{' '} Close dropdown on select/deselect
+ + this.setState({ + closeOnClickInput: !this.state.closeOnClickInput + }) + } + />{' '} + Close dropdown on click input +
Custom color{' '} { return ( @@ -14,7 +18,11 @@ const Content = ({ props, state, methods }) => { }`} onClick={(event) => { event.stopPropagation(); - methods.dropDown('open'); + if (state.dropdown === true && props.closeOnClickInput && !state.search) { + return methods.dropDown('close'); + } else { + return methods.dropDown('open'); + } }}> {props.contentRenderer ? ( props.contentRenderer({ props, state, methods }) @@ -40,6 +48,11 @@ const Content = ({ props, state, methods }) => { ); }; +Content.propTypes = { + props: PropTypes.shape(SelectPropsModel), + state: PropTypes.shape(SelectStateModel), + methods: PropTypes.shape(SelectMethodsModel), +}; const ContentComponent = styled.div` display: flex; flex: 1; diff --git a/src/index.js b/src/index.js index 1c9ace14..e67129d1 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,5 @@ import React, { Component } from 'react'; import ReactDOM from 'react-dom'; -import PropTypes from 'prop-types'; import styled from '@emotion/styled'; import ClickOutside from './components/ClickOutside'; @@ -21,267 +20,10 @@ import { isomorphicWindow } from './util'; import { LIB_NAME } from './constants'; +import SelectPropsModel from './models/SelectPropsModel'; export class Select extends Component { - static propTypes = { - /** - * Secondary placeholder on search field if any value selected - */ - addPlaceholder: PropTypes.string, - /** - * Additional props to pass to Select - */ - additionalProps: PropTypes.object, - /** - * If true, and searchable, dropdown will autofocus - */ - autoFocus: PropTypes.bool, - /** - * If true, backspace key will delete last value - */ - backspaceDelete: PropTypes.bool, - /** - * CSS class attribute to pass to select - */ - className: PropTypes.string, - /** - * Label for "Clear all" - */ - clearAllLabel: PropTypes.string, - /** - * If true, and searchable, search value will be cleared on blur - */ - clearOnBlur: PropTypes.bool, - /** - * If true, and searchable, search value will be cleared upon value select/de-select - */ - clearOnSelect: PropTypes.bool, - /** - * Overrides internal clear button - */ - clearRenderer: PropTypes.func, - /** - * Clear all indicator - */ - clearable: PropTypes.bool, - /** - * If true, scrolling the page will close the dropdown - */ - closeOnScroll: PropTypes.bool, - /** - * If true, selecting option will close the dropdown - */ - closeOnSelect: PropTypes.bool, - /** - * Base color (any css compatible) to use in component, also can be overwritten via CSS - */ - color: PropTypes.string, - /** - * Compare values override function - */ - compareValuesFunc: PropTypes.func, - /** - * Overrides internal content component (the contents of the select component) - * | example - */ - contentRenderer: PropTypes.func, - /** - * If true, select will create value from search string and fire onCreateNew callback prop - */ - create: PropTypes.bool, - /** - * If create set to true, this will be the label of the "add new" component. {search} will be replaced by search value - */ - createNewLabel: PropTypes.string, - /** - * Debounce Delay for updates upon component interactions - */ - debounceDelay: PropTypes.number, - - /** - * Direction of a dropdown "ltr", "rtl" or "auto" - */ - direction: PropTypes.string, - /** - * Disable select and all interactions - */ - disabled: PropTypes.bool, - /** - * Label shown on disabled field (after) the text - */ - disabledLabel: PropTypes.string, - /** - * Gap between select element and dropdown - */ - dropdownGap: PropTypes.number, - /** - * Show or hide dropdown handle to open/close dropdown - */ - dropdownHandle: PropTypes.bool, - /** - * Overrides internal dropdown handle - */ - dropdownHandleRenderer: PropTypes.func, - /** - * Minimum height of a dropdown - */ - dropdownHeight: PropTypes.string, - /** - * Available options are "auto", "top" and "bottom" defaults to "bottom". Auto will adjust itself according Select's position on the page - * | example - */ - dropdownPosition: PropTypes.oneOf(['top', 'bottom', 'auto']), - /** - * Overrides internal dropdown handle - */ - dropdownRenderer: PropTypes.func, - /** - * Overrides internal keyDown function - */ - handleKeyDownFn: PropTypes.func, - /** - * Overrides internal input text - */ - inputRenderer: PropTypes.func, - /** - * Overrides internal item in a dropdown - */ - itemRenderer: PropTypes.func, - /** - * If true, dropdown will always stay open (good for debugging) - */ - keepOpen: PropTypes.bool, - /** - * If false, selected item will not appear in a list - */ - keepSelectedInList: PropTypes.bool, - /** - * Field in data to use for label - */ - labelField: PropTypes.string, - /** - * Loading indicator - */ - loading: PropTypes.bool, - /** - * Overrides internal loading - */ - loadingRenderer: PropTypes.func, - /** - * If true - will act as multi-select, if false - only one option will be selected at the time - */ - multi: PropTypes.bool, - /** - * If set, input type hidden would be added in the component with the value of the `name` prop as `name` and select's `values` as `value` - * Useful for html forms - */ - name: PropTypes.string, - /** - * Label for "No data" - */ - noDataLabel: PropTypes.string, - /** - * Overrides internal "no data" (shown where search has no results) - */ - noDataRenderer: PropTypes.func, - /** - * onChange callback handler - */ - onChange: PropTypes.func.isRequired, - /** - * Fires upon clearing all values (via custom renderers) - */ - onClearAll: PropTypes.func, - /** - * Fires upon creation of new item if create prop set to true - */ - onCreateNew: PropTypes.func, - /** - * Fires upon dropdown close - */ - onDropdownClose: PropTypes.func, - /** - * Fires upon dropdown closing state, stops the closing and provides own method close() - * @return undefined - */ - onDropdownCloseRequest: PropTypes.func, - /** - * Fires upon dropdown open - */ - onDropdownOpen: PropTypes.func, - /** - * Fires upon selecting all values (via custom renderers) - */ - onSelectAll: PropTypes.func, - /** - * Overrides internal option (the pillow with an "x") on the select content - */ - optionRenderer: PropTypes.func, - /** - * Available options, (option with key disabled: true will be disabled) - */ - options: PropTypes.array.isRequired, - /** - * If set, input type hidden would be added in the component with pattern prop as regex - */ - pattern: PropTypes.string, - /** - * Placeholder shown where there are no selected values - */ - placeholder: PropTypes.string, - /** - * If valid dom element specified - dropdown will break out to render inside the specified element - */ - portal: PropTypes.element, - /** - * If set, input type hidden would be added in the component with required prop as true/false - */ - required: PropTypes.bool, - /** - * Search by object property in values - */ - searchBy: PropTypes.string, - /** - * Overrides internal search function - */ - searchFn: PropTypes.func, - /** - * If true, select will have search input text - */ - searchable: PropTypes.bool, - /** - * Allow to select all (if select is multi select) - */ - selectAll: PropTypes.bool, - /** - * Label for "Select all" - */ - selectAllLabel: PropTypes.string, - /** - * Separator line between close all and dropdown handle - */ - separator: PropTypes.bool, - /** - * Overrides internal separator - */ - separatorRenderer: PropTypes.func, - /** - * Sort by object property in values - */ - sortBy: PropTypes.string, - /** - * Style object to pass to select - */ - style: PropTypes.object, - /** - * Field in data to use for value - */ - valueField: PropTypes.string, - /** - * Selected values - */ - values: PropTypes.array - }; + static propTypes = SelectPropsModel; constructor(props) { super(props); @@ -785,6 +527,7 @@ Select.defaultProps = { clearable: false, closeOnScroll: false, closeOnSelect: false, + closeOnClickInput: false, color: '#0074D9', compareValuesFunc: isEqual, create: false, diff --git a/src/models/SelectMethodsModel.js b/src/models/SelectMethodsModel.js new file mode 100644 index 00000000..444fa079 --- /dev/null +++ b/src/models/SelectMethodsModel.js @@ -0,0 +1,75 @@ +import PropTypes from 'prop-types'; + +const SelectMethodsModel = Object.freeze({ + activeCursorItem: PropTypes.func, + /** + * Add a new item + */ + addItem: PropTypes.func.isRequired, + /** + * Check if all items are selected + */ + areAllSelected: PropTypes.func.isRequired, + /** + * Clear all selected items + */ + clearAll: PropTypes.func.isRequired, + /** + * Create a new item + */ + createNew: PropTypes.func.isRequired, + /** + * Close/Toggle/Open + */ + dropDown: PropTypes.func.isRequired, + /** + * Get the input size + */ + getInputSize: PropTypes.func.isRequired, + /** + * Get the bounds of the select component + */ + getSelectBounds: PropTypes.func.isRequired, + /** + * Get the reference to the select component + */ + getSelectRef: PropTypes.func.isRequired, + /** + * Handle key down event + */ + handleKeyDown: PropTypes.func.isRequired, + /** + * Check if an item is selected + */ + isSelected: PropTypes.func.isRequired, + /** + * Remove an item + */ + removeItem: PropTypes.func.isRequired, + /** + * Make a string safe + */ + safeString: PropTypes.func.isRequired, + /** + * Get search results + */ + searchResults: PropTypes.func.isRequired, + /** + * Select all items + */ + selectAll: PropTypes.func.isRequired, + /** + * Set the search string + */ + setSearch: PropTypes.func.isRequired, + /** + * Sort items + */ + sortBy: PropTypes.func.isRequired, + /** + * Toggle select all + */ + toggleSelectAll: PropTypes.func.isRequired, +}); + +export default SelectMethodsModel; diff --git a/src/models/SelectPropsModel.js b/src/models/SelectPropsModel.js new file mode 100644 index 00000000..ca9dd4c1 --- /dev/null +++ b/src/models/SelectPropsModel.js @@ -0,0 +1,266 @@ +import PropTypes from 'prop-types'; + +const SelectPropsModel = Object.freeze({ + /** + * Secondary placeholder on search field if any value selected + */ + addPlaceholder: PropTypes.string, + /** + * Additional props to pass to Select + */ + additionalProps: PropTypes.object, + /** + * If true, and searchable, dropdown will autofocus + */ + autoFocus: PropTypes.bool, + /** + * If true, backspace key will delete last value + */ + backspaceDelete: PropTypes.bool, + /** + * CSS class attribute to pass to select + */ + className: PropTypes.string, + /** + * Label for "Clear all" + */ + clearAllLabel: PropTypes.string, + /** + * If true, and searchable, search value will be cleared on blur + */ + clearOnBlur: PropTypes.bool, + /** + * If true, and searchable, search value will be cleared upon value select/de-select + */ + clearOnSelect: PropTypes.bool, + /** + * Overrides internal clear button + */ + clearRenderer: PropTypes.func, + /** + * Clear all indicator + */ + clearable: PropTypes.bool, + /** + * If true, scrolling the page will close the dropdown + */ + closeOnScroll: PropTypes.bool, + /** + * If true, selecting option will close the dropdown + */ + closeOnSelect: PropTypes.bool, + /** + * If true, clicking input will close the dropdown if you are not searching. + */ + closeOnClickInput: PropTypes.bool, + /** + * Base color (any CSS compatible) to use in component, also can be overwritten via CSS + */ + color: PropTypes.string, + /** + * Compare values override function + */ + compareValuesFunc: PropTypes.func, + /** + * Overrides internal content component (the contents of the select component) + * | example + */ + contentRenderer: PropTypes.func, + /** + * If true, select will create value from search string and fire onCreateNew callback prop + */ + create: PropTypes.bool, + /** + * If create set to true, this will be the label of the "add new" component. {search} will be replaced by search value + */ + createNewLabel: PropTypes.string, + /** + * Debounce Delay for updates upon component interactions + */ + debounceDelay: PropTypes.number, + /** + * Direction of a dropdown "ltr", "rtl" or "auto" + */ + direction: PropTypes.string, + /** + * Disable select and all interactions + */ + disabled: PropTypes.bool, + /** + * Label shown on disabled field (after) the text + */ + disabledLabel: PropTypes.string, + /** + * Gap between select element and dropdown + */ + dropdownGap: PropTypes.number, + /** + * Show or hide dropdown handle to open/close dropdown + */ + dropdownHandle: PropTypes.bool, + /** + * Overrides internal dropdown handle + */ + dropdownHandleRenderer: PropTypes.func, + /** + * Minimum height of a dropdown + */ + dropdownHeight: PropTypes.string, + /** + * Available options are "auto", "top" and "bottom" defaults to "bottom". Auto will adjust itself according Select's position on the page + * | example + */ + dropdownPosition: PropTypes.oneOf(['top', 'bottom', 'auto']), + /** + * Overrides internal dropdown handle + */ + dropdownRenderer: PropTypes.func, + /** + * Overrides internal keyDown function + */ + handleKeyDownFn: PropTypes.func, + /** + * Overrides internal input text + */ + inputRenderer: PropTypes.func, + /** + * Overrides internal item in a dropdown + */ + itemRenderer: PropTypes.func, + /** + * If true, dropdown will always stay open (good for debugging) + */ + keepOpen: PropTypes.bool, + /** + * If false, selected item will not appear in a list + */ + keepSelectedInList: PropTypes.bool, + /** + * Field in data to use for label + */ + labelField: PropTypes.string, + /** + * Loading indicator + */ + loading: PropTypes.bool, + /** + * Overrides internal loading + */ + loadingRenderer: PropTypes.func, + /** + * If true - will act as multi-select, if false - only one option will be selected at the time + */ + multi: PropTypes.bool, + /** + * If set, input type hidden would be added in the component with the value of the `name` prop as `name` and select's `values` as `value` + * Useful for HTML forms + */ + name: PropTypes.string, + /** + * Label for "No data" + */ + noDataLabel: PropTypes.string, + /** + * Overrides internal "no data" (shown where search has no results) + */ + noDataRenderer: PropTypes.func, + /** + * onChange callback handler + */ + onChange: PropTypes.func.isRequired, + /** + * Fires upon clearing all values (via custom renderers) + */ + onClearAll: PropTypes.func, + /** + * Fires upon creation of new item if create prop set to true + */ + onCreateNew: PropTypes.func, + /** + * Fires upon dropdown close + */ + onDropdownClose: PropTypes.func, + /** + * Fires upon dropdown closing state, stops the closing and provides own method close() + * @return undefined + */ + onDropdownCloseRequest: PropTypes.func, + /** + * Fires upon dropdown open + */ + onDropdownOpen: PropTypes.func, + /** + * Fires upon selecting all values (via custom renderers) + */ + onSelectAll: PropTypes.func, + /** + * Overrides internal option (the pillow with an "x") on the select content + */ + optionRenderer: PropTypes.func, + /** + * Available options, (option with key disabled: true will be disabled) + */ + options: PropTypes.array.isRequired, + /** + * If set, input type hidden would be added in the component with pattern prop as regex + */ + pattern: PropTypes.string, + /** + * Placeholder shown where there are no selected values + */ + placeholder: PropTypes.string, + /** + * If valid DOM element specified - dropdown will break out to render inside the specified element + */ + portal: PropTypes.element, + /** + * If set, input type hidden would be added in the component with required prop as true/false + */ + required: PropTypes.bool, + /** + * Search by object property in values + */ + searchBy: PropTypes.string, + /** + * Overrides internal search function + */ + searchFn: PropTypes.func, + /** + * If true, select will have search input text + */ + searchable: PropTypes.bool, + /** + * Allow to select all (if select is multi-select) + */ + selectAll: PropTypes.bool, + /** + * Label for "Select all" + */ + selectAllLabel: PropTypes.string, + /** + * Separator line between close all and dropdown handle + */ + separator: PropTypes.bool, + /** + * Overrides internal separator + */ + separatorRenderer: PropTypes.func, + /** + * Sort by object property in values + */ + sortBy: PropTypes.string, + /** + * Style object to pass to select + */ + style: PropTypes.object, + /** + * Field in data to use for value + */ + valueField: PropTypes.string, + /** + * Selected values + */ + values: PropTypes.array, +}); + +export default SelectPropsModel; diff --git a/src/models/SelectStateModel.js b/src/models/SelectStateModel.js new file mode 100644 index 00000000..e7c87169 --- /dev/null +++ b/src/models/SelectStateModel.js @@ -0,0 +1,30 @@ +import PropTypes from 'prop-types'; + +const SelectStateModel = Object.freeze({ + /** + * Flag indicating whether the dropdown is open or closed + */ + dropdown: PropTypes.bool.isRequired, + /** + * Array of selected values + */ + values: PropTypes.arrayOf(PropTypes.shape({})), + /** + * Search string + */ + search: PropTypes.string.isRequired, + /** + * Array of bounds for the select component + */ + selectBounds: PropTypes.shape({}), + /** + * Cursor position + */ + cursor: PropTypes.number, + /** + * Array of search results + */ + searchResults: PropTypes.arrayOf(PropTypes.shape({})), +}); + +export default SelectStateModel; diff --git a/types.d.ts b/types.d.ts index 5f4eb8ea..f066014a 100644 --- a/types.d.ts +++ b/types.d.ts @@ -107,6 +107,7 @@ declare module 'react-dropdown-select' { color?: string; keepSelectedInList?: boolean; closeOnSelect?: boolean; + closeOnClickInput?: boolean; clearOnBlur?: boolean; clearOnSelect?: boolean; dropdownPosition?: 'top' | 'bottom' | 'auto';