* feat: create a crayons component called MultiInput that appears under App Components/MultiInput * feat: add the doc describing the components to storybook * feat: create the component ina different file and reference its usage from the stories * feat: add a random class to hook up a stylesheet to the component * lets just copy soem HTML * chore: remove arias that will not be used for the multiinput * feat: update the classnames and remove unneeded html * feat: multi input css updates * feat: add the ability to set the list of emails using state * feat: add the ability to clear the input selection * feat: remove item * feat: handle key events like space, comma and enter in the input * refactor: clean up code a bit * refactor: rename email to item * fix: remove space keydown as its causing weird behaviour * refactor: remove unused css now that we using pills * feat: ensure that we do not add blank pills * feat: add the component to BETA * feat: rename addToList to addItemToList to be more verbose * feat: add the first component prop i.e. placeholder * feat: add a more descriptive text * feat: add a padding to the field * remove unused arial labels, and erword soem descriptions * feat: add imports to scss * Update app/javascript/crayons/MultiInput/MultiInput.jsx Co-authored-by: Suzanne Aitchison <suzanne@forem.com> * Update app/javascript/crayons/MultiInput/MultiInput.jsx Co-authored-by: Suzanne Aitchison <suzanne@forem.com> * Update app/javascript/crayons/MultiInput/__stories__/MultiInput.mdx Co-authored-by: Julianna Tetreault <32834804+juliannatetreault@users.noreply.github.com> Co-authored-by: Suzanne Aitchison <suzanne@forem.com> Co-authored-by: Julianna Tetreault <32834804+juliannatetreault@users.noreply.github.com>
112 lines
3.1 KiB
JavaScript
112 lines
3.1 KiB
JavaScript
import { h, Fragment } from 'preact';
|
|
import { useRef, useState } from 'preact/hooks';
|
|
|
|
const KEYS = {
|
|
ENTER: 'Enter',
|
|
COMMA: ',',
|
|
SPACE: ' ',
|
|
};
|
|
// TODO: think about how this may change based on
|
|
// a different usage. We will most likely want this to be passed in as a prop.
|
|
const ALLOWED_CHARS_REGEX = /([a-zA-Z0-9@.])/;
|
|
|
|
/**
|
|
* Component allowing users to add multiple entries for a given input field that get displayed as destructive pills
|
|
*
|
|
* @param {Object} props
|
|
* @param {string} props.placeholder Input placeholder text
|
|
*/
|
|
|
|
export const MultiInput = ({ placeholder }) => {
|
|
const inputRef = useRef(null);
|
|
const [items, setItems] = useState([]);
|
|
|
|
const handleBlur = ({ target: { value } }) => {
|
|
addItemToList(value);
|
|
clearSelection();
|
|
};
|
|
|
|
const handleKeyDown = (e) => {
|
|
switch (e.key) {
|
|
case KEYS.SPACE:
|
|
case KEYS.ENTER:
|
|
case KEYS.COMMA:
|
|
e.preventDefault();
|
|
addItemToList(e.target.value);
|
|
clearSelection();
|
|
break;
|
|
default:
|
|
if (!ALLOWED_CHARS_REGEX.test(e.key)) {
|
|
e.preventDefault();
|
|
}
|
|
}
|
|
};
|
|
|
|
const handleDestructiveClick = (clickedItem) => {
|
|
const newArr = items.filter((item) => item !== clickedItem);
|
|
setItems(newArr);
|
|
};
|
|
|
|
const addItemToList = (value) => {
|
|
// TODO: we will want to do some validation here based on a prop
|
|
if (value.trim().length > 0) {
|
|
setItems([...items, value]);
|
|
}
|
|
};
|
|
|
|
const clearSelection = () => {
|
|
inputRef.current.value = '';
|
|
};
|
|
|
|
const listItems = items.map((item, index) => (
|
|
<li
|
|
key={index}
|
|
class="c-input--multi__selection-list-item w-max"
|
|
style="order: 1;"
|
|
>
|
|
<div role="group" aria-label="two" class="flex mr-1 mb-1 w-max">
|
|
<button
|
|
class="c-pill c-pill--action-icon c-pill--action-icon--destructive"
|
|
type="button"
|
|
>
|
|
{item}
|
|
<svg
|
|
class="crayons-icon c-pill__action-icon"
|
|
aria-hidden="true"
|
|
focusable="false"
|
|
width="18"
|
|
height="18"
|
|
viewBox="0 0 24 24"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
onClick={() => handleDestructiveClick(item)}
|
|
>
|
|
<path d="m12 10.586 4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414 1.414-4.95-4.95-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636l4.95 4.95z" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</li>
|
|
));
|
|
|
|
return (
|
|
<Fragment>
|
|
<div class="c-input--multi relative">
|
|
<div class="c-input--multi__wrapper-border crayons-textfield flex items-center cursor-text pb-9">
|
|
<ul class="list-none flex flex-wrap w-100">
|
|
{listItems}
|
|
<li class="self-center" style="order: 3;">
|
|
<input
|
|
autocomplete="off"
|
|
class="c-input--multi__input"
|
|
type="text"
|
|
onBlur={handleBlur}
|
|
onKeyDown={handleKeyDown}
|
|
placeholder={placeholder}
|
|
ref={inputRef}
|
|
/>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</Fragment>
|
|
);
|
|
};
|