vira
Version:
A simple and highly versatile design system using element-vir.
180 lines (170 loc) • 6.32 kB
JavaScript
import { check } from '@augment-vir/assert';
import { filterMap } from '@augment-vir/common';
import { classMap, css, defineElementEvent, html, ifDefined, listen, nothing, testId, } from 'element-vir';
import { ChevronUp24Icon } from '../icons/index.js';
import { viraBorders } from '../styles/border.js';
import { viraFormCssVars } from '../styles/form-styles.js';
import { noUserSelect, viraAnimationDurations } from '../styles/index.js';
import { defineViraElement } from './define-vira-element.js';
import { ViraMenuTrigger } from './pop-up/vira-menu-trigger.element.js';
import { HorizontalAnchor } from './pop-up/vira-pop-up-trigger.element.js';
import { ViraIcon } from './vira-icon.element.js';
/**
* Test ids for {@link ViraDropdown}.
*
* @category Internal
*/
export const viraDropdownTestIds = {
trigger: 'dropdown-trigger',
icon: 'dropdown-icon',
prefix: 'dropdown-prefix',
};
/**
* A dropdown element that uses pop-up menus.
*
* @category Dropdown
* @category Elements
* @see https://electrovir.github.io/element-vir/vira/book/elements/dropdown/vira-dropdown
*/
export const ViraDropdown = defineViraElement()({
tagName: 'vira-dropdown',
styles: css `
:host {
display: inline-flex;
vertical-align: middle;
width: 256px;
position: relative;
max-width: 100%;
}
${ViraMenuTrigger} {
width: 100%;
}
.selection-display {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.trigger-icon {
align-self: flex-start;
will-change: transform;
transform: rotate(180deg);
transition: transform
${viraAnimationDurations['vira-interaction-animation-duration'].value} linear;
}
.trigger-icon-wrapper {
flex-grow: 1;
display: flex;
justify-content: flex-end;
}
.open {
& .trigger-icon {
transform: rotate(0);
}
&:not(.open-upwards).dropdown-trigger {
border-bottom-left-radius: 0;
}
&.open-upwards.dropdown-trigger {
border-top-left-radius: 0;
}
}
.dropdown-trigger {
${noUserSelect};
border: 1px solid ${viraFormCssVars['vira-form-border-color'].value};
height: 100%;
width: 100%;
box-sizing: border-box;
display: flex;
gap: 8px;
text-align: left;
align-items: center;
padding: 3px;
padding-left: 10px;
border-radius: ${viraBorders['vira-form-input-radius'].value};
background-color: ${viraFormCssVars['vira-form-background-color'].value};
color: ${viraFormCssVars['vira-form-foreground-color'].value};
}
.using-placeholder {
opacity: 0.4;
}
`,
events: {
selectedChange: defineElementEvent(),
openChange: defineElementEvent(),
},
state() {
return {
/** `undefined` means the pop up is not currently showing. */
showPopUpResult: undefined,
};
},
render({ state, inputs, dispatch, events, updateState }) {
const selectedOptions = filterMap(inputs.selected, (selectedId) => inputs.options.find((option) => option.id === selectedId), check.isTruthy);
const leadingIconTemplate = inputs.icon
? html `
<${ViraIcon.assign({
icon: inputs.icon,
})}
${testId(viraDropdownTestIds.icon)}
></${ViraIcon}>
`
: nothing;
const shouldUsePlaceholder = !selectedOptions.length;
const prefixTemplate = inputs.selectionPrefix && !shouldUsePlaceholder
? html `
<span class="selected-label-prefix" ${testId(viraDropdownTestIds.prefix)}>
${inputs.selectionPrefix}
</span>
`
: nothing;
const selectionDisplay = shouldUsePlaceholder
? inputs.placeholder || ''
: inputs.isMultiSelect && selectedOptions.length > 1
? `${selectedOptions.length} Selected`
: selectedOptions[0]?.label || '';
return html `
<${ViraMenuTrigger.assign({
items: inputs.options,
selected: inputs.selected,
isDisabled: inputs.isDisabled,
isMultiSelect: inputs.isMultiSelect,
z_debug_forceOpenState: inputs.z_debug_forceOpenState,
popUpOffset: {
vertical: -1,
right: 24,
},
horizontalAnchor: inputs.horizontalAnchor || HorizontalAnchor.Both,
})}
${listen(ViraMenuTrigger.events.openChange, (event) => {
updateState({ showPopUpResult: event.detail });
dispatch(new events.openChange(event.detail));
})}
${listen(ViraMenuTrigger.events.itemActivate, (event) => {
dispatch(new events.selectedChange(event.detail));
})}
>
<div
class="dropdown-trigger ${classMap({
open: !!state.showPopUpResult,
'open-upwards': !state.showPopUpResult?.popDown,
})}"
${testId(viraDropdownTestIds.trigger)}
>
${leadingIconTemplate}
<span
class="selection-display ${classMap({
'using-placeholder': shouldUsePlaceholder,
})}"
title=${ifDefined(shouldUsePlaceholder ? undefined : selectionDisplay)}
>
${prefixTemplate} ${selectionDisplay}
</span>
<span class="trigger-icon-wrapper">
<${ViraIcon.assign({ icon: ChevronUp24Icon })}
class="trigger-icon"
></${ViraIcon}>
</span>
</div>
</${ViraMenuTrigger}>
`;
},
});