Skip to content

Commit

Permalink
Merge pull request #21 from rewiringamerica/project-multiselect
Browse files Browse the repository at this point in the history
Add multiselect projects field to state calculator
  • Loading branch information
ayangster authored Oct 2, 2023
2 parents f6a8672 + 0c31cfa commit 6d29646
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 40 deletions.
9 changes: 7 additions & 2 deletions cypress/e2e/state-calculator.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ describe('template spec', () => {
.shadow()
.contains('Your household info');

cy.get('rewiring-america-state-calculator')
.shadow()
.find('sl-select#projects')
.invoke('attr', 'value', 'hvac');

cy.get('rewiring-america-state-calculator')
.shadow()
.find('input#zip')
Expand All @@ -21,7 +26,7 @@ describe('template spec', () => {
cy.get('rewiring-america-state-calculator')
.shadow()
.find('select#utility')
.should("exist");
.should('exist');

cy.get('rewiring-america-state-calculator')
.shadow()
Expand Down Expand Up @@ -49,6 +54,6 @@ describe('template spec', () => {

cy.get('rewiring-america-state-calculator')
.shadow()
.contains("Other incentives available to you");
.contains('Other incentives available to you');
});
});
29 changes: 12 additions & 17 deletions src/calculator-form.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { html, css, nothing } from 'lit';
import { downIcon, questionIcon } from './icons';
import { select, selectStyles, OptionParam } from './select';
import { select, multiselect, selectStyles, OptionParam } from './select';
import { inputStyles } from './styles/input';
import './currency-input';
import '@shoelace-style/shoelace/dist/themes/light.css';
Expand Down Expand Up @@ -75,35 +75,30 @@ const HOUSEHOLD_SIZE_OPTIONS: OptionParam[] = [1, 2, 3, 4, 5, 6, 7, 8].map(
);

export const formTemplate = (
[
project,
zip,
ownerStatus,
householdIncome,
taxFiling,
householdSize,
]: Array<string>,
[zip, ownerStatus, householdIncome, taxFiling, householdSize]: Array<string>,
projects: Array<string>,
showProjectField: boolean,
onSubmit: (e: SubmitEvent) => void,
gridClass: string = 'grid-3-2',
) => {
const projectField = showProjectField
? html`<div>
<label for="project">
Project you're most interested in
<label for="projects">
Projects you're most interested in
<sl-tooltip
content="Select the project you're most interested in."
content="Select the projects you're most interested in."
hoist
>${questionIcon(18, 18)}</sl-tooltip
><br />
${select({
id: 'project',
required: true,
${multiselect({
id: 'projects',
options: Object.entries(PROJECTS)
.map(([value, data]) => ({ value, label: data.label }))
.sort((a, b) => a.label.localeCompare(b.label)),
currentValue: project,
tabIndex: 0,
currentValues: projects,
placeholder: 'None selected',
maxOptionsVisible: 1,
placement: 'top',
})}
</label>
</div> `
Expand Down
3 changes: 1 addition & 2 deletions src/calculator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
} from './calculator-types';
import { CALCULATOR_FOOTER } from './calculator-footer';
import { fetchApi } from './api/fetch';
import { NO_PROJECT } from './projects';

const loadedTemplate = (
results: ICalculatedIncentiveResults,
Expand Down Expand Up @@ -151,13 +150,13 @@ export class RewiringAmericaCalculator extends LitElement {
? nothing
: formTemplate(
[
NO_PROJECT,
this.zip,
this.ownerStatus,
this.householdIncome,
this.taxFiling,
this.householdSize,
],
[],
false,
(event: SubmitEvent) => this.submit(event),
)}
Expand Down
77 changes: 77 additions & 0 deletions src/select.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { html, css } from 'lit';
import { ifDefined } from 'lit/directives/if-defined.js';
import '@shoelace-style/shoelace/dist/themes/light.css';
import '@shoelace-style/shoelace/dist/components/option/option.js';
import '@shoelace-style/shoelace/dist/components/select/select.js';

export interface OptionParam {
label: string;
Expand All @@ -17,9 +20,27 @@ export interface SelectParam {
disabled?: boolean;
}

export interface SLSelectParam {
id: string;
label?: string;
options: OptionParam[];
helpText?: string;
placeholder?: string;
placement?: string;
required?: boolean;
}

export interface MultiSelectParam extends SLSelectParam {
currentValues: string[];
maxOptionsVisible?: number;
}

export const option = ({ label, value }: OptionParam, selected: boolean) =>
html` <option value="${value}" ?selected=${selected}>${label}</option> `;

export const multioption = ({ label, value }: OptionParam) =>
html` <sl-option value="${value}">${label}</sl-option> `;

export const select = ({
id,
required,
Expand Down Expand Up @@ -48,6 +69,38 @@ export const select = ({
`;
};

export const multiselect = ({
id,
label,
currentValues,
options,
helpText,
placeholder,
maxOptionsVisible,
placement,
}: MultiSelectParam) => {
return html`
<div>
<sl-select
id="${id}"
name="${id}"
label="${ifDefined(label)}"
value="${currentValues.join(' ')}"
help-text="${ifDefined(helpText)}"
placeholder="${ifDefined(placeholder)}"
max-options-visible="${ifDefined(maxOptionsVisible)}"
placement="${ifDefined(placement)}"
hoist
multiple
>
<sl-icon slot="expand-icon"></sl-icon>
${options.map(o => multioption(o))}
</sl-select>
<span class="focus"></span>
</div>
`;
};

export const selectStyles = css`
/* // @link https://moderncss.dev/custom-select-styles-with-pure-css/ */
Expand Down Expand Up @@ -174,4 +227,28 @@ export const selectStyles = css`
background-color: #eee;
background-image: linear-gradient(to top, #ddd, #eee 33%);
}
sl-select {
--sl-input-height-medium: 2.8215rem;
--sl-input-font-family: var(--ra-embed-font-family);
--sl-input-focus-ring-color: var(--select-focus);
--sl-input-focus-ring-style: solid;
--sl-focus-ring-width: 1px;
--sl-input-border-width: 1px;
--sl-input-border-color-focus: var(--select-focus);
margin-top: 4px;
}
sl-select::part(expand-icon) {
content: '';
justify-self: end;
width: 0.6em;
height: 0.4em;
background-color: var(--select-arrow);
clip-path: polygon(100% 0%, 0 0%, 50% 100%);
}
`;
47 changes: 40 additions & 7 deletions src/state-calculator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,14 @@ export class RewiringAmericaStateCalculator extends LitElement {
@property({ type: String })
utility: string = '';

@property({ type: Array })
projects: Project[] = [];

@property({ type: String })
selectedProject: Project = 'hvac';
selectedProjectTab: Project | undefined;

@property({ type: String })
selectedOtherTab: Project = 'heat_pump_clothes_dryer';
selectedOtherTab: Project | undefined;

/**
* This is a hack to deal with a quirk of the UI.
Expand Down Expand Up @@ -124,7 +127,7 @@ export class RewiringAmericaStateCalculator extends LitElement {
this.householdIncome = (formData.get('household_income') as string) || '';
this.taxFiling = (formData.get('tax_filing') as FilingStatus) || '';
this.householdSize = (formData.get('household_size') as string) || '';
this.selectedProject = (formData.get('project') as Project) || '';
this.projects = (formData.getAll('projects') as Project[]) || '';

// Zip is the only thing that determines what utilities are available, so
// only fetch utilities if zip has changed since last calculation, or if
Expand All @@ -140,14 +143,41 @@ export class RewiringAmericaStateCalculator extends LitElement {
}
}

override async firstUpdated() {
// Give the browser a chance to paint
await new Promise(r => setTimeout(r, 0));
const select = this.renderRoot.querySelector('sl-select');
const combobox = this.renderRoot
.querySelector('sl-select')
?.renderRoot.querySelector('div.select__combobox');

select?.addEventListener('keydown', event => {
if (event.key === 'Tab' && select.open) {
event.preventDefault();
event.stopPropagation();
select.hide();
select.displayInput.focus({ preventScroll: true });
}
});

combobox?.addEventListener('keydown', event => {
if (event.key === 'Tab' && select?.open) {
event.preventDefault();
event.stopPropagation();
select.hide();
select.displayInput.focus({ preventScroll: true });
}
});
}

isFormComplete() {
return !!(
this.zip &&
this.ownerStatus &&
this.taxFiling &&
this.householdIncome &&
this.householdSize &&
this.selectedProject
this.projects
);
}

Expand Down Expand Up @@ -253,13 +283,13 @@ export class RewiringAmericaStateCalculator extends LitElement {
? nothing
: formTemplate(
[
this.selectedProject,
this.zip,
this.ownerStatus,
this.householdIncome,
this.taxFiling,
this.householdSize,
],
this.projects,
true,
(event: SubmitEvent) => this.submit(event),
'grid-3-2-1',
Expand Down Expand Up @@ -292,9 +322,12 @@ export class RewiringAmericaStateCalculator extends LitElement {
html`<div class="separator"></div>`,
stateIncentivesTemplate(
this._task.value!,
this.selectedProject,
this.projects,
newOtherSelection =>
(this.selectedOtherTab = newOtherSelection),
newSelection => (this.selectedProjectTab = newSelection),
this.selectedOtherTab,
newSelection => (this.selectedOtherTab = newSelection),
this.selectedProjectTab,
),
]
: nothing}
Expand Down
Loading

1 comment on commit 6d29646

@vercel
Copy link

@vercel vercel bot commented on 6d29646 Oct 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.