Skip to content

Commit a6ef4b0

Browse files
committed
Add basic Dropdown stories
1 parent 9b5d68c commit a6ef4b0

File tree

8 files changed

+7733
-4931
lines changed

8 files changed

+7733
-4931
lines changed

.storybook/main.js

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
module.exports = {
2-
stories: ['../stories/**/*.stories.tsx'],
3-
addons: ['@storybook/addon-actions', '@storybook/addon-links'],
4-
};
2+
"stories": [
3+
"../stories/*.stories.mdx",
4+
"../stories/*.stories.@(js|jsx|ts|tsx)"
5+
],
6+
"addons": [
7+
"@storybook/addon-links",
8+
"@storybook/addon-essentials"
9+
]
10+
}

.storybook/preview.js

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export const parameters = {
2+
actions: { argTypesRegex: "^on[A-Z].*" },
3+
controls: {
4+
matchers: {
5+
color: /(background|color)$/i,
6+
date: /Date$/,
7+
},
8+
},
9+
}

example/Dropdown.tsx

+96-87
Original file line numberDiff line numberDiff line change
@@ -1,106 +1,115 @@
1-
import React, {useCallback, useEffect} from 'react';
1+
import React, {ChangeEvent, KeyboardEvent, useCallback, useEffect, useMemo, useState} from 'react';
22
import ReactDOM from 'react-dom';
33
import {useDropdown, DropdownState, ReducerAction} from '../src';
44
import './Dropdown.css';
55

6-
type Option = {
7-
label: string;
6+
export type Item = {
7+
name: string;
88
value: string;
9-
}
9+
};
1010

11-
type DropdownProps = {
12-
items: Array<Option>;
13-
value?: Option;
14-
onChange?(value: Option): void;
15-
root?: HTMLElement;
16-
autoScroll?: boolean;
11+
type Props = {
12+
onSelect: (item: Item) => void;
13+
value?: Item;
1714
}
18-
export const Dropdown = ({
19-
items,
20-
value,
21-
onChange = () => {},
22-
autoScroll,
23-
root,
24-
}: DropdownProps) => {
25-
26-
const reducer = (state: DropdownState, action: ReducerAction<Option>) => {
27-
console.log('Reducer', state, action);
28-
switch (action.type) {
29-
default:
30-
return state;
31-
}
15+
16+
const items: Item[] = [
17+
{
18+
name: 'NewYork',
19+
value: 'NewYork'
20+
},
21+
{
22+
name: 'Moscow',
23+
value: 'Moscow'
24+
},
25+
{
26+
name: 'London',
27+
value: 'London'
28+
},
29+
{
30+
name: 'Amsterdam',
31+
value: 'Amsterdam'
32+
},
33+
];
34+
35+
36+
37+
export const Dropdown: React.FC<Props> = ({onSelect, value}) => {
38+
const [inputValue, setInputValue] = useState<string>('');
39+
40+
const handleSelect = (item: Item) => {
41+
setInputValue(item.name);
42+
onSelect(item);
3243
}
3344

45+
const options = useMemo(() => {
46+
return items.filter(item => item.name.includes(inputValue));
47+
}, [inputValue])
48+
3449
const {
3550
isOpen,
36-
getInputProps,
51+
highlightedIndex,
3752
getWrapperProps,
38-
getItemProps,
53+
getInputProps,
3954
getMenuProps,
40-
highlightedIndex,
55+
getItemProps,
4156
setOpen,
42-
} = useDropdown<Option>({
43-
reducer,
44-
onSelect: (value: Option) => {
45-
onChange(value);
46-
},
47-
items,
48-
autoScroll,
49-
root
50-
})
51-
52-
const inputProps = getInputProps();
53-
54-
const handleInputKeyDown = useCallback((ev: KeyboardEvent) => {
55-
switch (ev.key) {
56-
case 'Backspace':
57-
onChange(null);
58-
}
59-
}, []);
57+
} = useDropdown<Item>({items: options, onSelect: handleSelect})
58+
59+
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
60+
setOpen(true);
61+
62+
setInputValue(event.target.value);
63+
onSelect(undefined);
64+
};
6065

61-
useEffect(() => {
62-
inputProps.ref.current.addEventListener('keydown', handleInputKeyDown);
66+
const handleKeyDown = (event: KeyboardEvent) => {
67+
switch (event.code) {
68+
case 'ArrowDown':
69+
setOpen(true);
70+
break;
6371

64-
return () => {
65-
inputProps.ref.current.removeEventListener('keydown', handleInputKeyDown);
72+
// case 'Backspace':
73+
// if(inputValue === value?.name) {
74+
// onSelect(undefined);
75+
// }
76+
// break;
6677
}
67-
}, [inputProps]);
68-
69-
const toggle = useCallback(() => {
70-
setOpen(!isOpen);
71-
}, [isOpen])
72-
73-
return (
74-
<div>
75-
<div {...getWrapperProps()} className="wrapper">
76-
<span>{value?.label}</span>
77-
<input
78-
type="text"
79-
className="input"
80-
value={value?.label}
81-
{...inputProps}
82-
/>
83-
<span onClick={toggle}>{isOpen ? '-' : '+'}</span>
84-
</div>
85-
{
86-
isOpen && (
87-
ReactDOM.createPortal(
88-
<ul className="menu" {...getMenuProps()}>
89-
{
90-
items.map((item, index) => (
91-
<li
92-
className={`item ${highlightedIndex === index ? 'active' : ''}`}
93-
{...getItemProps(item, index)}>
94-
{item.label}
95-
</li>
96-
))
97-
}
98-
</ul>,
99-
document.body
100-
)
78+
}
10179

102-
)
103-
}
104-
</div>
105-
)
80+
const handleBlur = () => {
81+
console.log('blur');
82+
setInputValue('');
83+
}
84+
85+
return <div className='wrapper' {...getWrapperProps()} onKeyDown={handleKeyDown} onBlur={handleBlur}>
86+
87+
<input
88+
className='input'
89+
type="text" id="input" {...getInputProps()}
90+
placeholder='Select city'
91+
value={inputValue}
92+
onChange={handleChange}
93+
autoComplete='off'
94+
95+
/>
96+
97+
{isOpen &&
98+
<ul className='menu' {...getMenuProps() as any}>
99+
{options.length === 0 ?
100+
<li>No data</li>
101+
: options.map(
102+
(item: Item, index) =>
103+
<li
104+
key={item.value}
105+
className={highlightedIndex === index ? 'item active' : 'item'}
106+
{...getItemProps(item, index)}
107+
>
108+
{item.name}
109+
</li>
110+
)
111+
}
112+
</ul>
113+
}
114+
</div>
106115
}

0 commit comments

Comments
 (0)