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

Commit 4d55c7b

Browse files
committed
proto(dropdown): @mention scenario
1 parent 161ab7d commit 4d55c7b

File tree

13 files changed

+325
-51
lines changed

13 files changed

+325
-51
lines changed

docs/src/components/Sidebar/Sidebar.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -343,10 +343,10 @@ class Sidebar extends React.Component<any, any> {
343343
styles: menuItemStyles,
344344
},
345345
{
346-
key: 'asyncdropdown',
347-
content: 'Async Dropdown Search',
346+
key: 'dropdowns',
347+
content: 'Dropdowns',
348348
as: NavLink,
349-
to: '/prototype-async-dropdown-search',
349+
to: '/prototype-dropdowns',
350350
styles: menuItemStyles,
351351
},
352352
{

docs/src/prototypes/AsyncDropdownSearch/index.ts

-1
This file was deleted.

docs/src/prototypes/Protoypes.tsx

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import * as React from 'react'
2+
import { Box, Header, Segment } from '@stardust-ui/react'
3+
4+
interface PrototypeSectionProps {
5+
title?: string
6+
}
7+
8+
interface ComponentPrototypeProps extends PrototypeSectionProps {
9+
description?: string
10+
}
11+
12+
export const PrototypeSection: React.SFC<ComponentPrototypeProps> = props => (
13+
<Box style={{ margin: 20 }}>
14+
{props.title && <Header as="h1">{props.title}</Header>}
15+
{props.children}
16+
</Box>
17+
)
18+
19+
export const ComponentPrototype: React.SFC<ComponentPrototypeProps> = props => (
20+
<Box style={{ marginTop: 20 }}>
21+
{(props.title || props.description) && (
22+
<Segment>
23+
{props.title && <Header as="h3">{props.title}</Header>}
24+
{props.description && <p>{props.description}</p>}
25+
</Segment>
26+
)}
27+
<Segment>{props.children}</Segment>
28+
</Box>
29+
)
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Divider, Dropdown, DropdownProps, Header, Loader, Segment } from '@stardust-ui/react'
1+
import { Divider, Dropdown, DropdownProps, Loader } from '@stardust-ui/react'
22
import * as faker from 'faker'
33
import * as _ from 'lodash'
44
import * as React from 'react'
@@ -34,25 +34,55 @@ const createEntry = (): Entry => ({
3434
// Prototype Search Page View
3535
// ----------------------------------------
3636
class AsyncDropdownSearch extends React.Component<{}, SearchPageState> {
37+
private searchTimer: number
38+
3739
state = {
3840
loading: false,
3941
searchQuery: '',
4042
items: [],
4143
value: [],
4244
}
4345

44-
searchTimer: number
46+
render() {
47+
const { items, loading, searchQuery, value } = this.state
48+
49+
return (
50+
<>
51+
<Dropdown
52+
fluid
53+
items={items}
54+
loading={loading}
55+
loadingMessage={{
56+
content: <Loader label="Loading..." labelPosition="end" size="larger" />,
57+
}}
58+
multiple
59+
onSearchQueryChange={this.handleSearchQueryChange}
60+
onSelectedChange={this.handleSelectedChange}
61+
placeholder="Try to enter something..."
62+
search
63+
searchQuery={searchQuery}
64+
toggleIndicator={false}
65+
value={value}
66+
/>
67+
<Divider />
68+
<CodeSnippet mode="json" value={this.state} />
69+
</>
70+
)
71+
}
4572

46-
handleSelectedChange = (e: React.SyntheticEvent, { searchQuery, value }: DropdownProps) => {
73+
private handleSelectedChange = (
74+
e: React.SyntheticEvent,
75+
{ searchQuery, value }: DropdownProps,
76+
) => {
4777
this.setState({ value: value as Entry[], searchQuery })
4878
}
4979

50-
handleSearchQueryChange = (e: React.SyntheticEvent, { searchQuery }: DropdownProps) => {
80+
private handleSearchQueryChange = (e: React.SyntheticEvent, { searchQuery }: DropdownProps) => {
5181
this.setState({ searchQuery })
5282
this.fetchItems()
5383
}
5484

55-
fetchItems = () => {
85+
private fetchItems = () => {
5686
clearTimeout(this.searchTimer)
5787
this.setState({ loading: true })
5888

@@ -63,40 +93,6 @@ class AsyncDropdownSearch extends React.Component<{}, SearchPageState> {
6393
}))
6494
}, 2000)
6595
}
66-
67-
render() {
68-
const { items, loading, searchQuery, value } = this.state
69-
70-
return (
71-
<div style={{ margin: 20 }}>
72-
<Segment>
73-
<Header content="Async Dropdown Search" />
74-
<p>Use the field to perform a simulated search.</p>
75-
</Segment>
76-
77-
<Segment>
78-
<Dropdown
79-
fluid
80-
items={items}
81-
loading={loading}
82-
loadingMessage={{
83-
content: <Loader label="Loading..." labelPosition="end" size="larger" />,
84-
}}
85-
multiple
86-
onSearchQueryChange={this.handleSearchQueryChange}
87-
onSelectedChange={this.handleSelectedChange}
88-
placeholder="Try to enter something..."
89-
search
90-
searchQuery={searchQuery}
91-
toggleIndicator={false}
92-
value={value}
93-
/>
94-
<Divider />
95-
<CodeSnippet mode="json" value={this.state} />
96-
</Segment>
97-
</div>
98-
)
99-
}
10096
}
10197

10298
export default AsyncDropdownSearch
+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
export interface AtMention {
2+
header: string
3+
image: string
4+
content: string
5+
}
6+
7+
export const atMentionItems: AtMention[] = [
8+
{
9+
header: 'Bruce Wayne',
10+
image: 'public/images/avatar/small/matt.jpg',
11+
content: 'Software Engineer',
12+
},
13+
{
14+
header: 'Natasha Romanoff',
15+
image: 'public/images/avatar/small/jenny.jpg',
16+
content: 'UX Designer 2',
17+
},
18+
{
19+
header: 'Steven Strange',
20+
image: 'public/images/avatar/small/joe.jpg',
21+
content: 'Principal Software Engineering Manager',
22+
},
23+
{
24+
header: 'Alfred Pennyworth',
25+
image: 'public/images/avatar/small/justen.jpg',
26+
content: 'Technology Consultant',
27+
},
28+
{
29+
header: `Scarlett O'Hara`,
30+
image: 'public/images/avatar/small/laura.jpg',
31+
content: 'Software Engineer 2',
32+
},
33+
{
34+
header: 'Imperator Furiosa',
35+
image: 'public/images/avatar/small/veronika.jpg',
36+
content: 'Boss',
37+
},
38+
{
39+
header: 'Bruce Banner',
40+
image: 'public/images/avatar/small/chris.jpg',
41+
content: 'Senior Computer Scientist',
42+
},
43+
{
44+
header: 'Peter Parker',
45+
image: 'public/images/avatar/small/daniel.jpg',
46+
content: 'Partner Software Engineer',
47+
},
48+
{
49+
header: 'Selina Kyle',
50+
image: 'public/images/avatar/small/ade.jpg',
51+
content: 'Graphic Designer',
52+
},
53+
]
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import * as React from 'react'
2+
import { PrototypeSection, ComponentPrototype } from '../Protoypes'
3+
import AsyncDropdownSearch from './AsyncDropdownSearch'
4+
import InputWithDropdownExample from './inputWithDropdown'
5+
6+
export default () => (
7+
<PrototypeSection title="Dropdowns">
8+
<ComponentPrototype
9+
title="Async Dropdown Search"
10+
description="Use the field to perform a simulated search."
11+
>
12+
<AsyncDropdownSearch />
13+
</ComponentPrototype>
14+
<ComponentPrototype
15+
title="Input with Dropdown"
16+
description="Use the '@' key to mention people."
17+
>
18+
<InputWithDropdownExample />
19+
</ComponentPrototype>
20+
</PrototypeSection>
21+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import * as React from 'react'
2+
import * as ReactDOM from 'react-dom'
3+
import * as _ from 'lodash'
4+
import keyboardKey from 'keyboard-key'
5+
import { Provider, Dropdown, DropdownProps, Input, themes } from '@stardust-ui/react'
6+
7+
import { atMentionItems, AtMention } from './dataMocks'
8+
import { insertNodeAtCursorPosition, removeElement } from './utils'
9+
10+
interface InputWithDropdownState {
11+
dropdownValue?: string
12+
searchQuery?: string
13+
}
14+
15+
class InputWithDropdownExample extends React.Component<{}, InputWithDropdownState> {
16+
private readonly mountNodeId = 'dropdown-mount-node'
17+
private readonly dropdownInputSelector = `#${this.mountNodeId} .${Input.slotClassNames.input}`
18+
private readonly editorStyle: React.CSSProperties = {
19+
backgroundColor: 'lightgrey',
20+
padding: '5px',
21+
minHeight: '100px',
22+
outline: 0,
23+
}
24+
25+
private dropdownExists = false
26+
private contendEditableRef = React.createRef<HTMLDivElement>()
27+
28+
public state: InputWithDropdownState = {
29+
dropdownValue: null,
30+
searchQuery: '',
31+
}
32+
33+
render() {
34+
return (
35+
<div
36+
contentEditable
37+
ref={this.contendEditableRef}
38+
onKeyUp={this.handleEditorKeyUp}
39+
style={this.editorStyle}
40+
/>
41+
)
42+
}
43+
44+
private showDropdown = () => {
45+
this.dropdownExists = true
46+
insertNodeAtCursorPosition({ id: this.mountNodeId })
47+
48+
const node = this.getMountNode()
49+
ReactDOM.render(
50+
<Provider theme={themes.teams}>
51+
<Dropdown
52+
defaultOpen={true}
53+
inline
54+
search
55+
items={atMentionItems}
56+
toggleIndicator={null}
57+
searchInput={{
58+
input: { autoFocus: true },
59+
onInputKeyDown: this.handleInputKeyDown,
60+
}}
61+
onSelectedChange={this.handleSelectedChange}
62+
onSearchQueryChange={this.handleSearchQueryChange}
63+
noResultsMessage="We couldn't find any matches."
64+
/>
65+
</Provider>,
66+
node,
67+
)
68+
}
69+
70+
private hideDropdownAndRestoreEditor = () => {
71+
const node = this.getMountNode()
72+
ReactDOM.unmountComponentAtNode(node)
73+
removeElement(node)
74+
75+
this.tryFocusEditor()
76+
this.dropdownExists = false
77+
}
78+
79+
private handleEditorKeyUp = (e: React.KeyboardEvent) => {
80+
if (!this.dropdownExists && keyboardKey.getCode(e) === keyboardKey.AtSign) {
81+
this.showDropdown()
82+
this.setInputElementSize(0)
83+
}
84+
}
85+
86+
private handleSelectedChange = (
87+
e: React.SyntheticEvent,
88+
{ searchQuery, value }: DropdownProps,
89+
) => {
90+
const dropdownValue = (value as AtMention).header
91+
this.hideDropdownAndRestoreEditor()
92+
insertNodeAtCursorPosition({ text: dropdownValue })
93+
94+
this.setState({ searchQuery, dropdownValue })
95+
}
96+
97+
private handleSearchQueryChange = (e: React.SyntheticEvent, { searchQuery }: DropdownProps) => {
98+
this.setInputElementSize(searchQuery.length)
99+
this.setState({ searchQuery })
100+
}
101+
102+
private handleInputKeyDown = (e: React.KeyboardEvent) => {
103+
const code = keyboardKey.getCode(e)
104+
switch (code) {
105+
case keyboardKey.Backspace: // 8
106+
if (this.state.searchQuery === '') {
107+
this.hideDropdownAndRestoreEditor()
108+
}
109+
break
110+
case keyboardKey.Escape: // 27
111+
this.hideDropdownAndRestoreEditor()
112+
break
113+
}
114+
}
115+
116+
private tryFocusEditor = () => {
117+
if (this.contendEditableRef) {
118+
this.contendEditableRef.current.focus()
119+
}
120+
}
121+
122+
private getMountNode = () => document.getElementById(this.mountNodeId)
123+
124+
private getInputElement = (): HTMLInputElement =>
125+
document.querySelector(this.dropdownInputSelector)
126+
127+
private setInputElementSize = (size: number) => {
128+
const input = this.getInputElement()
129+
if (input) {
130+
input.size = size || 0 + 1
131+
console.log('setInputElementSize: ', input.size)
132+
}
133+
}
134+
}
135+
136+
export default InputWithDropdownExample

0 commit comments

Comments
 (0)