Skip to content

Commit

Permalink
fix dropdown element logic
Browse files Browse the repository at this point in the history
also add icon input to dropdown element
  • Loading branch information
electrovir committed Jul 30, 2022
1 parent 50b6a94 commit 37190fd
Show file tree
Hide file tree
Showing 5 changed files with 235 additions and 48 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@toniq-labs/design-system",
"version": "0.0.17",
"version": "0.0.18",
"private": false,
"description": "Design system elements for Toniq Labs",
"keywords": [
Expand Down
83 changes: 57 additions & 26 deletions src/elements/toniq-dropdown/toniq-dropdown.element.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {assign, css, defineElementEvent, html} from 'element-vir';
import {ChevronDown24Icon} from '../../icons';
import {ChevronDown24Icon, ToniqSvg} from '../../icons';
import {interactionDuration, noUserSelect, toniqFontStyles} from '../../styles';
import {applyBackgroundAndForeground, toniqColors} from '../../styles/colors';
import {createFocusStyles} from '../../styles/focus';
Expand All @@ -8,15 +8,8 @@ import {toniqShadows} from '../../styles/shadows';
import {defineToniqElement} from '../define-toniq-element';
import {ToniqIcon} from '../toniq-icon/toniq-icon.element';

declare global {
interface Window {
dropdownListenerAdded: boolean;
}
}
window.dropdownListenerAdded = window.dropdownListenerAdded || false;

export interface ToniqDropdownOption {
value: unknown;
export interface ToniqDropdownOption<ValueType = unknown> {
value: ValueType;
label: string;
}

Expand All @@ -25,13 +18,17 @@ export const ToniqDropdown = defineToniqElement({
props: {
options: [] as Readonly<ToniqDropdownOption[]>,
selected: undefined as undefined | Readonly<ToniqDropdownOption>,
icon: undefined as ToniqSvg | undefined,
selectedLabelPrefix: '',
dropdownOpen: false,
},
events: {
selectChange: defineElementEvent<ToniqDropdownOption>(),
},
styles: css`
:host {
display: inline-flex;
vertical-align: middle;
${toniqFontStyles.boldParagraphFont};
}
Expand All @@ -50,12 +47,14 @@ export const ToniqDropdown = defineToniqElement({
transition: ${interactionDuration} linear;
}
.dropdown.open ${ToniqIcon} {
.dropdown.open .trigger-icon {
transform: rotate(180deg);
}
.dropdown.open .select-options {
display: grid;
display: flex;
flex-direction: column;
will-change: filter;
}
.dropdown.open,
Expand All @@ -66,7 +65,7 @@ export const ToniqDropdown = defineToniqElement({
.select {
display: flex;
justify-content: space-between;
gap: 8px;
border-radius: 8px;
cursor: pointer;
padding: 12px;
Expand All @@ -87,6 +86,10 @@ export const ToniqDropdown = defineToniqElement({
${toniqShadows.popupShadow};
}
.selected-label-prefix {
${toniqFontStyles.paragraphFont};
}
.select-options .option {
padding: 16px;
cursor: pointer;
Expand All @@ -99,26 +102,31 @@ export const ToniqDropdown = defineToniqElement({
}
.select-options .option:not(.selected):hover {
${applyBackgroundAndForeground(toniqColors.accentSecondary)}
background-color: ${toniqColors.accentTertiary.backgroundColor};
}
.select-options .option:last-child {
border-radius: 0px 0px 8px 8px;
}
.trigger-icon-wrapper {
flex-grow: 1;
display: flex;
justify-content: flex-end;
}
`,
initCallback: ({props, host, setProps}) => {
if (!window.dropdownListenerAdded) {
window.addEventListener('click', clickOutside);
window.dropdownListenerAdded = true;
}

function clickOutside(event: Event) {
const dropDownEl = host.shadowRoot?.querySelector('button.dropdown') as HTMLElement;
const withinBoundaries = event.composedPath().includes(dropDownEl);
const dropdownTrigger = host.shadowRoot?.querySelector(
'button.dropdown',
) as HTMLButtonElement;
const withinBoundaries = event.composedPath().includes(dropdownTrigger);
if (!withinBoundaries && props.dropdownOpen) {
setProps({dropdownOpen: false});
}
}

window.addEventListener('click', clickOutside);
},
renderCallback: ({dispatch, events, props, setProps}) => {
const selectedOption = props.selected ? props.selected : props.options[0];
Expand All @@ -127,19 +135,42 @@ export const ToniqDropdown = defineToniqElement({
setProps({dropdownOpen: !props.dropdownOpen});
}

function onSelectOption(event: Event, item: ToniqDropdownOption) {
setProps({selected: item, dropdownOpen: false});
function onSelectOption(item: ToniqDropdownOption) {
setProps({dropdownOpen: false});
dispatch(new events.selectChange(item));
}

const leadingIconTemplate = props.icon
? html`
<${ToniqIcon}
data-test-id="rendered-input-icon"
${assign(ToniqIcon.props.icon, props.icon)}
></${ToniqIcon}>`
: '';

const prefixTemplate = props.selectedLabelPrefix
? html`
<span class="selected-label-prefix">${props.selectedLabelPrefix}</span>
`
: '';

return html`
<button class="dropdown ${props.dropdownOpen ? 'open' : ''}"
@click=${() => onToggleDropdown()}
role="listbox"
aria-expanded=${props.dropdownOpen}>
<div class="select">
<span class="select-selected">${selectedOption?.label}</span>
<${ToniqIcon} ${assign(ToniqIcon.props.icon, ChevronDown24Icon)}></${ToniqIcon}>
${leadingIconTemplate}
<span class="select-selected">
${prefixTemplate}
${selectedOption?.label}
</span>
<span class="trigger-icon-wrapper">
<${ToniqIcon}
class="trigger-icon"
${assign(ToniqIcon.props.icon, ChevronDown24Icon)}
></${ToniqIcon}>
</span>
</div>
<div class="select-options">
${props.options.map(
Expand All @@ -152,7 +183,7 @@ export const ToniqDropdown = defineToniqElement({
@click=${(event: Event) => {
event.preventDefault();
event.stopPropagation();
onSelectOption(event, item);
onSelectOption(item);
}}
role="option"
>
Expand Down
170 changes: 151 additions & 19 deletions src/elements/toniq-dropdown/toniq-dropdown.story.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import {ArgTypes, ComponentMeta} from '@storybook/react';
import React from 'react';
import {ArrayElement} from 'augment-vir';
import {css} from 'element-vir';
import React, {useReducer} from 'react';
import {ArrowsSort24Icon} from '../../icons';
import {handleEventAsAction} from '../../storybook-helpers/actions';
import {toniqColorCssVarNames} from '../../styles';
import {ToniqDropdown} from '../react-components';
import {ToniqDropdownOption} from './toniq-dropdown.element';
import {ToniqDropdown as NativeToniqDropdown, ToniqDropdownOption} from './toniq-dropdown.element';

const options: ToniqDropdownOption[] = [
const options: ToniqDropdownOption<number | string>[] = [
{
value: 1,
label: 'Option 1',
Expand All @@ -26,11 +30,10 @@ const dropdownStoryControls = (<SpecificArgsGeneric extends ArgTypes>(input: Spe
mapping: options,
control: {
type: 'select',
labels: {
0: options[0]?.label,
1: options[1]?.label,
2: options[2]?.label,
},
labels: options.reduce((accum, option) => {
accum[option.value] = option.label;
return accum;
}, {} as Record<PropertyKey, string>),
},
},
dropdownOpen: {
Expand All @@ -52,15 +55,144 @@ const componentStoryMeta: ComponentMeta<typeof ToniqDropdown> = {

export default componentStoryMeta;

export const mainStory = (controls: Record<keyof typeof dropdownStoryControls, any>) => (
<article>
<h3>Dropdown</h3>
<ToniqDropdown
options={options}
selected={controls.selected}
dropdownOpen={controls.dropdownOpen}
onSelectChange={handleEventAsAction}
/>
</article>
);
const dropdownSelectionStateInit = {
static: {
default: options[0]!,
defaultWithIcon: options[0]!,
withPrefixAndIcon: options[0]!,
withPrefixOnly: options[0]!,
withDifferentBackgroundColor: options[0]!,
},
custom: options[0]!,
} as const;

type DropdownStoryState = typeof dropdownSelectionStateInit;

export const mainStory = (controls: Record<keyof typeof dropdownStoryControls, any>) => {
const [
dropdownSelectionStates,
updateDropdownSelectionStates,
] = useReducer(
(
state: DropdownStoryState,
{
key,
subKey,
option,
}: {
key: keyof DropdownStoryState;
subKey: keyof DropdownStoryState['static'] | undefined;
option: ArrayElement<typeof options>;
},
): DropdownStoryState => {
if (key === 'custom') {
return {...state, custom: option};
} else if (subKey !== undefined) {
return {
...state,
[key]: {
...state[key],
[subKey]: option,
},
};
} else {
throw new Error(`Key was not custom but subKey was not defined.`);
}
},
dropdownSelectionStateInit,
);

return (
<>
<style
dangerouslySetInnerHTML={{
__html: String(css`
${NativeToniqDropdown} {
margin: 8px;
}
`),
}}
/>
<article>
<h3>Static examples</h3>
<ToniqDropdown
options={options}
selected={dropdownSelectionStates.static.default}
onSelectChange={(event: CustomEvent<ArrayElement<typeof options>>) => {
updateDropdownSelectionStates({
key: 'static',
subKey: 'default',
option: event.detail,
});
handleEventAsAction(event);
}}
/>
<ToniqDropdown
options={options}
icon={ArrowsSort24Icon}
selected={dropdownSelectionStates.static.defaultWithIcon}
onSelectChange={(event: CustomEvent<ArrayElement<typeof options>>) => {
updateDropdownSelectionStates({
key: 'static',
subKey: 'defaultWithIcon',
option: event.detail,
});
handleEventAsAction(event);
}}
/>
<ToniqDropdown
options={options}
icon={ArrowsSort24Icon}
selectedLabelPrefix={'Sort By: '}
selected={dropdownSelectionStates.static.withPrefixAndIcon}
onSelectChange={(event: CustomEvent<ArrayElement<typeof options>>) => {
updateDropdownSelectionStates({
key: 'static',
subKey: 'withPrefixAndIcon',
option: event.detail,
});
handleEventAsAction(event);
}}
/>
<ToniqDropdown
options={options}
selectedLabelPrefix={'Sort By: '}
selected={dropdownSelectionStates.static.withPrefixOnly}
onSelectChange={(event: CustomEvent<ArrayElement<typeof options>>) => {
updateDropdownSelectionStates({
key: 'static',
subKey: 'withPrefixOnly',
option: event.detail,
});
handleEventAsAction(event);
}}
/>
<ToniqDropdown
style={{
[String(toniqColorCssVarNames.accentSecondary.backgroundColor)]:
'transparent',
}}
options={options}
icon={ArrowsSort24Icon}
selected={dropdownSelectionStates.static.withDifferentBackgroundColor}
onSelectChange={(event: CustomEvent<ArrayElement<typeof options>>) => {
updateDropdownSelectionStates({
key: 'static',
subKey: 'withDifferentBackgroundColor',
option: event.detail,
});
handleEventAsAction(event);
}}
/>
<h3>Customized example</h3>
<ToniqDropdown
options={options}
selected={controls.selected}
dropdownOpen={controls.dropdownOpen}
onSelectChange={handleEventAsAction}
/>
</article>
</>
);
};
mainStory.storyName = 'Toniq Dropdown';
Loading

0 comments on commit 37190fd

Please sign in to comment.