Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/person/select/ux #1103

Merged
merged 9 commits into from
Nov 27, 2023
6 changes: 6 additions & 0 deletions .changeset/few-pugs-grow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@equinor/fusion-wc-person': minor
---

- Adding ui with selected person as person-list-items
- Adding variable to person-list-item for setting background color
2 changes: 2 additions & 0 deletions packages/person/src/components/list-item/element.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { styles as theme } from '@equinor/fusion-web-theme';
const style: CSSResult = css`
:host {
--fwc-avatar-size: 2.5rem;
--fwc-person-list-item-background: initial;
}
:host([size='small']) {
--fwc-avatar-size: 2rem;
Expand All @@ -15,6 +16,7 @@ const style: CSSResult = css`
display: flex;
justify-content: space-between;
column-gap: ${unsafeCSS(theme.spacing.comfortable.medium.getVariable('padding'))};
background-color: var(--fwc-person-list-item-background);
border: 1px solid;
border-color: ${unsafeCSS(theme.colors.ui.background__medium.getVariable('color'))};
padding: calc(
Expand Down
60 changes: 44 additions & 16 deletions packages/person/src/components/select/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ export class PersonSelectEvent extends CustomEvent<PersonSelectEventDetail> {
export class PersonSelectController implements ReactiveController {
protected timer?: ReturnType<typeof setTimeout>;
protected _isOpen = false;
public _listItems: Array<string> = [];
public selectedItems: Set<string> = new Set();

public listItems: Array<PersonInfo> = [];
public selectedIds: Set<string> = new Set();

#externaCloseHandler?: (e: MouseEvent | KeyboardEvent) => void;
#host: PersonSelectElement;
Expand Down Expand Up @@ -113,21 +114,18 @@ export class PersonSelectController implements ReactiveController {
}

if (this.#host.multiple) {
if (this.selectedItems.has(azureId)) {
this.selectedItems.delete(azureId);
if (this.selectedIds.has(azureId)) {
this.selectedIds.delete(azureId);
} else {
this.selectedItems.add(azureId);
this.#host.value = dataSource?.name ?? '';
this.selectedIds.add(azureId);
}
} else {
this.isOpen = false;
if (this.selectedItems.has(azureId)) {
this.#host.value = '';
this.selectedItems.clear();
if (this.selectedIds.has(azureId)) {
this.selectedIds.clear();
} else {
this.selectedItems.clear();
this.selectedItems.add(azureId);
this.#host.value = dataSource?.name ?? '';
this.selectedIds.clear();
this.selectedIds.add(azureId);
}
}

Expand All @@ -145,6 +143,28 @@ export class PersonSelectController implements ReactiveController {
this.#host.requestUpdate();
}

public deSelectPerson(person: PersonInfo): boolean {
if (!person?.azureId || !this.selectedIds.has(person.azureId)) {
return false;
}

this.selectedIds.delete(person.azureId);
this.#host.textInputElement.focus();

/* Dispatch custom select event with our details */
this.#host.dispatchEvent(
new PersonSelectEvent({
detail: {
selected: person,
},
bubbles: true,
}),
);

this.#host.requestUpdate();
return true;
}

/**
* Fires on click on close icon in textinput
* Calls closeHandler callback set in resolver
Expand All @@ -171,8 +191,8 @@ export class PersonSelectController implements ReactiveController {
}

this.#host.value = '';
this.selectedItems.clear();
// this.#search = '';
this.selectedIds.clear();

this.#host.search = '';

/* also runs task */
Expand All @@ -183,6 +203,16 @@ export class PersonSelectController implements ReactiveController {
this.#externaCloseHandler(e);
}

if (this.selectedIds.size) {
// TODO add support for multiple
this.selectedIds.forEach((azureId) => {
const deSelectedPerson = this.listItems.find((p) => p.azureId === azureId);
if (deSelectedPerson) {
this.deSelectPerson(deSelectedPerson);
}
});
}

/* fire event for sdd closed */
const ddClosedEvent = new CustomEvent<{ date: number }>('dropdownClosed', {
detail: {
Expand All @@ -196,8 +226,6 @@ export class PersonSelectController implements ReactiveController {
/* Settter: Open/Closed state for host */
public set isOpen(state: boolean) {
this._isOpen = state;
// toogle close icon
this.#host.trailingIcon = state ? 'close' : '';

/* Close on escape key */
if (this._isOpen) {
Expand Down
33 changes: 33 additions & 0 deletions packages/person/src/components/select/element.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { css, type CSSResult } from 'lit';
import { sddStyles } from '@equinor/fusion-wc-searchable-dropdown';

// TODO - maybe this styling should be changed in parent!
export const styles: CSSResult[] = [
...sddStyles,
css`
fwc-list {
--fwc-list-side-padding: 0.5rem;
}
fwc-list-item {
--fwc-list-item-vertical-padding: 0;
}
fwc-person-list-item {
--fwc-person-list-item-background: #ffffff;
}
#selected-persons {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: row;
flex-gap: 0;
position: absolute;
top: 1px;
left: 0;
width: 100%;
}
#selected-persons li {
flex: 1 0 auto;
}
`,
];
85 changes: 58 additions & 27 deletions packages/person/src/components/select/element.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,32 @@
import { html, LitElement, type HTMLTemplateResult, type CSSResult, css } from 'lit';
import { html, LitElement, type HTMLTemplateResult, type CSSResult } from 'lit';
import { property, state } from 'lit/decorators.js';
import { query } from 'lit/decorators/query.js';
import { queryAll } from 'lit/decorators/query-all.js';
import { ifDefined } from 'lit/directives/if-defined.js';
import { classMap } from 'lit/directives/class-map.js';
import { repeat } from 'lit/directives/repeat.js';
import { cache } from 'lit/directives/cache.js';

import { PersonSelectController } from './controller';
import { styles as psStyles } from './element.css';
import { PersonSearchTask, PersonSearchControllerHost } from '../../tasks/person-search-task';

import { SearchableDropdownControllerHost, sddStyles } from '@equinor/fusion-wc-searchable-dropdown';
import { SearchableDropdownControllerHost } from '@equinor/fusion-wc-searchable-dropdown';

import type { PersonInfo } from '../../types';

import AvatarElement from '@equinor/fusion-wc-avatar';
import IconElement from '@equinor/fusion-wc-icon';
import ListElement, { ListItemElement } from '@equinor/fusion-wc-list';
import TextInputElement from '@equinor/fusion-wc-textinput';

import { PersonListItemElement } from '../list-item';
import { IconButtonElement } from '@equinor/fusion-wc-button';
AvatarElement;
IconElement;
ListElement;
TextInputElement;
PersonListItemElement;
IconButtonElement;

// TODO !!!! clean up when extending fwc-searchable-dropdown

Expand Down Expand Up @@ -58,18 +63,7 @@ export class PersonSelectElement
implements SearchableDropdownControllerHost, PersonSearchControllerHost
{
/* style object css */
// TODO - maybe this styling should be changed in parent!
static styles: CSSResult[] = [
...sddStyles,
css`
fwc-list {
--fwc-list-side-padding: 0.5rem;
}
fwc-list-item {
--fwc-list-item-vertical-padding: 0;
}
`,
];
static styles: CSSResult[] = psStyles;

/**
* Label passed to the fwc-text-input component
Expand Down Expand Up @@ -97,7 +91,7 @@ export class PersonSelectElement

/* The trailing icon to display in fwc-textinput */
@property({ attribute: false, state: true })
trailingIcon = '';
trailingIcon = 'close';

/* The icon string to render in result list items on the meta slot */
@property()
Expand Down Expand Up @@ -148,6 +142,7 @@ export class PersonSelectElement
protected controllers = {
element: new PersonSelectController(this),
};

/**
* Render the menu if state is open
* @returns HTMLTemplateResult
Expand All @@ -174,7 +169,8 @@ export class PersonSelectElement
`;
}

this.controllers.element._listItems = result.map((item) => item.azureId);
// apend items to
this.controllers.element.listItems = result.map((item) => item);

return html`
${repeat(
Expand All @@ -184,7 +180,7 @@ export class PersonSelectElement
return html`
<fwc-list-item
graphic="avatar"
.activated=${this.controllers.element.selectedItems.has(item.azureId)}
.activated=${this.controllers.element.selectedIds.has(item.azureId)}
.dataSource=${item}
>
<fwc-person-avatar
Expand All @@ -194,7 +190,6 @@ export class PersonSelectElement
slot="graphic"
trigger="none"
></fwc-person-avatar>

<span class="item-text">
${item.name && html`<span class="item-title">${item.name}</span>`}
${item.mail && html`<span class="item-subtitle" slot="secondary">${item.mail}</span>`}
Expand All @@ -216,14 +211,47 @@ export class PersonSelectElement
</fwc-list>`;
}

protected selectedPersonsTemplate(): HTMLTemplateResult {
const { selectedIds } = this.controllers.element;
/* Empty template when no person is selected */
if (selectedIds.size < 1 || this.controllers.element.isOpen) {
return html``;
}

// convert selected azureId to PeronInfo for returning to PersonSelectEvent
const people = Array.from(selectedIds).map((sel) => ({
azureId: sel,
}));

/* show all selected persons */
return html`${cache(
html`<ul id="selected-persons">
${repeat(
people,
(item) => item.azureId,
(item) => {
return html`<li>
<fwc-person-list-item
size="small"
azureid="${item.azureId}"
@click=${() => (this.controllers.element.isOpen = true)}
></fwc-person-list-item>
</li>`;
},
)}
</ul>`,
)}`;
}

/**
* The main render function
* The main render functions
* @returns HTMLTemplateResult
*/
protected render(): HTMLTemplateResult {
const dense = ['page-dense', 'header', 'header-filled'].indexOf(this.variant) > -1 ? true : undefined;
const variant = ['header', 'page-outlined'].indexOf(this.variant) > -1 ? 'outlined' : 'filled';
const disabled = this.disabled ? true : undefined;

const cssClasses = {
'fwc-sdd': true,
'list-open': this.controllers.element.isOpen,
Expand All @@ -248,15 +276,18 @@ export class PersonSelectElement
@focus=${() => (this.controllers.element.isOpen = true)}
@keyup=${this.controllers.element.handleKeyup}
></fwc-textinput>
${this.selectedPersonsTemplate()}
<slot name="trailing">
<span slot="trailing">
<fwc-icon
tabindex=${this.controllers.element.isOpen ? '0' : '-1'}
class="trailing interactive"
icon=${this.trailingIcon}
@click=${this.controllers.element.closeClick}
@keydown=${this.controllers.element.closeClick}
></fwc-icon>
${this.controllers.element.selectedIds.size || this.controllers.element.isOpen
? html`<fwc-icon
tabindex=${this.controllers.element.isOpen ? '0' : '-1'}
class="trailing interactive"
icon=${this.trailingIcon}
@click=${this.controllers.element.closeClick}
@keydown=${this.controllers.element.closeClick}
></fwc-icon>`
: html``}
</span>
</slot>
</div>
Expand Down
Loading