Skip to content

Commit

Permalink
feat(react): allow to select & remove value items
Browse files Browse the repository at this point in the history
  • Loading branch information
dackmin committed Sep 28, 2022
1 parent a97b771 commit fb59d2b
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 7 deletions.
80 changes: 77 additions & 3 deletions packages/react/lib/SelectField/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const SelectField = forwardRef(({
onBlur,
onFocus,
onKeyPress,
onKeyUp,
onValidate = (val, { required, multiple }) => (
(multiple && Array.isArray(val) && val.length > 0) ||
(!multiple && !!val) ||
Expand All @@ -68,6 +69,7 @@ const SelectField = forwardRef(({
search: '',
searching: false,
searchResults: null,
selectedItem: -1,
});

useImperativeHandle(ref, () => ({
Expand All @@ -78,6 +80,8 @@ const SelectField = forwardRef(({
dirty: state.dirty,
focused: state.focused,
opened: state.opened,
focus,
blur,
reset,
isJunipero: true,
}));
Expand Down Expand Up @@ -112,6 +116,7 @@ const SelectField = forwardRef(({
value: state.value,
valid: state.valid,
dirty: true,
selectedItem: -1,
...resetSearch && { search: '', searchResults: null },
});
onChange?.({ value: parseValue(state.value), valid: state.valid });
Expand Down Expand Up @@ -207,13 +212,77 @@ const SelectField = forwardRef(({
};

const onKeyPress_ = e => {
if (e.key === 'Enter' && allowArbitraryItems) {
onSelectOption(state.search, { resetSearch: true });
if (disabled) {
return;
}

switch (e.key) {
case 'Enter':
if (allowArbitraryItems) {
onSelectOption(state.search, { resetSearch: true });
}

break;
}

onKeyPress?.(e);
};

const onKeyUp_ = e => {
if (disabled) {
return;
}

switch (e.key) {
case 'Backspace':
if (state.selectedItem >= 0) {
onRemoveOption(state.value[state.selectedItem]);
} else {
dispatch({ selectedItem: state.value.length - 1 });
}

break;

case 'ArrowLeft':
if (state.selectedItem > 0) {
dispatch({ selectedItem: state.selectedItem - 1 });
} else if (state.selectedItem === -1 && !state.search) {
dispatch({ selectedItem: state.value.length - 1 });
}

break;

case 'ArrowRight':
if (state.selectedItem < state.value.length - 1) {
dispatch({ selectedItem: state.selectedItem + 1 });
} else if (state.selectedItem !== -1) {
dispatch({ selectedItem: -1 });
}

break;
}

onKeyUp?.(e);
};

const focus = () => {
if (multiple) {
searchInputRef.current?.focus();
} else {
dispatch({ focused: false });
dropdownRef.current?.open();
}
};

const blur = () => {
if (multiple) {
searchInputRef.current?.blur();
} else {
dispatch({ focused: false });
dropdownRef.current?.close();
}
};

const reset = () => {
dispatch({
value: value ?? '',
Expand Down Expand Up @@ -320,7 +389,10 @@ const SelectField = forwardRef(({
{ hasTags() ? state.value.map((o, i) => (
<Tag
key={i}
className="info"
className={classNames(
'info',
{ selected: i === state.selectedItem }
)}
onDelete={onRemoveOption.bind(null, o)}
>
{ parseTitle(o) }
Expand All @@ -337,6 +409,7 @@ const SelectField = forwardRef(({
onFocus={onFocus_}
onBlur={onBlur_}
onKeyPress={onKeyPress_}
onKeyUp={onKeyUp_}
/>
) }
<div className="icons">
Expand Down Expand Up @@ -384,6 +457,7 @@ SelectField.propTypes = {
onChange: PropTypes.func,
onFocus: PropTypes.func,
onKeyPress: PropTypes.func,
onKeyUp: PropTypes.func,
onSearch: PropTypes.func,
onValidate: PropTypes.func,
parseItem: PropTypes.func,
Expand Down
21 changes: 20 additions & 1 deletion packages/react/lib/SelectField/index.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { render } from '@testing-library/react';
import { createRef } from 'react';
import { render, act, fireEvent } from '@testing-library/react';

import SelectField from './index';

Expand Down Expand Up @@ -28,4 +29,22 @@ describe('<SelectField />', () => {
expect(container).toMatchSnapshot();
unmount();
});

it('should be invalid if validation fails', async () => {
const ref = createRef();
const { unmount, container, getByText } = render(
<SelectField
ref={ref}
placeholder="Name"
options={['Item 1', 'Item 2', 'Item 3']}
onValidate={() => false}
autoFocus
/>
);

fireEvent.click(getByText('Item 1'));
await act(async () => { ref.current.blur(); });
expect(container).toMatchSnapshot();
unmount();
});
});
48 changes: 48 additions & 0 deletions packages/react/lib/SelectField/index.test.js.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,53 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`<SelectField /> should be invalid if validation fails 1`] = `
<div>
<div
class="junipero dropdown select-field dirty invalid"
>
<div
class="field dropdown-toggle"
>
<input
readonly=""
type="text"
value="Item 1"
/>
<div
class="icons"
>
<svg
class="junipero icon remove"
height="10"
viewBox="0 0 9 10"
width="10"
>
<path
d="M8 1.5L1 8.5"
/>
<path
d="M1 1.5L8 8.5"
/>
</svg>
<svg
class="junipero icon arrows"
height="13"
viewBox="0 0 8 13"
width="8"
>
<path
d="M1 4.5L4 1.5L7 4.5"
/>
<path
d="M1 8.5L4 11.5L7 8.5"
/>
</svg>
</div>
</div>
</div>
</div>
`;

exports[`<SelectField /> should render 1`] = `
<div>
<div
Expand Down
6 changes: 3 additions & 3 deletions packages/theme/lib/SelectField.sass
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@
opacity: 1
user-select: none

.value
padding: 10px
min-height: 36px
.tag.selected
--background-color: var(--main-color)
--text-color: var(--junipero-seashell)

.icons
position: absolute
Expand Down

0 comments on commit fb59d2b

Please sign in to comment.