This repository has been archived by the owner on Mar 4, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 53
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(prototypes): mention scenario with dropdown (#931)
* proto(dropdown): @mention scenario * reverted AsyncDropdownSearch changes * implemented the new Dropdown using ReactDOM.createPortal instead of ReactDOM.render * - addressed PR comments; - refactoring of using portal - fix for all styling regressions * another round of comments addressed * renamed file because of case insensitive behavior on Mac * - 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 * improved documentation for itemToString prop * addressed comments and fixed issue with creating empty text node * improve visual appearance of async loading example * changelog
- Loading branch information
Alexandru Buliga
authored
Feb 22, 2019
1 parent
ccc755b
commit 5d930fa
Showing
16 changed files
with
309 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import * as React from 'react' | ||
import { Box, Header, Segment } from '@stardust-ui/react' | ||
|
||
interface PrototypeSectionProps { | ||
title?: React.ReactNode | ||
} | ||
|
||
interface ComponentPrototypeProps extends PrototypeSectionProps { | ||
description?: React.ReactNode | ||
} | ||
|
||
export const PrototypeSection: React.FC<ComponentPrototypeProps> = props => ( | ||
<Box style={{ margin: 20 }}> | ||
{props.title && <Header as="h1">{props.title}</Header>} | ||
{props.children} | ||
</Box> | ||
) | ||
|
||
export const ComponentPrototype: React.FC<ComponentPrototypeProps> = props => ( | ||
<Box style={{ marginTop: 20 }}> | ||
{(props.title || props.description) && ( | ||
<Segment> | ||
{props.title && <Header as="h3">{props.title}</Header>} | ||
{props.description && <p>{props.description}</p>} | ||
</Segment> | ||
)} | ||
<Segment>{props.children}</Segment> | ||
</Box> | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
import * as React from 'react' | ||
import * as _ from 'lodash' | ||
import keyboardKey from 'keyboard-key' | ||
import { Dropdown, DropdownProps } from '@stardust-ui/react' | ||
|
||
import { atMentionItems } from './dataMocks' | ||
import { insertTextAtCursorPosition } from './utils' | ||
import { PortalAtCursorPosition } from './PortalAtCursorPosition' | ||
|
||
interface MentionsWithDropdownState { | ||
dropdownOpen?: boolean | ||
searchQuery?: string | ||
} | ||
|
||
const editorStyle: React.CSSProperties = { | ||
backgroundColor: '#eee', | ||
borderRadius: '5px', | ||
border: '1px dashed grey', | ||
padding: '5px', | ||
minHeight: '100px', | ||
outline: 0, | ||
} | ||
|
||
class MentionsWithDropdown extends React.Component<{}, MentionsWithDropdownState> { | ||
private readonly initialState: MentionsWithDropdownState = { | ||
dropdownOpen: false, | ||
searchQuery: '', | ||
} | ||
|
||
private contendEditableRef = React.createRef<HTMLDivElement>() | ||
|
||
state = this.initialState | ||
|
||
render() { | ||
const { dropdownOpen, searchQuery } = this.state | ||
|
||
return ( | ||
<> | ||
<div | ||
contentEditable | ||
ref={this.contendEditableRef} | ||
onKeyUp={this.handleEditorKeyUp} | ||
style={editorStyle} | ||
/> | ||
<PortalAtCursorPosition open={dropdownOpen}> | ||
<Dropdown | ||
defaultOpen={true} | ||
inline | ||
search | ||
items={atMentionItems} | ||
toggleIndicator={null} | ||
searchInput={{ | ||
input: { autoFocus: true, size: searchQuery.length + 1 }, | ||
onInputKeyDown: this.handleInputKeyDown, | ||
}} | ||
onOpenChange={this.handleOpenChange} | ||
onSearchQueryChange={this.handleSearchQueryChange} | ||
noResultsMessage="We couldn't find any matches." | ||
/> | ||
</PortalAtCursorPosition> | ||
</> | ||
) | ||
} | ||
|
||
private handleEditorKeyUp = (e: React.KeyboardEvent) => { | ||
if (!this.state.dropdownOpen && e.shiftKey && keyboardKey.getCode(e) === keyboardKey.AtSign) { | ||
this.setState({ dropdownOpen: true }) | ||
} | ||
} | ||
|
||
private handleOpenChange = (e: React.SyntheticEvent, { open }: DropdownProps) => { | ||
if (!open) { | ||
this.resetStateAndUpdateEditor() | ||
} | ||
} | ||
|
||
private handleSearchQueryChange = (e: React.SyntheticEvent, { searchQuery }: DropdownProps) => { | ||
this.setState({ searchQuery }) | ||
} | ||
|
||
private handleInputKeyDown = (e: React.KeyboardEvent) => { | ||
const keyCode = keyboardKey.getCode(e) | ||
switch (keyCode) { | ||
case keyboardKey.Backspace: // 8 | ||
if (this.state.searchQuery === '') { | ||
this.resetStateAndUpdateEditor() | ||
} | ||
break | ||
case keyboardKey.Escape: // 27 | ||
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') | ||
} | ||
|
||
export default MentionsWithDropdown |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import * as React from 'react' | ||
import * as ReactDOM from 'react-dom' | ||
import { insertSpanAtCursorPosition, removeElement } from './utils' | ||
|
||
export interface PortalAtCursorPositionProps { | ||
mountNodeId: string | ||
open?: boolean | ||
} | ||
|
||
export class PortalAtCursorPosition extends React.Component<PortalAtCursorPositionProps> { | ||
private mountNodeInstance: HTMLElement = 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 = () => { | ||
const { mountNodeId, open } = this.props | ||
|
||
if (open) { | ||
this.mountNodeInstance = this.mountNodeInstance || insertSpanAtCursorPosition(mountNodeId) | ||
} else { | ||
this.removeMountNode() | ||
} | ||
} | ||
|
||
private removeMountNode = () => { | ||
if (this.mountNodeInstance) { | ||
removeElement(this.mountNodeInstance) | ||
this.mountNodeInstance = null | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import * as _ from 'lodash' | ||
import { name, internet } from 'faker' | ||
|
||
interface AtMentionItem { | ||
header: string | ||
image: string | ||
content: string | ||
} | ||
|
||
export const atMentionItems: AtMentionItem[] = _.times(10, () => ({ | ||
header: `${name.firstName()} ${name.lastName()}`, | ||
image: internet.avatar(), | ||
content: name.title(), | ||
})) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import * as React from 'react' | ||
import { PrototypeSection, ComponentPrototype } from '../Prototypes' | ||
import AsyncDropdownSearch from './AsyncDropdownSearch' | ||
import MentionsWithDropdown from './MentionsWithDropdown' | ||
|
||
export default () => ( | ||
<PrototypeSection title="Dropdowns"> | ||
<ComponentPrototype | ||
title="Async Dropdown Search" | ||
description="Use the field to perform a simulated search." | ||
> | ||
<AsyncDropdownSearch /> | ||
</ComponentPrototype> | ||
<ComponentPrototype | ||
title="Editable Area with Dropdown" | ||
description="Type text into editable area below. Use the '@' key to mention people." | ||
> | ||
<MentionsWithDropdown /> | ||
</ComponentPrototype> | ||
</PrototypeSection> | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
const getRangeAtCursorPosition = () => { | ||
if (!window.getSelection) { | ||
return null | ||
} | ||
|
||
const sel = window.getSelection() | ||
if (!sel.getRangeAt || !sel.rangeCount) { | ||
return null | ||
} | ||
|
||
return sel.getRangeAt(0) | ||
} | ||
|
||
export const insertSpanAtCursorPosition = (id: string) => { | ||
if (!id) { | ||
throw '[insertSpanAtCursorPosition]: id must be supplied' | ||
} | ||
|
||
const range = getRangeAtCursorPosition() | ||
if (!range) { | ||
return null | ||
} | ||
|
||
const elem = document.createElement('span') | ||
elem.id = id | ||
range.insertNode(elem) | ||
|
||
return elem | ||
} | ||
|
||
export const insertTextAtCursorPosition = (text: string) => { | ||
if (!text) { | ||
return null | ||
} | ||
|
||
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.