Skip to content

Commit 82f2a8f

Browse files
committed
feat(SelectBox): Init
* Creates select box component * Implements arrow down and arrow up event treatment * implements scroll to selected item on render * implements default stories * implements scrollbars
1 parent 0827a55 commit 82f2a8f

File tree

5 files changed

+215
-6
lines changed

5 files changed

+215
-6
lines changed

package-lock.json

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/SelectBox/SelectBox.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import React, { useRef, useEffect } from 'react';
2+
import propTypes from 'prop-types';
3+
import {
4+
StyledOptionsList,
5+
StyledOptionsListItemInnerButton,
6+
StyledOptionsListItem
7+
} from './SelectBox.styles';
8+
import { StyledCutout } from '../Cutout/Cutout';
9+
10+
const SelectBox = React.forwardRef(function SelectBox(props) {
11+
const { options, value, onSelect, width, height } = props;
12+
const selectedListItemRef = useRef(null);
13+
const listRef = useRef(null);
14+
15+
const handleKeyDown = event => {
16+
const { key } = event;
17+
switch (key) {
18+
case 'ArrowDown':
19+
if (value < options.length - 1) onSelect(value + 1);
20+
break;
21+
case 'ArrowUp':
22+
if (value > 0) onSelect(value - 1);
23+
break;
24+
default:
25+
break;
26+
}
27+
};
28+
29+
useEffect(() => {
30+
selectedListItemRef.current.scrollIntoView({
31+
behavior: 'smooth',
32+
block: 'start'
33+
});
34+
}, [selectedListItemRef]);
35+
36+
return (
37+
<StyledCutout>
38+
<StyledOptionsList
39+
style={{ width, height }}
40+
ref={listRef}
41+
onKeyDown={handleKeyDown}
42+
>
43+
{options.map(o => (
44+
<StyledOptionsListItem key={o.value.toString()}>
45+
<StyledOptionsListItemInnerButton
46+
onClick={() => onSelect(o.value)}
47+
type='button'
48+
autoFocus={o.value === value}
49+
isSelected={o.value === value}
50+
ref={o.value === value ? selectedListItemRef : null}
51+
>
52+
{o.label}
53+
</StyledOptionsListItemInnerButton>
54+
</StyledOptionsListItem>
55+
))}
56+
</StyledOptionsList>
57+
</StyledCutout>
58+
);
59+
});
60+
61+
SelectBox.defaultProps = {
62+
onSelect: () => {},
63+
options: [],
64+
value: 0,
65+
width: '300px',
66+
height: '150px'
67+
};
68+
69+
SelectBox.propTypes = {
70+
onSelect: propTypes.func,
71+
options: propTypes.arrayOf(
72+
propTypes.objectOf({ value: propTypes.number, label: propTypes.string })
73+
),
74+
value: propTypes.number,
75+
width: propTypes.string,
76+
height: propTypes.string
77+
};
78+
79+
export default SelectBox;

src/SelectBox/SelectBox.spec.js

Whitespace-only changes.

src/SelectBox/SelectBox.stories.js

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/* eslint-disable no-console */
2+
import React, { useState } from 'react';
3+
import styled from 'styled-components';
4+
5+
import { Window, WindowContent, Cutout, Fieldset } from 'react95';
6+
import SelectBox from './SelectBox';
7+
8+
const Wrapper = styled.div`
9+
background: ${({ theme }) => theme.material};
10+
padding: 5rem;
11+
fieldset,
12+
fieldset {
13+
margin-bottom: 2rem;
14+
}
15+
legend + * {
16+
margin-bottom: 1rem;
17+
}
18+
#default-selects {
19+
width: 200px;
20+
}
21+
#cutout > div {
22+
width: 250px;
23+
padding: 1rem;
24+
background: ${({ theme }) => theme.canvas};
25+
& > p {
26+
margin-bottom: 2rem;
27+
}
28+
}
29+
`;
30+
31+
export default {
32+
title: 'SelectBox',
33+
component: SelectBox,
34+
decorators: [story => <Wrapper>{story()}</Wrapper>]
35+
};
36+
37+
const options = [
38+
{ value: 0, label: '[None]' },
39+
{ value: 1, label: 'Pikachu' },
40+
{ value: 2, label: 'Bulbasaur' },
41+
{ value: 3, label: 'Squirtle' },
42+
{ value: 4, label: 'Mega Charizard Y' },
43+
{ value: 5, label: 'Jigglypuff' },
44+
{ value: 6, label: 'Snorlax' },
45+
{ value: 7, label: 'Geodude' }
46+
];
47+
48+
export const Default = () => {
49+
const [selected, setSelected] = useState(0);
50+
const onSelect = value => {
51+
setSelected(value);
52+
};
53+
return (
54+
<div id='default-selects'>
55+
<Fieldset label='default'>
56+
<SelectBox options={options} value={selected} onSelect={onSelect} />
57+
</Fieldset>
58+
</div>
59+
);
60+
};
61+
62+
Default.story = {
63+
name: 'default'
64+
};
65+
66+
export const Flat = () => (
67+
<Window>
68+
<WindowContent>
69+
<Cutout id='cutout'>
70+
<p>
71+
When you want to use SelectBox on a light background (like scrollable
72+
content), just use the flat variant:
73+
</p>
74+
<Fieldset label='flat' variant='flat'>
75+
<SelectBox />
76+
</Fieldset>
77+
</Cutout>
78+
</WindowContent>
79+
</Window>
80+
);
81+
82+
Flat.story = {
83+
name: 'flat'
84+
};
85+
86+
export const CustomDisplayFormatting = () => <SelectBox />;
87+
88+
CustomDisplayFormatting.story = {
89+
name: 'custom display formatting'
90+
};

src/SelectBox/SelectBox.styles.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import styled from 'styled-components';
2+
3+
import { createScrollbars } from '../common';
4+
import { blockSizes } from '../common/system';
5+
6+
export const StyledOptionsList = styled.ul`
7+
position: relative;
8+
box-sizing: border-box;
9+
background-color: #fff;
10+
font-size: 1rem;
11+
border-style: solid;
12+
border-width: 2px;
13+
border-left-color: ${({ theme }) => theme.borderDark};
14+
border-top-color: ${({ theme }) => theme.borderDark};
15+
border-right-color: ${({ theme }) => theme.borderLightest};
16+
border-bottom-color: ${({ theme }) => theme.borderLightest};
17+
line-height: 1.5;
18+
overflow-y: auto;
19+
${({ variant }) => createScrollbars(variant)}
20+
`;
21+
22+
export const StyledOptionsListItem = styled.li`
23+
margin: 0;
24+
padding: 0;
25+
height: ${blockSizes.md};
26+
`;
27+
28+
export const StyledOptionsListItemInnerButton = styled.button`
29+
outline: 0;
30+
border: none;
31+
width: 100%;
32+
height: 100%;
33+
text-align: left;
34+
font-size: 1rem;
35+
background: ${({ theme, isSelected }) =>
36+
isSelected ? theme.hoverBackground : 'none'};
37+
color: ${({ theme, isSelected }) =>
38+
isSelected ? theme.canvasTextInvert : '#000'};
39+
outline: 0;
40+
`;

0 commit comments

Comments
 (0)