Skip to content
This repository has been archived by the owner on Mar 4, 2020. It is now read-only.

Commit

Permalink
proto(dropdown): @mention scenario
Browse files Browse the repository at this point in the history
  • Loading branch information
bmdalex committed Feb 19, 2019
1 parent 161ab7d commit 4d55c7b
Show file tree
Hide file tree
Showing 13 changed files with 325 additions and 51 deletions.
6 changes: 3 additions & 3 deletions docs/src/components/Sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -343,10 +343,10 @@ class Sidebar extends React.Component<any, any> {
styles: menuItemStyles,
},
{
key: 'asyncdropdown',
content: 'Async Dropdown Search',
key: 'dropdowns',
content: 'Dropdowns',
as: NavLink,
to: '/prototype-async-dropdown-search',
to: '/prototype-dropdowns',
styles: menuItemStyles,
},
{
Expand Down
1 change: 0 additions & 1 deletion docs/src/prototypes/AsyncDropdownSearch/index.ts

This file was deleted.

29 changes: 29 additions & 0 deletions docs/src/prototypes/Protoypes.tsx
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?: string
}

interface ComponentPrototypeProps extends PrototypeSectionProps {
description?: string
}

export const PrototypeSection: React.SFC<ComponentPrototypeProps> = props => (
<Box style={{ margin: 20 }}>
{props.title && <Header as="h1">{props.title}</Header>}
{props.children}
</Box>
)

export const ComponentPrototype: React.SFC<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>
)
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -34,25 +34,55 @@ const createEntry = (): Entry => ({
// Prototype Search Page View
// ----------------------------------------
class AsyncDropdownSearch extends React.Component<{}, SearchPageState> {
private searchTimer: number

state = {
loading: false,
searchQuery: '',
items: [],
value: [],
}

searchTimer: number
render() {
const { items, loading, searchQuery, value } = this.state

return (
<>
<Dropdown
fluid
items={items}
loading={loading}
loadingMessage={{
content: <Loader label="Loading..." labelPosition="end" size="larger" />,
}}
multiple
onSearchQueryChange={this.handleSearchQueryChange}
onSelectedChange={this.handleSelectedChange}
placeholder="Try to enter something..."
search
searchQuery={searchQuery}
toggleIndicator={false}
value={value}
/>
<Divider />
<CodeSnippet mode="json" value={this.state} />
</>
)
}

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 })

Expand All @@ -63,40 +93,6 @@ class AsyncDropdownSearch extends React.Component<{}, SearchPageState> {
}))
}, 2000)
}

render() {
const { items, loading, searchQuery, value } = this.state

return (
<div style={{ margin: 20 }}>
<Segment>
<Header content="Async Dropdown Search" />
<p>Use the field to perform a simulated search.</p>
</Segment>

<Segment>
<Dropdown
fluid
items={items}
loading={loading}
loadingMessage={{
content: <Loader label="Loading..." labelPosition="end" size="larger" />,
}}
multiple
onSearchQueryChange={this.handleSearchQueryChange}
onSelectedChange={this.handleSelectedChange}
placeholder="Try to enter something..."
search
searchQuery={searchQuery}
toggleIndicator={false}
value={value}
/>
<Divider />
<CodeSnippet mode="json" value={this.state} />
</Segment>
</div>
)
}
}

export default AsyncDropdownSearch
53 changes: 53 additions & 0 deletions docs/src/prototypes/dropdowns/dataMocks.ts
Original file line number Diff line number Diff line change
@@ -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',
},
]
21 changes: 21 additions & 0 deletions docs/src/prototypes/dropdowns/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import * as React from 'react'
import { PrototypeSection, ComponentPrototype } from '../Protoypes'
import AsyncDropdownSearch from './AsyncDropdownSearch'
import InputWithDropdownExample from './inputWithDropdown'

export default () => (
<PrototypeSection title="Dropdowns">
<ComponentPrototype
title="Async Dropdown Search"
description="Use the field to perform a simulated search."
>
<AsyncDropdownSearch />
</ComponentPrototype>
<ComponentPrototype
title="Input with Dropdown"
description="Use the '@' key to mention people."
>
<InputWithDropdownExample />
</ComponentPrototype>
</PrototypeSection>
)
136 changes: 136 additions & 0 deletions docs/src/prototypes/dropdowns/inputWithDropdown.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement>()

public state: InputWithDropdownState = {
dropdownValue: null,
searchQuery: '',
}

render() {
return (
<div
contentEditable
ref={this.contendEditableRef}
onKeyUp={this.handleEditorKeyUp}
style={this.editorStyle}
/>
)
}

private showDropdown = () => {
this.dropdownExists = true
insertNodeAtCursorPosition({ id: this.mountNodeId })

const node = this.getMountNode()
ReactDOM.render(
<Provider theme={themes.teams}>
<Dropdown
defaultOpen={true}
inline
search
items={atMentionItems}
toggleIndicator={null}
searchInput={{
input: { autoFocus: true },
onInputKeyDown: this.handleInputKeyDown,
}}
onSelectedChange={this.handleSelectedChange}
onSearchQueryChange={this.handleSearchQueryChange}
noResultsMessage="We couldn't find any matches."
/>
</Provider>,
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
Loading

0 comments on commit 4d55c7b

Please sign in to comment.