From ad9d39279782ba87602a263f46c89c5af2934275 Mon Sep 17 00:00:00 2001 From: Alexandru Buliga Date: Thu, 14 Feb 2019 15:22:49 +0100 Subject: [PATCH 01/11] proto(dropdown): @mention scenario --- docs/src/components/Sidebar/Sidebar.tsx | 6 +- .../prototypes/AsyncDropdownSearch/index.ts | 1 - docs/src/prototypes/Protoypes.tsx | 29 ++++ .../AsyncDropdownSearch.tsx | 74 +++++----- docs/src/prototypes/dropdowns/dataMocks.ts | 53 +++++++ docs/src/prototypes/dropdowns/index.tsx | 21 +++ .../dropdowns/inputWithDropdown.tsx | 136 ++++++++++++++++++ docs/src/prototypes/dropdowns/utils.ts | 36 +++++ docs/src/routes.tsx | 6 +- .../Dropdown/DropdownSearchInput.tsx | 4 +- packages/react/src/components/Input/Input.tsx | 1 - .../Dropdown/dropdownSearchInputStyles.ts | 4 +- .../components/Dropdown/dropdownStyles.ts | 5 +- 13 files changed, 325 insertions(+), 51 deletions(-) delete mode 100644 docs/src/prototypes/AsyncDropdownSearch/index.ts create mode 100644 docs/src/prototypes/Protoypes.tsx rename docs/src/prototypes/{AsyncDropdownSearch => dropdowns}/AsyncDropdownSearch.tsx (55%) create mode 100644 docs/src/prototypes/dropdowns/dataMocks.ts create mode 100644 docs/src/prototypes/dropdowns/index.tsx create mode 100644 docs/src/prototypes/dropdowns/inputWithDropdown.tsx create mode 100644 docs/src/prototypes/dropdowns/utils.ts diff --git a/docs/src/components/Sidebar/Sidebar.tsx b/docs/src/components/Sidebar/Sidebar.tsx index 1cfdcea662..77d1ae1b39 100644 --- a/docs/src/components/Sidebar/Sidebar.tsx +++ b/docs/src/components/Sidebar/Sidebar.tsx @@ -343,10 +343,10 @@ class Sidebar extends React.Component { styles: menuItemStyles, }, { - key: 'asyncdropdown', - content: 'Async Dropdown Search', + key: 'dropdowns', + content: 'Dropdowns', as: NavLink, - to: '/prototype-async-dropdown-search', + to: '/prototype-dropdowns', styles: menuItemStyles, }, { diff --git a/docs/src/prototypes/AsyncDropdownSearch/index.ts b/docs/src/prototypes/AsyncDropdownSearch/index.ts deleted file mode 100644 index 4f527e450b..0000000000 --- a/docs/src/prototypes/AsyncDropdownSearch/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './AsyncDropdownSearch' diff --git a/docs/src/prototypes/Protoypes.tsx b/docs/src/prototypes/Protoypes.tsx new file mode 100644 index 0000000000..750dcd2fec --- /dev/null +++ b/docs/src/prototypes/Protoypes.tsx @@ -0,0 +1,29 @@ +import * as React from 'react' +import { Box, Header, Segment } from '@stardust-ui/react' + +interface PrototypeSectionProps { + title?: string +} + +interface ComponentPrototypeProps extends PrototypeSectionProps { + description?: string +} + +export const PrototypeSection: React.SFC = props => ( + + {props.title &&
{props.title}
} + {props.children} +
+) + +export const ComponentPrototype: React.SFC = props => ( + + {(props.title || props.description) && ( + + {props.title &&
{props.title}
} + {props.description &&

{props.description}

} +
+ )} + {props.children} +
+) diff --git a/docs/src/prototypes/AsyncDropdownSearch/AsyncDropdownSearch.tsx b/docs/src/prototypes/dropdowns/AsyncDropdownSearch.tsx similarity index 55% rename from docs/src/prototypes/AsyncDropdownSearch/AsyncDropdownSearch.tsx rename to docs/src/prototypes/dropdowns/AsyncDropdownSearch.tsx index d082cdee1d..0933f7124a 100644 --- a/docs/src/prototypes/AsyncDropdownSearch/AsyncDropdownSearch.tsx +++ b/docs/src/prototypes/dropdowns/AsyncDropdownSearch.tsx @@ -1,4 +1,4 @@ -import { Divider, Dropdown, DropdownProps, Header, Loader, Segment } from '@stardust-ui/react' +import { Divider, Dropdown, DropdownProps, Loader } from '@stardust-ui/react' import * as faker from 'faker' import * as _ from 'lodash' import * as React from 'react' @@ -34,6 +34,8 @@ const createEntry = (): Entry => ({ // Prototype Search Page View // ---------------------------------------- class AsyncDropdownSearch extends React.Component<{}, SearchPageState> { + private searchTimer: number + state = { loading: false, searchQuery: '', @@ -41,18 +43,46 @@ class AsyncDropdownSearch extends React.Component<{}, SearchPageState> { value: [], } - searchTimer: number + render() { + const { items, loading, searchQuery, value } = this.state + + return ( + <> + , + }} + multiple + onSearchQueryChange={this.handleSearchQueryChange} + onSelectedChange={this.handleSelectedChange} + placeholder="Try to enter something..." + search + searchQuery={searchQuery} + toggleIndicator={false} + value={value} + /> + + + + ) + } - handleSelectedChange = (e: React.SyntheticEvent, { searchQuery, value }: DropdownProps) => { + private handleSelectedChange = ( + e: React.SyntheticEvent, + { searchQuery, value }: DropdownProps, + ) => { this.setState({ value: value as Entry[], searchQuery }) } - handleSearchQueryChange = (e: React.SyntheticEvent, { searchQuery }: DropdownProps) => { + private handleSearchQueryChange = (e: React.SyntheticEvent, { searchQuery }: DropdownProps) => { this.setState({ searchQuery }) this.fetchItems() } - fetchItems = () => { + private fetchItems = () => { clearTimeout(this.searchTimer) this.setState({ loading: true }) @@ -63,40 +93,6 @@ class AsyncDropdownSearch extends React.Component<{}, SearchPageState> { })) }, 2000) } - - render() { - const { items, loading, searchQuery, value } = this.state - - return ( -
- -
-

Use the field to perform a simulated search.

- - - - , - }} - multiple - onSearchQueryChange={this.handleSearchQueryChange} - onSelectedChange={this.handleSelectedChange} - placeholder="Try to enter something..." - search - searchQuery={searchQuery} - toggleIndicator={false} - value={value} - /> - - - -
- ) - } } export default AsyncDropdownSearch diff --git a/docs/src/prototypes/dropdowns/dataMocks.ts b/docs/src/prototypes/dropdowns/dataMocks.ts new file mode 100644 index 0000000000..abed50eae2 --- /dev/null +++ b/docs/src/prototypes/dropdowns/dataMocks.ts @@ -0,0 +1,53 @@ +export interface AtMention { + header: string + image: string + content: string +} + +export const atMentionItems: AtMention[] = [ + { + header: 'Bruce Wayne', + image: 'public/images/avatar/small/matt.jpg', + content: 'Software Engineer', + }, + { + header: 'Natasha Romanoff', + image: 'public/images/avatar/small/jenny.jpg', + content: 'UX Designer 2', + }, + { + header: 'Steven Strange', + image: 'public/images/avatar/small/joe.jpg', + content: 'Principal Software Engineering Manager', + }, + { + header: 'Alfred Pennyworth', + image: 'public/images/avatar/small/justen.jpg', + content: 'Technology Consultant', + }, + { + header: `Scarlett O'Hara`, + image: 'public/images/avatar/small/laura.jpg', + content: 'Software Engineer 2', + }, + { + header: 'Imperator Furiosa', + image: 'public/images/avatar/small/veronika.jpg', + content: 'Boss', + }, + { + header: 'Bruce Banner', + image: 'public/images/avatar/small/chris.jpg', + content: 'Senior Computer Scientist', + }, + { + header: 'Peter Parker', + image: 'public/images/avatar/small/daniel.jpg', + content: 'Partner Software Engineer', + }, + { + header: 'Selina Kyle', + image: 'public/images/avatar/small/ade.jpg', + content: 'Graphic Designer', + }, +] diff --git a/docs/src/prototypes/dropdowns/index.tsx b/docs/src/prototypes/dropdowns/index.tsx new file mode 100644 index 0000000000..9ce5ede045 --- /dev/null +++ b/docs/src/prototypes/dropdowns/index.tsx @@ -0,0 +1,21 @@ +import * as React from 'react' +import { PrototypeSection, ComponentPrototype } from '../Protoypes' +import AsyncDropdownSearch from './AsyncDropdownSearch' +import InputWithDropdownExample from './inputWithDropdown' + +export default () => ( + + + + + + + + +) diff --git a/docs/src/prototypes/dropdowns/inputWithDropdown.tsx b/docs/src/prototypes/dropdowns/inputWithDropdown.tsx new file mode 100644 index 0000000000..4eeea68c6e --- /dev/null +++ b/docs/src/prototypes/dropdowns/inputWithDropdown.tsx @@ -0,0 +1,136 @@ +import * as React from 'react' +import * as ReactDOM from 'react-dom' +import * as _ from 'lodash' +import keyboardKey from 'keyboard-key' +import { Provider, Dropdown, DropdownProps, Input, themes } from '@stardust-ui/react' + +import { atMentionItems, AtMention } from './dataMocks' +import { insertNodeAtCursorPosition, removeElement } from './utils' + +interface InputWithDropdownState { + dropdownValue?: string + searchQuery?: string +} + +class InputWithDropdownExample extends React.Component<{}, InputWithDropdownState> { + private readonly mountNodeId = 'dropdown-mount-node' + private readonly dropdownInputSelector = `#${this.mountNodeId} .${Input.slotClassNames.input}` + private readonly editorStyle: React.CSSProperties = { + backgroundColor: 'lightgrey', + padding: '5px', + minHeight: '100px', + outline: 0, + } + + private dropdownExists = false + private contendEditableRef = React.createRef() + + public state: InputWithDropdownState = { + dropdownValue: null, + searchQuery: '', + } + + render() { + return ( +
+ ) + } + + private showDropdown = () => { + this.dropdownExists = true + insertNodeAtCursorPosition({ id: this.mountNodeId }) + + const node = this.getMountNode() + ReactDOM.render( + + + , + node, + ) + } + + private hideDropdownAndRestoreEditor = () => { + const node = this.getMountNode() + ReactDOM.unmountComponentAtNode(node) + removeElement(node) + + this.tryFocusEditor() + this.dropdownExists = false + } + + private handleEditorKeyUp = (e: React.KeyboardEvent) => { + if (!this.dropdownExists && keyboardKey.getCode(e) === keyboardKey.AtSign) { + this.showDropdown() + this.setInputElementSize(0) + } + } + + private handleSelectedChange = ( + e: React.SyntheticEvent, + { searchQuery, value }: DropdownProps, + ) => { + const dropdownValue = (value as AtMention).header + this.hideDropdownAndRestoreEditor() + insertNodeAtCursorPosition({ text: dropdownValue }) + + this.setState({ searchQuery, dropdownValue }) + } + + private handleSearchQueryChange = (e: React.SyntheticEvent, { searchQuery }: DropdownProps) => { + this.setInputElementSize(searchQuery.length) + this.setState({ searchQuery }) + } + + private handleInputKeyDown = (e: React.KeyboardEvent) => { + const code = keyboardKey.getCode(e) + switch (code) { + case keyboardKey.Backspace: // 8 + if (this.state.searchQuery === '') { + this.hideDropdownAndRestoreEditor() + } + break + case keyboardKey.Escape: // 27 + this.hideDropdownAndRestoreEditor() + break + } + } + + private tryFocusEditor = () => { + if (this.contendEditableRef) { + this.contendEditableRef.current.focus() + } + } + + private getMountNode = () => document.getElementById(this.mountNodeId) + + private getInputElement = (): HTMLInputElement => + document.querySelector(this.dropdownInputSelector) + + private setInputElementSize = (size: number) => { + const input = this.getInputElement() + if (input) { + input.size = size || 0 + 1 + console.log('setInputElementSize: ', input.size) + } + } +} + +export default InputWithDropdownExample diff --git a/docs/src/prototypes/dropdowns/utils.ts b/docs/src/prototypes/dropdowns/utils.ts new file mode 100644 index 0000000000..008de3385a --- /dev/null +++ b/docs/src/prototypes/dropdowns/utils.ts @@ -0,0 +1,36 @@ +export const removeElement = (element: string | HTMLElement): HTMLElement => { + const elementToRemove = typeof element === 'string' ? document.getElementById(element) : element + return elementToRemove.parentNode.removeChild(elementToRemove) +} + +export const insertNodeAtCursorPosition = (params: { id?: string; text?: string } = {}) => { + const { id, text } = params + if (!id && !text) { + throw '[insertNodeAtCursorPosition]: at least one parameter has to be supplied' + } + + if (!window.getSelection) { + return + } + + const sel = window.getSelection() + if (!sel.getRangeAt || !sel.rangeCount) { + return + } + + const range = sel.getRangeAt(0) + + if (text && !id) { + const textNode = document.createTextNode(text) + range.insertNode(textNode) + range.setStartAfter(textNode) + return + } + + const elem = document.createElement('span') + elem.id = id + if (text) { + elem.innerText = text + } + range.insertNode(elem) +} diff --git a/docs/src/routes.tsx b/docs/src/routes.tsx index b9a1490e9f..ee59aaf4c5 100644 --- a/docs/src/routes.tsx +++ b/docs/src/routes.tsx @@ -62,9 +62,9 @@ const Router = () => ( />, , ) } diff --git a/packages/react/src/components/Input/Input.tsx b/packages/react/src/components/Input/Input.tsx index 8f6819e24a..0c195754a0 100644 --- a/packages/react/src/components/Input/Input.tsx +++ b/packages/react/src/components/Input/Input.tsx @@ -124,7 +124,6 @@ class Input extends AutoControlledComponent, InputState> renderComponent({ accessibility, ElementType, - classes, unhandledProps, styles, variables, diff --git a/packages/react/src/themes/teams/components/Dropdown/dropdownSearchInputStyles.ts b/packages/react/src/themes/teams/components/Dropdown/dropdownSearchInputStyles.ts index 7452a15b6b..237ebe0b07 100644 --- a/packages/react/src/themes/teams/components/Dropdown/dropdownSearchInputStyles.ts +++ b/packages/react/src/themes/teams/components/Dropdown/dropdownSearchInputStyles.ts @@ -16,8 +16,8 @@ const dropdownSearchInputStyles: ComponentSlotStylesInput< backgroundColor: 'transparent', borderWidth: 0, ...(p.inline && { - paddingLeft: 0, - paddingRight: 0, + padding: 0, + lineHeight: 'initial', }), }), } diff --git a/packages/react/src/themes/teams/components/Dropdown/dropdownStyles.ts b/packages/react/src/themes/teams/components/Dropdown/dropdownStyles.ts index d6a7c808b4..468d41015b 100644 --- a/packages/react/src/themes/teams/components/Dropdown/dropdownStyles.ts +++ b/packages/react/src/themes/teams/components/Dropdown/dropdownStyles.ts @@ -74,7 +74,10 @@ const dropdownStyles: ComponentSlotStylesInput ({ From cb4f482c4f22bbbb5f370316b225e7facb50d5a9 Mon Sep 17 00:00:00 2001 From: Alexandru Buliga Date: Tue, 19 Feb 2019 19:01:02 +0100 Subject: [PATCH 02/11] reverted AsyncDropdownSearch changes --- .../dropdowns/AsyncDropdownSearch.tsx | 49 +++++++++---------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/docs/src/prototypes/dropdowns/AsyncDropdownSearch.tsx b/docs/src/prototypes/dropdowns/AsyncDropdownSearch.tsx index 0933f7124a..0e1223cc60 100644 --- a/docs/src/prototypes/dropdowns/AsyncDropdownSearch.tsx +++ b/docs/src/prototypes/dropdowns/AsyncDropdownSearch.tsx @@ -34,8 +34,6 @@ const createEntry = (): Entry => ({ // Prototype Search Page View // ---------------------------------------- class AsyncDropdownSearch extends React.Component<{}, SearchPageState> { - private searchTimer: number - state = { loading: false, searchQuery: '', @@ -43,6 +41,29 @@ class AsyncDropdownSearch extends React.Component<{}, SearchPageState> { value: [], } + searchTimer: number + + handleSelectedChange = (e: React.SyntheticEvent, { searchQuery, value }: DropdownProps) => { + this.setState({ value: value as Entry[], searchQuery }) + } + + handleSearchQueryChange = (e: React.SyntheticEvent, { searchQuery }: DropdownProps) => { + this.setState({ searchQuery }) + this.fetchItems() + } + + fetchItems = () => { + clearTimeout(this.searchTimer) + this.setState({ loading: true }) + + this.searchTimer = setTimeout(() => { + this.setState(prevState => ({ + loading: false, + items: [...prevState.items, ..._.times(10, createEntry)], + })) + }, 2000) + } + render() { const { items, loading, searchQuery, value } = this.state @@ -69,30 +90,6 @@ class AsyncDropdownSearch extends React.Component<{}, SearchPageState> { ) } - - private handleSelectedChange = ( - e: React.SyntheticEvent, - { searchQuery, value }: DropdownProps, - ) => { - this.setState({ value: value as Entry[], searchQuery }) - } - - private handleSearchQueryChange = (e: React.SyntheticEvent, { searchQuery }: DropdownProps) => { - this.setState({ searchQuery }) - this.fetchItems() - } - - private fetchItems = () => { - clearTimeout(this.searchTimer) - this.setState({ loading: true }) - - this.searchTimer = setTimeout(() => { - this.setState(prevState => ({ - loading: false, - items: [...prevState.items, ..._.times(10, createEntry)], - })) - }, 2000) - } } export default AsyncDropdownSearch From e29d76b85230580658c2d7c4856869ed3655a2e7 Mon Sep 17 00:00:00 2001 From: Alexandru Buliga Date: Tue, 19 Feb 2019 20:59:04 +0100 Subject: [PATCH 03/11] implemented the new Dropdown using ReactDOM.createPortal instead of ReactDOM.render --- .../dropdowns/inputWithDropdown.tsx | 75 ++++++++++--------- 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/docs/src/prototypes/dropdowns/inputWithDropdown.tsx b/docs/src/prototypes/dropdowns/inputWithDropdown.tsx index 4eeea68c6e..a0890db191 100644 --- a/docs/src/prototypes/dropdowns/inputWithDropdown.tsx +++ b/docs/src/prototypes/dropdowns/inputWithDropdown.tsx @@ -2,12 +2,13 @@ import * as React from 'react' import * as ReactDOM from 'react-dom' import * as _ from 'lodash' import keyboardKey from 'keyboard-key' -import { Provider, Dropdown, DropdownProps, Input, themes } from '@stardust-ui/react' +import { Dropdown, DropdownProps, Input } from '@stardust-ui/react' import { atMentionItems, AtMention } from './dataMocks' import { insertNodeAtCursorPosition, removeElement } from './utils' interface InputWithDropdownState { + dropdownOpen?: boolean dropdownValue?: string searchQuery?: string } @@ -22,63 +23,69 @@ class InputWithDropdownExample extends React.Component<{}, InputWithDropdownStat outline: 0, } - private dropdownExists = false private contendEditableRef = React.createRef() public state: InputWithDropdownState = { + dropdownOpen: false, dropdownValue: null, searchQuery: '', } render() { return ( -
+ <> +
+ {this.renderDropdown()} + ) } - private showDropdown = () => { - this.dropdownExists = true - insertNodeAtCursorPosition({ id: this.mountNodeId }) + private renderDropdown = () => { + if (!this.state.dropdownOpen) { + return null + } + insertNodeAtCursorPosition({ id: this.mountNodeId }) const node = this.getMountNode() - ReactDOM.render( - - - , + + if (!node) { + return null + } + + return ReactDOM.createPortal( + , node, ) } private hideDropdownAndRestoreEditor = () => { - const node = this.getMountNode() - ReactDOM.unmountComponentAtNode(node) - removeElement(node) + removeElement(this.getMountNode()) this.tryFocusEditor() - this.dropdownExists = false + this.setState({ dropdownOpen: false }) } private handleEditorKeyUp = (e: React.KeyboardEvent) => { - if (!this.dropdownExists && keyboardKey.getCode(e) === keyboardKey.AtSign) { - this.showDropdown() + if (!this.state.dropdownOpen && keyboardKey.getCode(e) === keyboardKey.AtSign) { + this.setState({ dropdownOpen: true }) this.setInputElementSize(0) } } From 6dadd4ff3a208ff5034b82e8d1c0d91743bcf0a1 Mon Sep 17 00:00:00 2001 From: Alexandru Buliga Date: Thu, 21 Feb 2019 09:46:53 +0100 Subject: [PATCH 04/11] - addressed PR comments; - refactoring of using portal - fix for all styling regressions --- .../{Protoypes.tsx => Prototypes.tsx} | 0 docs/src/prototypes/dropdowns/CustomPortal.ts | 39 ++++++ docs/src/prototypes/dropdowns/dataMocks.ts | 57 ++------ docs/src/prototypes/dropdowns/index.tsx | 6 +- .../dropdowns/inputWithDropdown.tsx | 130 +++++++----------- docs/src/prototypes/dropdowns/utils.ts | 59 +++++--- 6 files changed, 136 insertions(+), 155 deletions(-) rename docs/src/prototypes/{Protoypes.tsx => Prototypes.tsx} (100%) create mode 100644 docs/src/prototypes/dropdowns/CustomPortal.ts diff --git a/docs/src/prototypes/Protoypes.tsx b/docs/src/prototypes/Prototypes.tsx similarity index 100% rename from docs/src/prototypes/Protoypes.tsx rename to docs/src/prototypes/Prototypes.tsx diff --git a/docs/src/prototypes/dropdowns/CustomPortal.ts b/docs/src/prototypes/dropdowns/CustomPortal.ts new file mode 100644 index 0000000000..231f236d5f --- /dev/null +++ b/docs/src/prototypes/dropdowns/CustomPortal.ts @@ -0,0 +1,39 @@ +import * as React from 'react' +import * as ReactDOM from 'react-dom' +import { insertSpanAtCursorPosition, removeElement } from './utils' + +export interface CustomPortalProps { + mountNodeId: string + open?: boolean +} + +export class CustomPortal extends React.Component { + private mountNodeInstance: HTMLElement = null + + public render() { + this.setupMountNode() + return this.props.open && this.mountNodeInstance + ? ReactDOM.createPortal(this.props.children, this.mountNodeInstance) + : null + } + + public componentWillUnmount() { + this.removeMountNode() + } + + private setupMountNode = () => { + if (this.props.open) { + this.mountNodeInstance = + this.mountNodeInstance || insertSpanAtCursorPosition(this.props.mountNodeId) + } else { + this.removeMountNode() + } + } + + private removeMountNode = () => { + if (this.mountNodeInstance) { + removeElement(this.mountNodeInstance) + this.mountNodeInstance = null + } + } +} diff --git a/docs/src/prototypes/dropdowns/dataMocks.ts b/docs/src/prototypes/dropdowns/dataMocks.ts index abed50eae2..9c8e36592a 100644 --- a/docs/src/prototypes/dropdowns/dataMocks.ts +++ b/docs/src/prototypes/dropdowns/dataMocks.ts @@ -1,53 +1,14 @@ -export interface AtMention { +import * as _ from 'lodash' +import { name, internet } from 'faker' + +export interface AtMentionItem { header: string image: string content: string } -export const atMentionItems: AtMention[] = [ - { - header: 'Bruce Wayne', - image: 'public/images/avatar/small/matt.jpg', - content: 'Software Engineer', - }, - { - header: 'Natasha Romanoff', - image: 'public/images/avatar/small/jenny.jpg', - content: 'UX Designer 2', - }, - { - header: 'Steven Strange', - image: 'public/images/avatar/small/joe.jpg', - content: 'Principal Software Engineering Manager', - }, - { - header: 'Alfred Pennyworth', - image: 'public/images/avatar/small/justen.jpg', - content: 'Technology Consultant', - }, - { - header: `Scarlett O'Hara`, - image: 'public/images/avatar/small/laura.jpg', - content: 'Software Engineer 2', - }, - { - header: 'Imperator Furiosa', - image: 'public/images/avatar/small/veronika.jpg', - content: 'Boss', - }, - { - header: 'Bruce Banner', - image: 'public/images/avatar/small/chris.jpg', - content: 'Senior Computer Scientist', - }, - { - header: 'Peter Parker', - image: 'public/images/avatar/small/daniel.jpg', - content: 'Partner Software Engineer', - }, - { - header: 'Selina Kyle', - image: 'public/images/avatar/small/ade.jpg', - content: 'Graphic Designer', - }, -] +export const atMentionItems: AtMentionItem[] = _.times(10, () => ({ + header: `${name.firstName()} ${name.lastName()}`, + image: internet.avatar(), + content: name.title(), +})) diff --git a/docs/src/prototypes/dropdowns/index.tsx b/docs/src/prototypes/dropdowns/index.tsx index 9ce5ede045..44a72d4ded 100644 --- a/docs/src/prototypes/dropdowns/index.tsx +++ b/docs/src/prototypes/dropdowns/index.tsx @@ -1,7 +1,7 @@ import * as React from 'react' -import { PrototypeSection, ComponentPrototype } from '../Protoypes' +import { PrototypeSection, ComponentPrototype } from '../Prototypes' import AsyncDropdownSearch from './AsyncDropdownSearch' -import InputWithDropdownExample from './inputWithDropdown' +import InputWithDropdown from './InputWithDropdown' export default () => ( @@ -15,7 +15,7 @@ export default () => ( title="Input with Dropdown" description="Use the '@' key to mention people." > - + ) diff --git a/docs/src/prototypes/dropdowns/inputWithDropdown.tsx b/docs/src/prototypes/dropdowns/inputWithDropdown.tsx index a0890db191..a9fb3da766 100644 --- a/docs/src/prototypes/dropdowns/inputWithDropdown.tsx +++ b/docs/src/prototypes/dropdowns/inputWithDropdown.tsx @@ -1,36 +1,34 @@ import * as React from 'react' -import * as ReactDOM from 'react-dom' import * as _ from 'lodash' import keyboardKey from 'keyboard-key' -import { Dropdown, DropdownProps, Input } from '@stardust-ui/react' +import { Dropdown, DropdownProps } from '@stardust-ui/react' -import { atMentionItems, AtMention } from './dataMocks' -import { insertNodeAtCursorPosition, removeElement } from './utils' +import { atMentionItems, AtMentionItem } from './dataMocks' +import { insertTextAtCursorPosition } from './utils' +import { CustomPortal } from './CustomPortal' interface InputWithDropdownState { dropdownOpen?: boolean - dropdownValue?: string searchQuery?: string } -class InputWithDropdownExample extends React.Component<{}, InputWithDropdownState> { - private readonly mountNodeId = 'dropdown-mount-node' - private readonly dropdownInputSelector = `#${this.mountNodeId} .${Input.slotClassNames.input}` - private readonly editorStyle: React.CSSProperties = { - backgroundColor: 'lightgrey', - padding: '5px', - minHeight: '100px', - outline: 0, - } - - private contendEditableRef = React.createRef() +const editorStyle: React.CSSProperties = { + backgroundColor: 'lightgrey', + padding: '5px', + minHeight: '100px', + outline: 0, +} - public state: InputWithDropdownState = { +class InputWithDropdown extends React.Component<{}, InputWithDropdownState> { + private readonly initialState: InputWithDropdownState = { dropdownOpen: false, - dropdownValue: null, searchQuery: '', } + private contendEditableRef = React.createRef() + + state = this.initialState + render() { return ( <> @@ -38,7 +36,7 @@ class InputWithDropdownExample extends React.Component<{}, InputWithDropdownStat contentEditable ref={this.contendEditableRef} onKeyUp={this.handleEditorKeyUp} - style={this.editorStyle} + style={editorStyle} /> {this.renderDropdown()} @@ -46,69 +44,54 @@ class InputWithDropdownExample extends React.Component<{}, InputWithDropdownStat } private renderDropdown = () => { - if (!this.state.dropdownOpen) { - return null - } - - insertNodeAtCursorPosition({ id: this.mountNodeId }) - const node = this.getMountNode() - - if (!node) { - return null - } + const { dropdownOpen, searchQuery } = this.state - return ReactDOM.createPortal( - , - node, + return ( + + + ) } - private hideDropdownAndRestoreEditor = () => { - removeElement(this.getMountNode()) - - this.tryFocusEditor() - this.setState({ dropdownOpen: false }) + private hideDropdownAndRestoreEditor = (cb?: () => void) => { + this.setState(this.initialState, () => { + this.tryFocusEditor() + cb && cb() + }) } private handleEditorKeyUp = (e: React.KeyboardEvent) => { if (!this.state.dropdownOpen && keyboardKey.getCode(e) === keyboardKey.AtSign) { this.setState({ dropdownOpen: true }) - this.setInputElementSize(0) } } - private handleSelectedChange = ( - e: React.SyntheticEvent, - { searchQuery, value }: DropdownProps, - ) => { - const dropdownValue = (value as AtMention).header - this.hideDropdownAndRestoreEditor() - insertNodeAtCursorPosition({ text: dropdownValue }) - - this.setState({ searchQuery, dropdownValue }) + private handleSelectedChange = (e: React.SyntheticEvent, { value }: DropdownProps) => { + this.hideDropdownAndRestoreEditor(() => { + insertTextAtCursorPosition((value as AtMentionItem).header) + }) } private handleSearchQueryChange = (e: React.SyntheticEvent, { searchQuery }: DropdownProps) => { - this.setInputElementSize(searchQuery.length) this.setState({ searchQuery }) } private handleInputKeyDown = (e: React.KeyboardEvent) => { - const code = keyboardKey.getCode(e) - switch (code) { + const keyCode = keyboardKey.getCode(e) + switch (keyCode) { case keyboardKey.Backspace: // 8 if (this.state.searchQuery === '') { this.hideDropdownAndRestoreEditor() @@ -120,24 +103,7 @@ class InputWithDropdownExample extends React.Component<{}, InputWithDropdownStat } } - private tryFocusEditor = () => { - if (this.contendEditableRef) { - this.contendEditableRef.current.focus() - } - } - - private getMountNode = () => document.getElementById(this.mountNodeId) - - private getInputElement = (): HTMLInputElement => - document.querySelector(this.dropdownInputSelector) - - private setInputElementSize = (size: number) => { - const input = this.getInputElement() - if (input) { - input.size = size || 0 + 1 - console.log('setInputElementSize: ', input.size) - } - } + private tryFocusEditor = () => _.invoke(this.contendEditableRef.current, 'focus') } -export default InputWithDropdownExample +export default InputWithDropdown diff --git a/docs/src/prototypes/dropdowns/utils.ts b/docs/src/prototypes/dropdowns/utils.ts index 008de3385a..3c1ac4314d 100644 --- a/docs/src/prototypes/dropdowns/utils.ts +++ b/docs/src/prototypes/dropdowns/utils.ts @@ -1,36 +1,51 @@ -export const removeElement = (element: string | HTMLElement): HTMLElement => { - const elementToRemove = typeof element === 'string' ? document.getElementById(element) : element - return elementToRemove.parentNode.removeChild(elementToRemove) -} - -export const insertNodeAtCursorPosition = (params: { id?: string; text?: string } = {}) => { - const { id, text } = params - if (!id && !text) { - throw '[insertNodeAtCursorPosition]: at least one parameter has to be supplied' - } - +const getRangeAtCursorPosition = () => { if (!window.getSelection) { - return + return null } const sel = window.getSelection() if (!sel.getRangeAt || !sel.rangeCount) { - return + return null } - const range = sel.getRangeAt(0) + return sel.getRangeAt(0) +} + +export const insertSpanAtCursorPosition = (id: string) => { + if (!id) { + throw '[insertSpanAtCursorPosition]: id must be supplied' + } - if (text && !id) { - const textNode = document.createTextNode(text) - range.insertNode(textNode) - range.setStartAfter(textNode) - return + const range = getRangeAtCursorPosition() + if (!range) { + return null } const elem = document.createElement('span') elem.id = id - if (text) { - elem.innerText = text - } range.insertNode(elem) + + return elem +} + +export const insertTextAtCursorPosition = (text: string) => { + if (!text) { + throw '[insertTextAtCursorPosition]: text must be supplied' + } + + const range = getRangeAtCursorPosition() + if (!range) { + return null + } + + const textNode = document.createTextNode(text) + range.insertNode(textNode) + range.setStartAfter(textNode) + + return textNode +} + +export const removeElement = (element: string | HTMLElement): HTMLElement => { + const elementToRemove = typeof element === 'string' ? document.getElementById(element) : element + return elementToRemove.parentNode.removeChild(elementToRemove) } From 98d36e6dc932cb95042fb621e1f3077175899df6 Mon Sep 17 00:00:00 2001 From: Alexandru Buliga Date: Thu, 21 Feb 2019 11:55:24 +0100 Subject: [PATCH 05/11] another round of comments addressed --- docs/src/prototypes/Prototypes.tsx | 4 +- .../dropdowns/inputWithDropdown.tsx | 42 ++++++++----------- 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/docs/src/prototypes/Prototypes.tsx b/docs/src/prototypes/Prototypes.tsx index 750dcd2fec..e70b791bd9 100644 --- a/docs/src/prototypes/Prototypes.tsx +++ b/docs/src/prototypes/Prototypes.tsx @@ -9,14 +9,14 @@ interface ComponentPrototypeProps extends PrototypeSectionProps { description?: string } -export const PrototypeSection: React.SFC = props => ( +export const PrototypeSection: React.FC = props => ( {props.title &&
{props.title}
} {props.children}
) -export const ComponentPrototype: React.SFC = props => ( +export const ComponentPrototype: React.FC = props => ( {(props.title || props.description) && ( diff --git a/docs/src/prototypes/dropdowns/inputWithDropdown.tsx b/docs/src/prototypes/dropdowns/inputWithDropdown.tsx index a9fb3da766..60ca45e095 100644 --- a/docs/src/prototypes/dropdowns/inputWithDropdown.tsx +++ b/docs/src/prototypes/dropdowns/inputWithDropdown.tsx @@ -30,6 +30,8 @@ class InputWithDropdown extends React.Component<{}, InputWithDropdownState> { state = this.initialState render() { + const { dropdownOpen, searchQuery } = this.state + return ( <>
{ onKeyUp={this.handleEditorKeyUp} style={editorStyle} /> - {this.renderDropdown()} + + + ) } - private renderDropdown = () => { - const { dropdownOpen, searchQuery } = this.state - - return ( - - - - ) - } - private hideDropdownAndRestoreEditor = (cb?: () => void) => { this.setState(this.initialState, () => { this.tryFocusEditor() From efb14907418a769ce5e99c617b366972236ca309 Mon Sep 17 00:00:00 2001 From: Alexandru Buliga Date: Thu, 21 Feb 2019 14:23:45 +0100 Subject: [PATCH 06/11] renamed file because of case insensitive behavior on Mac --- .../{inputWithDropdown.tsx => MentionsWithDropdown.tsx} | 8 ++++---- docs/src/prototypes/dropdowns/index.tsx | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) rename docs/src/prototypes/dropdowns/{inputWithDropdown.tsx => MentionsWithDropdown.tsx} (92%) diff --git a/docs/src/prototypes/dropdowns/inputWithDropdown.tsx b/docs/src/prototypes/dropdowns/MentionsWithDropdown.tsx similarity index 92% rename from docs/src/prototypes/dropdowns/inputWithDropdown.tsx rename to docs/src/prototypes/dropdowns/MentionsWithDropdown.tsx index 60ca45e095..c8a7f707f2 100644 --- a/docs/src/prototypes/dropdowns/inputWithDropdown.tsx +++ b/docs/src/prototypes/dropdowns/MentionsWithDropdown.tsx @@ -7,7 +7,7 @@ import { atMentionItems, AtMentionItem } from './dataMocks' import { insertTextAtCursorPosition } from './utils' import { CustomPortal } from './CustomPortal' -interface InputWithDropdownState { +interface MentionsWithDropdownState { dropdownOpen?: boolean searchQuery?: string } @@ -19,8 +19,8 @@ const editorStyle: React.CSSProperties = { outline: 0, } -class InputWithDropdown extends React.Component<{}, InputWithDropdownState> { - private readonly initialState: InputWithDropdownState = { +class MentionsWithDropdown extends React.Component<{}, MentionsWithDropdownState> { + private readonly initialState: MentionsWithDropdownState = { dropdownOpen: false, searchQuery: '', } @@ -100,4 +100,4 @@ class InputWithDropdown extends React.Component<{}, InputWithDropdownState> { private tryFocusEditor = () => _.invoke(this.contendEditableRef.current, 'focus') } -export default InputWithDropdown +export default MentionsWithDropdown diff --git a/docs/src/prototypes/dropdowns/index.tsx b/docs/src/prototypes/dropdowns/index.tsx index 44a72d4ded..46744aa9d0 100644 --- a/docs/src/prototypes/dropdowns/index.tsx +++ b/docs/src/prototypes/dropdowns/index.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { PrototypeSection, ComponentPrototype } from '../Prototypes' import AsyncDropdownSearch from './AsyncDropdownSearch' -import InputWithDropdown from './InputWithDropdown' +import MentionsWithDropdown from './MentionsWithDropdown' export default () => ( @@ -15,7 +15,7 @@ export default () => ( title="Input with Dropdown" description="Use the '@' key to mention people." > - + ) From 8ab6fa2ba4283205f5e71f555adf84c15b80dc57 Mon Sep 17 00:00:00 2001 From: Alexandru Buliga Date: Thu, 21 Feb 2019 18:49:25 +0100 Subject: [PATCH 07/11] - fixed bug with dropdown not being deleted from editor; - fixed bug with text not being inserted when dropdown is closed; - small refactoring of CustomPortal -> PortalAtCursorPosition --- .../dropdowns/MentionsWithDropdown.tsx | 42 +++++++++++-------- ...tomPortal.ts => PortalAtCursorPosition.ts} | 27 +++++++----- docs/src/prototypes/dropdowns/dataMocks.ts | 2 +- 3 files changed, 42 insertions(+), 29 deletions(-) rename docs/src/prototypes/dropdowns/{CustomPortal.ts => PortalAtCursorPosition.ts} (53%) diff --git a/docs/src/prototypes/dropdowns/MentionsWithDropdown.tsx b/docs/src/prototypes/dropdowns/MentionsWithDropdown.tsx index c8a7f707f2..484b683171 100644 --- a/docs/src/prototypes/dropdowns/MentionsWithDropdown.tsx +++ b/docs/src/prototypes/dropdowns/MentionsWithDropdown.tsx @@ -3,9 +3,9 @@ import * as _ from 'lodash' import keyboardKey from 'keyboard-key' import { Dropdown, DropdownProps } from '@stardust-ui/react' -import { atMentionItems, AtMentionItem } from './dataMocks' +import { atMentionItems } from './dataMocks' import { insertTextAtCursorPosition } from './utils' -import { CustomPortal } from './CustomPortal' +import { PortalAtCursorPosition } from './PortalAtCursorPosition' interface MentionsWithDropdownState { dropdownOpen?: boolean @@ -40,7 +40,7 @@ class MentionsWithDropdown extends React.Component<{}, MentionsWithDropdownState onKeyUp={this.handleEditorKeyUp} style={editorStyle} /> - + - + ) } - private hideDropdownAndRestoreEditor = (cb?: () => void) => { - this.setState(this.initialState, () => { - this.tryFocusEditor() - cb && cb() - }) - } - private handleEditorKeyUp = (e: React.KeyboardEvent) => { if (!this.state.dropdownOpen && keyboardKey.getCode(e) === keyboardKey.AtSign) { this.setState({ dropdownOpen: true }) } } - private handleSelectedChange = (e: React.SyntheticEvent, { value }: DropdownProps) => { - this.hideDropdownAndRestoreEditor(() => { - insertTextAtCursorPosition((value as AtMentionItem).header) - }) + private handleOpenChange = (e: React.SyntheticEvent, { open }: DropdownProps) => { + if (!open) { + this.resetStateAndUpdateEditor() + } } private handleSearchQueryChange = (e: React.SyntheticEvent, { searchQuery }: DropdownProps) => { @@ -88,15 +81,28 @@ class MentionsWithDropdown extends React.Component<{}, MentionsWithDropdownState switch (keyCode) { case keyboardKey.Backspace: // 8 if (this.state.searchQuery === '') { - this.hideDropdownAndRestoreEditor() + this.resetStateAndUpdateEditor() } break case keyboardKey.Escape: // 27 - this.hideDropdownAndRestoreEditor() + this.resetStateAndUpdateEditor() break } } + private resetStateAndUpdateEditor = () => { + const { searchQuery, dropdownOpen } = this.state + + if (dropdownOpen) { + this.setState(this.initialState, () => { + this.tryFocusEditor() + + // after the dropdown is closed the value of the search query is inserted in the editor at cursor position + insertTextAtCursorPosition(searchQuery) + }) + } + } + private tryFocusEditor = () => _.invoke(this.contendEditableRef.current, 'focus') } diff --git a/docs/src/prototypes/dropdowns/CustomPortal.ts b/docs/src/prototypes/dropdowns/PortalAtCursorPosition.ts similarity index 53% rename from docs/src/prototypes/dropdowns/CustomPortal.ts rename to docs/src/prototypes/dropdowns/PortalAtCursorPosition.ts index 231f236d5f..6469e2b5d7 100644 --- a/docs/src/prototypes/dropdowns/CustomPortal.ts +++ b/docs/src/prototypes/dropdowns/PortalAtCursorPosition.ts @@ -2,29 +2,36 @@ import * as React from 'react' import * as ReactDOM from 'react-dom' import { insertSpanAtCursorPosition, removeElement } from './utils' -export interface CustomPortalProps { +export interface PortalAtCursorPositionProps { mountNodeId: string open?: boolean } -export class CustomPortal extends React.Component { +export class PortalAtCursorPosition extends React.Component { private mountNodeInstance: HTMLElement = null - public render() { - this.setupMountNode() - return this.props.open && this.mountNodeInstance - ? ReactDOM.createPortal(this.props.children, this.mountNodeInstance) - : null + static defaultProps = { + mountNodeId: 'portal-at-cursor-position', } public componentWillUnmount() { this.removeMountNode() } + public render() { + const { children, open } = this.props + + this.setupMountNode() + return open && this.mountNodeInstance + ? ReactDOM.createPortal(children, this.mountNodeInstance) + : null + } + private setupMountNode = () => { - if (this.props.open) { - this.mountNodeInstance = - this.mountNodeInstance || insertSpanAtCursorPosition(this.props.mountNodeId) + const { mountNodeId, open } = this.props + + if (open) { + this.mountNodeInstance = this.mountNodeInstance || insertSpanAtCursorPosition(mountNodeId) } else { this.removeMountNode() } diff --git a/docs/src/prototypes/dropdowns/dataMocks.ts b/docs/src/prototypes/dropdowns/dataMocks.ts index 9c8e36592a..3ebcc80b6a 100644 --- a/docs/src/prototypes/dropdowns/dataMocks.ts +++ b/docs/src/prototypes/dropdowns/dataMocks.ts @@ -1,7 +1,7 @@ import * as _ from 'lodash' import { name, internet } from 'faker' -export interface AtMentionItem { +interface AtMentionItem { header: string image: string content: string From 40ef30f6da6f4acbfbf1fa8cd4eca47f2d52037d Mon Sep 17 00:00:00 2001 From: Alexandru Buliga Date: Thu, 21 Feb 2019 19:54:02 +0100 Subject: [PATCH 08/11] improved documentation for itemToString prop --- packages/react/src/components/Dropdown/Dropdown.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/react/src/components/Dropdown/Dropdown.tsx b/packages/react/src/components/Dropdown/Dropdown.tsx index 4f261b6a8c..61ef601548 100644 --- a/packages/react/src/components/Dropdown/Dropdown.tsx +++ b/packages/react/src/components/Dropdown/Dropdown.tsx @@ -101,7 +101,10 @@ export interface DropdownProps extends UIComponentProps string From fd016e59cf6566305e7c1e07f52daf070d41212e Mon Sep 17 00:00:00 2001 From: Alexandru Buliga Date: Fri, 22 Feb 2019 14:42:14 +0100 Subject: [PATCH 09/11] addressed comments and fixed issue with creating empty text node --- docs/src/prototypes/Prototypes.tsx | 4 ++-- docs/src/prototypes/dropdowns/MentionsWithDropdown.tsx | 6 ++++-- docs/src/prototypes/dropdowns/index.tsx | 2 +- docs/src/prototypes/dropdowns/utils.ts | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/src/prototypes/Prototypes.tsx b/docs/src/prototypes/Prototypes.tsx index e70b791bd9..0bc01ae9c4 100644 --- a/docs/src/prototypes/Prototypes.tsx +++ b/docs/src/prototypes/Prototypes.tsx @@ -2,11 +2,11 @@ import * as React from 'react' import { Box, Header, Segment } from '@stardust-ui/react' interface PrototypeSectionProps { - title?: string + title?: React.ReactNode } interface ComponentPrototypeProps extends PrototypeSectionProps { - description?: string + description?: React.ReactNode } export const PrototypeSection: React.FC = props => ( diff --git a/docs/src/prototypes/dropdowns/MentionsWithDropdown.tsx b/docs/src/prototypes/dropdowns/MentionsWithDropdown.tsx index 484b683171..7b84696af0 100644 --- a/docs/src/prototypes/dropdowns/MentionsWithDropdown.tsx +++ b/docs/src/prototypes/dropdowns/MentionsWithDropdown.tsx @@ -13,7 +13,9 @@ interface MentionsWithDropdownState { } const editorStyle: React.CSSProperties = { - backgroundColor: 'lightgrey', + backgroundColor: '#eee', + borderRadius: '5px', + border: '1px dashed grey', padding: '5px', minHeight: '100px', outline: 0, @@ -61,7 +63,7 @@ class MentionsWithDropdown extends React.Component<{}, MentionsWithDropdownState } private handleEditorKeyUp = (e: React.KeyboardEvent) => { - if (!this.state.dropdownOpen && keyboardKey.getCode(e) === keyboardKey.AtSign) { + if (!this.state.dropdownOpen && e.shiftKey && keyboardKey.getCode(e) === keyboardKey.AtSign) { this.setState({ dropdownOpen: true }) } } diff --git a/docs/src/prototypes/dropdowns/index.tsx b/docs/src/prototypes/dropdowns/index.tsx index 46744aa9d0..db5a8baf83 100644 --- a/docs/src/prototypes/dropdowns/index.tsx +++ b/docs/src/prototypes/dropdowns/index.tsx @@ -13,7 +13,7 @@ export default () => ( diff --git a/docs/src/prototypes/dropdowns/utils.ts b/docs/src/prototypes/dropdowns/utils.ts index 3c1ac4314d..03ffc6fa52 100644 --- a/docs/src/prototypes/dropdowns/utils.ts +++ b/docs/src/prototypes/dropdowns/utils.ts @@ -30,7 +30,7 @@ export const insertSpanAtCursorPosition = (id: string) => { export const insertTextAtCursorPosition = (text: string) => { if (!text) { - throw '[insertTextAtCursorPosition]: text must be supplied' + return null } const range = getRangeAtCursorPosition() From 5f3f6770f0545c664a93b81763511caf7349330f Mon Sep 17 00:00:00 2001 From: kuzhelov Date: Fri, 22 Feb 2019 17:23:22 +0300 Subject: [PATCH 10/11] improve visual appearance of async loading example --- .../dropdowns/AsyncDropdownSearch.tsx | 54 +++++++++++-------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/docs/src/prototypes/dropdowns/AsyncDropdownSearch.tsx b/docs/src/prototypes/dropdowns/AsyncDropdownSearch.tsx index 0e1223cc60..1dfa61f0dd 100644 --- a/docs/src/prototypes/dropdowns/AsyncDropdownSearch.tsx +++ b/docs/src/prototypes/dropdowns/AsyncDropdownSearch.tsx @@ -1,4 +1,4 @@ -import { Divider, Dropdown, DropdownProps, Loader } from '@stardust-ui/react' +import { Dropdown, DropdownProps, Flex, Label, Loader } from '@stardust-ui/react' import * as faker from 'faker' import * as _ from 'lodash' import * as React from 'react' @@ -54,12 +54,13 @@ class AsyncDropdownSearch extends React.Component<{}, SearchPageState> { fetchItems = () => { clearTimeout(this.searchTimer) - this.setState({ loading: true }) + if (this.state.items.length > 10) return + this.setState({ loading: true }) this.searchTimer = setTimeout(() => { this.setState(prevState => ({ loading: false, - items: [...prevState.items, ..._.times(10, createEntry)], + items: [...prevState.items, ..._.times(2, createEntry)], })) }, 2000) } @@ -68,26 +69,33 @@ class AsyncDropdownSearch extends React.Component<{}, SearchPageState> { const { items, loading, searchQuery, value } = this.state return ( - <> - , - }} - multiple - onSearchQueryChange={this.handleSearchQueryChange} - onSelectedChange={this.handleSelectedChange} - placeholder="Try to enter something..." - search - searchQuery={searchQuery} - toggleIndicator={false} - value={value} - /> - - - + + + , + }} + multiple + onSearchQueryChange={this.handleSearchQueryChange} + onSelectedChange={this.handleSelectedChange} + placeholder="Try to enter something..." + search + searchQuery={searchQuery} + toggleIndicator={false} + value={value} + noResultsMessage="We couldn't find any matches" + /> + + +
+ + +
+
+
) } } From a57f371d60b430c3145f9a9137f3a6874fa5711f Mon Sep 17 00:00:00 2001 From: Alexandru Buliga Date: Fri, 22 Feb 2019 15:34:35 +0100 Subject: [PATCH 11/11] changelog --- CHANGELOG.md | 3 +++ docs/src/prototypes/dropdowns/index.tsx | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9bcaf2b6f..5244862c38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Expose `Popup`'s content Ref @sophieH29 ([#913](https://github.com/stardust-ui/react/pull/913)) - Fix `Button` Teams theme styles to use semibold weight @notandrew ([#829](https://github.com/stardust-ui/react/pull/829)) +### Documentation +- Add `Editable Area with Dropdown` prototype for mentioning people using `@` character (only available in development mode) @Bugaa92 ([#931](https://github.com/stardust-ui/react/pull/931)) + ## [v0.21.1](https://github.com/stardust-ui/react/tree/v0.21.1) (2019-02-14) [Compare changes](https://github.com/stardust-ui/react/compare/v0.21.0...v0.21.1) diff --git a/docs/src/prototypes/dropdowns/index.tsx b/docs/src/prototypes/dropdowns/index.tsx index db5a8baf83..83666c8e43 100644 --- a/docs/src/prototypes/dropdowns/index.tsx +++ b/docs/src/prototypes/dropdowns/index.tsx @@ -12,7 +12,7 @@ export default () => (