Skip to content

Commit

Permalink
feat(list-dropdown): added configuration for setting secondary labels…
Browse files Browse the repository at this point in the history
… and for providing additional configuration to leading/trailing icon component elements
  • Loading branch information
DRiFTy17 committed Sep 27, 2023
1 parent 32917ca commit 8a900b6
Show file tree
Hide file tree
Showing 8 changed files with 92 additions and 3 deletions.
4 changes: 4 additions & 0 deletions src/lib/list-dropdown/list-dropdown-constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { IconExternalType, IIconComponent } from '../icon';
import { IPopupPosition, PopupPlacement } from '../popup';

const attributes = {
Expand Down Expand Up @@ -33,15 +34,18 @@ export type ListDropdownIconType = 'font' | 'component';
export interface IBaseListDropdownOption<T = any> {
value: T;
label: string;
secondaryLabel?: string;
disabled?: boolean;
divider?: boolean;
optionClass?: string | string[];
leadingIcon?: string;
leadingIconClass?: string;
leadingIconType?: ListDropdownIconType;
leadingIconComponentProps?: Partial<IIconComponent>;
trailingIcon?: string;
trailingIconClass?: string;
trailingIconType?: ListDropdownIconType;
trailingIconComponentProps?: Partial<IIconComponent>;
leadingBuilder?: () => HTMLElement;
trailingBuilder?: () => HTMLElement;
}
Expand Down
19 changes: 16 additions & 3 deletions src/lib/list-dropdown/list-dropdown-utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { addClass, getEventPath, isDeepEqual, isDefined, isObject } from '@tylertech/forge-core';
import { tylIconCheckBox, tylIconCheckBoxOutlineBlank } from '@tylertech/tyler-icons/standard';
import { ICON_CLASS_NAME } from '../constants';
import { IIconComponent } from '../icon';
import { ILinearProgressComponent, LINEAR_PROGRESS_CONSTANTS } from '../linear-progress';
import { IListComponent, LIST_CONSTANTS } from '../list/list';
import { IPopupComponent, PopupAnimationType, POPUP_CONSTANTS } from '../popup';
Expand Down Expand Up @@ -221,6 +222,15 @@ export function createListItems(config: IListDropdownOpenConfig, listElement: IL
}
}

// Check for secondary (subtitle) text
if (option.secondaryLabel) {
const secondaryLabelElement = document.createElement('span');
secondaryLabelElement.slot = 'subtitle';
secondaryLabelElement.textContent = option.secondaryLabel;
listItemElement.twoLine = true;
listItemElement.appendChild(secondaryLabelElement);
}

// If multiple selections are enabled then we need to create and append a leading checkbox element
if (config.multiple) {
const checkboxElement = createCheckboxElement(isSelected);
Expand All @@ -243,7 +253,7 @@ export function createListItems(config: IListDropdownOpenConfig, listElement: IL
listItemElement.appendChild(element);
}
} else if (option.leadingIcon) {
const leadingIconElement = createIconElement(option.leadingIconType, option.leadingIcon, option.leadingIconClass || config.iconClass);
const leadingIconElement = createIconElement(option.leadingIconType, option.leadingIcon, option.leadingIconClass || config.iconClass, option.leadingIconComponentProps);
leadingIconElement.slot = 'leading';
listItemElement.appendChild(leadingIconElement);
}
Expand All @@ -256,7 +266,7 @@ export function createListItems(config: IListDropdownOpenConfig, listElement: IL
listItemElement.appendChild(element);
}
} else if (option.trailingIcon) {
const trailingIconElement = createIconElement(option.trailingIconType, option.trailingIcon, option.trailingIconClass || config.iconClass);
const trailingIconElement = createIconElement(option.trailingIconType, option.trailingIcon, option.trailingIconClass || config.iconClass, option.trailingIconComponentProps);
trailingIconElement.slot = 'trailing';
listItemElement.appendChild(trailingIconElement);
}
Expand Down Expand Up @@ -318,14 +328,17 @@ function createDivider(): HTMLElement {
return divider;
}

function createIconElement(type: ListDropdownIconType = 'font', iconName: string, iconClass?: string): HTMLElement {
function createIconElement(type: ListDropdownIconType = 'font', iconName: string, iconClass?: string, componentProps?: Partial<IIconComponent>): HTMLElement {
if (type === 'component') {
const icon = document.createElement('forge-icon');
if (iconClass) {
icon.classList.add(iconClass);
}
icon.setAttribute('aria-hidden', 'true');
icon.name = iconName;
if (componentProps) {
Object.assign(icon, componentProps);
}
return icon;
}

Expand Down
3 changes: 3 additions & 0 deletions src/lib/select/core/base-select-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,16 +86,19 @@ export abstract class BaseSelectAdapter extends BaseAdapter<IBaseSelectComponent
return {
// eslint-disable-next-line @typescript-eslint/no-extra-parens
label: o.hasAttribute(OPTION_CONSTANTS.attributes.LABEL) ? o.getAttribute(OPTION_CONSTANTS.attributes.LABEL) as string : (isDefined(o.label) ? o.label : o.innerText),
secondaryLabel: o.hasAttribute(OPTION_CONSTANTS.attributes.SECONDARY_LABEL) ? o.getAttribute(OPTION_CONSTANTS.attributes.SECONDARY_LABEL) as string : isDefined(o.secondaryLabel) ? o.secondaryLabel : undefined,
value: o.hasAttribute(OPTION_CONSTANTS.attributes.VALUE) ? o.getAttribute(OPTION_CONSTANTS.attributes.VALUE) : o.value,
disabled: o.hasAttribute(OPTION_CONSTANTS.attributes.DISABLED),
divider: o.hasAttribute(OPTION_CONSTANTS.attributes.DIVIDER),
optionClass,
leadingIcon: o.hasAttribute(OPTION_CONSTANTS.attributes.LEADING_ICON) ? o.getAttribute(OPTION_CONSTANTS.attributes.LEADING_ICON) as string : o.leadingIcon,
leadingIconClass: o.hasAttribute(OPTION_CONSTANTS.attributes.LEADING_ICON_CLASS) ? o.getAttribute(OPTION_CONSTANTS.attributes.LEADING_ICON_CLASS) as string : o.leadingIconClass,
leadingIconType: o.hasAttribute(OPTION_CONSTANTS.attributes.LEADING_ICON_TYPE) ? o.getAttribute(OPTION_CONSTANTS.attributes.LEADING_ICON_TYPE) as ListDropdownIconType : o.leadingIconType,
leadingIconComponentProps: o.leadingIconComponentProps,
trailingIcon: o.hasAttribute(OPTION_CONSTANTS.attributes.TRAILING_ICON) ? o.getAttribute(OPTION_CONSTANTS.attributes.TRAILING_ICON) as string : o.trailingIcon,
trailingIconClass: o.hasAttribute(OPTION_CONSTANTS.attributes.TRAILING_ICON_CLASS) ? o.getAttribute(OPTION_CONSTANTS.attributes.TRAILING_ICON_CLASS) as string : o.trailingIconClass,
trailingIconType: o.hasAttribute(OPTION_CONSTANTS.attributes.TRAILING_ICON_TYPE) ? o.getAttribute(OPTION_CONSTANTS.attributes.TRAILING_ICON_TYPE) as ListDropdownIconType : o.trailingIconType,
trailingIconComponentProps: o.trailingIconComponentProps,
leadingBuilder: o.leadingBuilder,
trailingBuilder: o.trailingBuilder
};
Expand Down
1 change: 1 addition & 0 deletions src/lib/select/option/option-constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const attributes = {
DISABLED: 'disabled',
DIVIDER: 'divider',
LABEL: 'label',
SECONDARY_LABEL: 'secondary-label',
LEADING_ICON_CLASS: 'leading-icon-class',
LEADING_ICON_TYPE: 'leading-icon-type',
LEADING_ICON: 'leading-icon',
Expand Down
35 changes: 35 additions & 0 deletions src/lib/select/option/option-foundation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ICustomElementFoundation } from '@tylertech/forge-core';
import { IIconComponent } from '../../icon';
import { IBaseListDropdownOption, ListDropdownIconType } from '../../list-dropdown/list-dropdown-constants';
import { IOptionAdapter } from './option-adapter';
import { OPTION_CONSTANTS } from './option-constants';
Expand All @@ -9,15 +10,18 @@ export interface IOptionFoundation extends ICustomElementFoundation, Required<IB
export class OptionFoundation implements IOptionFoundation {
private _value: any;
private _label: string;
private _secondaryLabel: string;
private _disabled = false;
private _divider = false;
private _optionClass: string[] = [];
private _leadingIcon: string;
private _leadingIconClass: string;
private _leadingIconType: ListDropdownIconType;
private _leadingIconComponentProps: Partial<IIconComponent>;
private _trailingIcon: string;
private _trailingIconClass: string;
private _trailingIconType: ListDropdownIconType;
private _trailingIconComponentProps: Partial<IIconComponent>;
private _leadingBuilder: () => HTMLElement;
private _trailingBuilder: () => HTMLElement;

Expand Down Expand Up @@ -45,6 +49,17 @@ export class OptionFoundation implements IOptionFoundation {
}
}

/** Gets/sets the secondary label of this option. */
public get secondaryLabel(): string {
return this._secondaryLabel;
}
public set secondaryLabel(value: string) {
if (this._secondaryLabel !== value) {
this._secondaryLabel = value;
this._adapter.toggleHostAttribute(OPTION_CONSTANTS.attributes.SECONDARY_LABEL, !!this._secondaryLabel, this._secondaryLabel);
}
}

/** Gets/sets the disabled status of this option. */
public get disabled(): boolean {
return this._disabled;
Expand Down Expand Up @@ -119,6 +134,16 @@ export class OptionFoundation implements IOptionFoundation {
}
}

/** Gets/sets the props on the leading icon component. */
public get leadingIconComponentProps(): Partial<IIconComponent> {
return this._leadingIconComponentProps;
}
public set leadingIconComponentProps(value: Partial<IIconComponent>) {
if (this._leadingIconComponentProps !== value) {
this._leadingIconComponentProps = value;
}
}

/** Gets/sets the trailing icon of this option. */
public get trailingIcon(): string {
return this._trailingIcon;
Expand Down Expand Up @@ -152,6 +177,16 @@ export class OptionFoundation implements IOptionFoundation {
}
}

/** Gets/sets the props on the trailing icon component. */
public get trailingIconComponentProps(): Partial<IIconComponent> {
return this._trailingIconComponentProps;
}
public set trailingIconComponentProps(value: Partial<IIconComponent>) {
if (this._trailingIconComponentProps !== value) {
this._trailingIconComponentProps = value;
}
}

/** Gets/sets the leading builder of this option. */
public get leadingBuilder(): (() => HTMLElement) {
return this._leadingBuilder;
Expand Down
17 changes: 17 additions & 0 deletions src/lib/select/option/option.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { coerceBoolean, CustomElement, FoundationProperty, ICustomElement } from '@tylertech/forge-core';
import { IIconComponent } from '../../icon';
import { BaseComponent } from '../../core/base/base-component';
import { IBaseListDropdownOption, ListDropdownIconType } from '../../list-dropdown/list-dropdown-constants';
import { OptionAdapter } from './option-adapter';
Expand Down Expand Up @@ -26,6 +27,7 @@ export class OptionComponent extends BaseComponent implements IOptionComponent {
return [
OPTION_CONSTANTS.attributes.VALUE,
OPTION_CONSTANTS.attributes.LABEL,
OPTION_CONSTANTS.attributes.SECONDARY_LABEL,
OPTION_CONSTANTS.attributes.DISABLED,
OPTION_CONSTANTS.attributes.DIVIDER,
OPTION_CONSTANTS.attributes.OPTION_CLASS,
Expand Down Expand Up @@ -59,6 +61,9 @@ export class OptionComponent extends BaseComponent implements IOptionComponent {
case OPTION_CONSTANTS.attributes.LABEL:
this.label = newValue;
break;
case OPTION_CONSTANTS.attributes.SECONDARY_LABEL:
this.secondaryLabel = newValue;
break;
case OPTION_CONSTANTS.attributes.DISABLED:
this.disabled = coerceBoolean(newValue);
break;
Expand Down Expand Up @@ -97,6 +102,10 @@ export class OptionComponent extends BaseComponent implements IOptionComponent {
@FoundationProperty()
public declare label: string;

/** Gets/sets the secondary label of this option. */
@FoundationProperty()
public declare secondaryLabel: string;

/** Gets/sets the disabled status of this option. */
@FoundationProperty()
public declare disabled: boolean;
Expand All @@ -121,6 +130,10 @@ export class OptionComponent extends BaseComponent implements IOptionComponent {
@FoundationProperty()
public declare leadingIconType: ListDropdownIconType;

/** Gets/sets properties on leading icon component. */
@FoundationProperty()
public declare leadingIconComponentProps: Partial<IIconComponent>;

/** Gets/sets the trailing icon of this option. */
@FoundationProperty()
public declare trailingIcon: string;
Expand All @@ -133,6 +146,10 @@ export class OptionComponent extends BaseComponent implements IOptionComponent {
@FoundationProperty()
public declare trailingIconType: ListDropdownIconType;

/** Gets/sets properties on trailing icon component. */
@FoundationProperty()
public declare trailingIconComponentProps: Partial<IIconComponent>;

/** Gets/sets the leading builder of this option. */
@FoundationProperty()
public declare leadingBuilder: () => HTMLElement;
Expand Down
3 changes: 3 additions & 0 deletions src/stories/src/components/autocomplete/autocomplete.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -287,15 +287,18 @@ enum AutocompleteMode {
interface IAutocompleteOption<T = any> {
value: T;
label: string;
secondaryLabel?: string;
disabled?: boolean;
divider?: boolean;
optionClass?: string | string[];
leadingIcon?: string;
leadingIconClass?: string;
leadingIconType?: ListDropdownIconType;
leadingIconComponentProps?: Partial<IIconComponent>;
trailingIcon?: string;
trailingIconClass?: string;
trailingIconType?: ListDropdownIconType;
trailingIconComponentProps?: Partial<IIconComponent>;
leadingBuilder?: () => HTMLElement;
trailingBuilder?: () => HTMLElement;
options?: Array<IListDropdownOption | IListDropdownOptionGroup>;
Expand Down
13 changes: 13 additions & 0 deletions src/test/spec/list-dropdown/list-dropdown.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -953,4 +953,17 @@ describe('ListDropdown', function(this: ITestContext) {
expect(attrValue).toBeTruthy();
expect(attrValue).toBe('test-value');
});

it('should display options with secondary label', async function(this: ITestContext) {
const opts: IListDropdownOption[] = [
{ label: 'Label', secondaryLabel: 'Secondary label', value: 'value' },
];
this.context = createListDropdown({ ...DEFAULT_CONFIG, options: opts });
this.context.listDropdown.open();
await timer(POPUP_CONSTANTS.numbers.ANIMATION_DURATION);
await tick();

const listItems = getListItems();
expect(listItems[0].querySelector('span[slot=subtitle]')?.textContent).toBe('Secondary label');
});
});

0 comments on commit 8a900b6

Please sign in to comment.