This repository was archived by the owner on Mar 4, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 52
feat(prototypes): mention scenario with dropdown #931
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
ad9d392
proto(dropdown): @mention scenario
bmdalex cb4f482
reverted AsyncDropdownSearch changes
bmdalex e29d76b
implemented the new Dropdown using ReactDOM.createPortal instead of R…
bmdalex 6dadd4f
- addressed PR comments;
bmdalex 98d36e6
another round of comments addressed
bmdalex efb1490
renamed file because of case insensitive behavior on Mac
bmdalex 8ab6fa2
- fixed bug with dropdown not being deleted from editor;
bmdalex 40ef30f
improved documentation for itemToString prop
bmdalex fd016e5
addressed comments and fixed issue with creating empty text node
bmdalex 5f3f677
improve visual appearance of async loading example
kuzhelov-ms a57f371
changelog
bmdalex File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 }, | ||
kuzhelov marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 hidden or 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 hidden or 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 hidden or 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 hidden or 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) | ||
} | ||
bmdalex marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains hidden or 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 hidden or 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.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.