Skip to content

Commit e22fd21

Browse files
committed
feat: add select and multi-select components
Also add button styles to allow for multi-select list of selected items
1 parent b8ada36 commit e22fd21

File tree

5 files changed

+225
-0
lines changed

5 files changed

+225
-0
lines changed

src/scss/components/_index.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
@forward "jump-menu/jump-menu";
1212
@forward "search-bar/search-bar";
1313
@forward "section/section";
14+
@forward "select/select";
1415
@forward "table/table";
1516
@forward "menu-toggle/menu-toggle";
1617
@forward "message/message";

src/scss/components/button/_button.scss

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,40 @@
5151
padding-top: 0.5rem;
5252
padding-bottom: 0.5rem;
5353
}
54+
55+
&--pill {
56+
color: $color-grey-90;
57+
background-color: $color-teal-20;
58+
border: 1px solid $color-teal-50;
59+
padding-top: 0.5rem;
60+
padding-bottom: 0.5rem;
61+
&:hover {
62+
background-color: white;
63+
}
64+
}
65+
66+
&--remove {
67+
position: relative;
68+
padding-right: 2.1em;
69+
70+
&::after {
71+
position: absolute;
72+
right: 0.7em;
73+
top: 50%;
74+
transform: translateY(-50%);
75+
content: "×";
76+
color: currentColor;
77+
pointer-events: none;
78+
padding-bottom: 0.1rem;
79+
font-size: 1em;
80+
transition: all 0.2s ease-in-out;
81+
}
82+
83+
&:hover {
84+
&::after {
85+
color: $color-red-40;
86+
transition: all 0.2s ease-in-out;
87+
}
88+
}
89+
}
5490
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
@use "../../tokens/color" as *;
2+
@use "../../tokens/font" as *;
3+
4+
.iati-select {
5+
color: currentColor;
6+
7+
&__label {
8+
display: block;
9+
margin: 0 0 1rem 0;
10+
}
11+
12+
&__control-wrapper {
13+
position: relative;
14+
display: inline-block;
15+
16+
&::after {
17+
position: absolute;
18+
right: 0.7em;
19+
top: calc(50% - 0.1875em);
20+
content: "";
21+
width: 0.75em;
22+
height: 0.375em;
23+
background-color: currentColor;
24+
clip-path: polygon(15% 0, 0 0, 50% 100%, 100% 0, 85% 0, 50% 70%);
25+
pointer-events: none;
26+
}
27+
}
28+
29+
&__control {
30+
padding: 0.7em 2.1em 0.7em 0.7em;
31+
font-family: $font-stack-heading;
32+
line-height: 1.1;
33+
width: 100%;
34+
height: 100%;
35+
appearance: none;
36+
background-color: #fff;
37+
border: 1px solid $color-teal-50;
38+
font-family: $font-stack-heading;
39+
color: $color-grey-90;
40+
font-weight: 600;
41+
}
42+
}
43+
44+
.iati-multi-select {
45+
margin: 1rem 0 0 0;
46+
padding: 0;
47+
48+
&__item {
49+
--display: inline-flex; // Used by .display--* utilities
50+
51+
display: var(--display);
52+
align-items: center;
53+
justify-content: center;
54+
gap: 0.25em;
55+
margin: 0;
56+
padding: 0;
57+
}
58+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { LitElement, html } from 'lit';
2+
import { customElement, property, state } from 'lit/decorators.js';
3+
import { map } from 'lit/directives/map.js';
4+
5+
@customElement('multi-select')
6+
export class MultiSelect extends LitElement {
7+
@property({ type: String })
8+
label: string = 'Select options';
9+
10+
@property({ type: Array })
11+
options: string[] = [];
12+
13+
@state()
14+
selectedItems: string[] = [];
15+
16+
createRenderRoot() {
17+
return this;
18+
}
19+
20+
_handleSelect(e: Event) {
21+
const selectEl = e.target as HTMLSelectElement;
22+
const value = selectEl.value;
23+
24+
if (value && !this.selectedItems.includes(value)) {
25+
this.selectedItems = [...this.selectedItems, value];
26+
}
27+
selectEl.value = '';
28+
}
29+
30+
_handleRemove(itemToRemove: string) {
31+
this.selectedItems = this.selectedItems.filter(
32+
(item) => item !== itemToRemove
33+
);
34+
}
35+
36+
render() {
37+
const availableOptions = this.options.filter(
38+
(opt) => !this.selectedItems.includes(opt)
39+
);
40+
41+
return html`
42+
<div class="iati-select">
43+
<label for="iati-multi-select" class="iati-select__label">${this.label}</label>
44+
45+
<div class="iati-select__control-wrapper">
46+
<select
47+
id="iati-multi-select"
48+
class="iati-select__control"
49+
@change=${this._handleSelect}
50+
>
51+
<option value="">---</option>
52+
${map(
53+
availableOptions,
54+
(opt) => html`<option value=${opt}>${opt}</option>`
55+
)}
56+
</select>
57+
</div>
58+
</div>
59+
60+
<ul class="iati-multi-select">
61+
${map(
62+
this.selectedItems,
63+
(item) => html`
64+
<li class="iati-multi-select__item">
65+
<button
66+
class="iati-button iati-button--pill iati-button--remove"
67+
@click=${() => this._handleRemove(item)}
68+
title="Remove ${item}"
69+
>
70+
${item}
71+
</button>
72+
</li>
73+
`
74+
)}
75+
</ul>
76+
`;
77+
}
78+
}
79+
80+
declare global {
81+
interface HTMLElementTagNameMap {
82+
'multi-select': MultiSelect;
83+
}
84+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import type { Meta, StoryObj } from "@storybook/web-components";
2+
import { html } from "lit";
3+
4+
import "./multi-select.ts";
5+
6+
const meta: Meta = {
7+
title: "Components/Select",
8+
argTypes: {
9+
label: { control: 'text' },
10+
options: { control: 'object' },
11+
}
12+
};
13+
14+
export default meta;
15+
type Story = StoryObj;
16+
17+
export const SingleSelect: Story = {
18+
name: "Single Select",
19+
render: () => html`
20+
<div class="iati-select">
21+
<label for="iati-single-select" class="iati-select__label">
22+
Choose your language
23+
</label>
24+
<div class="iati-select__control-wrapper">
25+
<select id="iati-single-select" class="iati-select__control">
26+
<option>English</option>
27+
<option>French</option>
28+
</select>
29+
</div>
30+
</div>
31+
`,
32+
};
33+
34+
export const MultiSelect: Story = {
35+
name: "Multi Select",
36+
args: {
37+
label: 'Choose your language(s)',
38+
options: ['English', 'French']
39+
},
40+
render: (args) => html`
41+
<multi-select
42+
.label=${args.label}
43+
.options=${args.options}
44+
></multi-select>
45+
`,
46+
};

0 commit comments

Comments
 (0)