Skip to content

Commit

Permalink
Merge pull request #134 from Neovici/feat/multi-chips
Browse files Browse the repository at this point in the history
feat(autocomplete): multiple chips
  • Loading branch information
megheaiulian authored Apr 27, 2023
2 parents 34b3965 + 8acb31e commit 0e0dc9d
Show file tree
Hide file tree
Showing 13 changed files with 505 additions and 238 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
coverage/
dist/
__snapshots__/
30 changes: 15 additions & 15 deletions package-lock.json

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

18 changes: 13 additions & 5 deletions src/autocomplete/autocomplete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useAutocomplete, Props as Base, RProps } from './use-autocomplete';
import { listbox } from '../listbox';
import style from './styles.css';
import { selection } from './selection';
import { useOverflow } from './use-overflow';

export interface Props<I> extends Base<I> {
invalid?: boolean;
Expand All @@ -22,7 +23,10 @@ export interface Props<I> extends Base<I> {
itemLimit?: number;
}

type AProps<I> = Omit<Props<I>, keyof RProps<I>> & RProps<I>;
type AProps<I> = Omit<Props<I>, keyof RProps<I>> &
RProps<I> & {
onInputRef?: (el?: Element) => void;
};

const blank = () => nothing;

Expand Down Expand Up @@ -81,7 +85,7 @@ const autocomplete = <I>(props: AProps<I>) => {
.label=${label}
.placeholder=${isSingle ? undefined : placeholder}
?no-label-float=${noLabelFloat}
?always-float-label=${isSingle || alwaysFloatLabel}
?always-float-label=${value?.length > 0 || alwaysFloatLabel}
?readonly=${isSingle}
?disabled=${disabled}
?invalid=${until(
Expand All @@ -105,6 +109,7 @@ const autocomplete = <I>(props: AProps<I>) => {
@click=${onClick}
autocomplete="off"
exportparts=${inputParts}
?data-one=${isOne}
>
<slot name="prefix" slot="prefix"></slot>
<slot name="suffix" slot="suffix"></slot>
Expand All @@ -123,11 +128,14 @@ const autocomplete = <I>(props: AProps<I>) => {
${suggestions}`;
},
Autocomplete = <I>(props: Props<I>) =>
autocomplete({
Autocomplete = <I>(props: Props<I>) => {
const thru = {
...props,
...useAutocomplete(props),
}),
};
useOverflow(thru);
return autocomplete(thru);
},
observedAttributes = [
'disabled',
'invalid',
Expand Down
47 changes: 47 additions & 0 deletions src/autocomplete/chip.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { tagged as css } from '@neovici/cosmoz-utils';

export default css`
:host {
border-radius: var(--cosmoz-autocomplete-chip-border-radius, 500px);
background: var(--cosmoz-autocomplete-chip-bg-color, #cbcfdb);
margin: 0px 0 2px 0;
display: flex;
flex-direction: row;
align-items: center;
flex: 0.0001 1 fit-content;
max-width: 18ch;
min-width: 40px;
padding: 0 4px 0 8px;
gap: 4px;
}
.content {
color: var(--cosmoz-autocomplete-chip-color, #424242);
font-size: var(--cosmoz-autocomplete-chip-text-font-size, 12px);
line-height: var(--cosmoz-autocomplete-chip-text-line-height, 22px);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: auto;
min-width: 16px;
}
.clear {
background-color: var(--cosmoz-autocomplete-chip-clear-bg-color, #81899b);
border-radius: 50%;
cursor: pointer;
width: 16px;
height: 16px;
stroke: var(
--cosmoz-autocomplete-chip-clear-stroke,
var(--cosmoz-autocomplete-chip-bg-color, #cbcfdb)
);
display: block;
flex: none;
}
.clear:hover {
filter: brightness(90%);
}
.clear svg {
display: block;
transform: translate(3.5px, 3.5px);
}
`;
75 changes: 75 additions & 0 deletions src/autocomplete/chip.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { html, component } from 'haunted';
import { when } from 'lit-html/directives/when.js';
import { ifDefined } from 'lit-html/directives/if-defined.js';
import styles from './chip.css';

const clear = html`
<svg
width="9"
height="8"
viewBox="0 0 9 8"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<line
x1="7.53033"
y1="0.994808"
x2="1.16637"
y2="7.35877"
stroke-width="1.5"
/>
<line
x1="7.46967"
y1="7.35882"
x2="1.10571"
y2="0.99486"
stroke-width="1.5"
/>
</svg>
`;

interface Props {
onClear?: () => void;
disabled?: boolean;
}
export const Chip = ({ onClear, disabled }: Props) => html`
<style>
${styles}
</style>
<div class="content" part="content chip-text"><slot></slot></div>
${when(
!disabled,
() =>
html` <span class="clear" part="clear chip-clear" @click=${onClear}>
${clear}
</span>`
)}
`;

customElements.define(
'cosmoz-autocomplete-chip',
component<Props>(Chip, { observedAttributes: ['disabled'] })
);

interface ChipProps extends Props {
slot?: string;
className?: string;
content: unknown;
}
export const chip = ({
content,
onClear,
disabled,
className = 'chip',
slot,
}: ChipProps) =>
html`<cosmoz-autocomplete-chip
class=${ifDefined(className)}
slot=${ifDefined(slot)}
part="chip"
exportparts="chip-text, chip-clear"
?disabled=${disabled}
.onClear=${onClear}
title=${ifDefined(typeof content === 'string' ? content : undefined)}
>${content}</cosmoz-autocomplete-chip
>`;
107 changes: 19 additions & 88 deletions src/autocomplete/selection.ts
Original file line number Diff line number Diff line change
@@ -1,79 +1,9 @@
import { html } from 'lit-html'; // eslint-disable-line object-curly-newline
import { when } from 'lit-html/directives/when.js';
import { html } from 'lit-html';
import { chip } from './chip';
import type { RProps } from './use-autocomplete';

type Deselect<I> = RProps<I>['onDeselect'];

const clear = html`
<svg
width="9"
height="8"
viewBox="0 0 9 8"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<line
x1="7.53033"
y1="0.994808"
x2="1.16637"
y2="7.35877"
stroke-width="1.5"
/>
<line
x1="7.46967"
y1="7.35882"
x2="1.10571"
y2="0.99486"
stroke-width="1.5"
/>
</svg>
`;

const chip = ({
text,
onClear,
disabled,
isOne,
}: {
text: string;
onClear: () => void;
disabled?: boolean;
isOne?: boolean;
}) => html`
<div class="chip" part="chip" slot="suffix" title=${text} ?data-one=${isOne}>
<span class="chip-text" part="chip-text">${text}</span>
${when(
!disabled,
() =>
html` <span class="chip-clear" part="chip-clear" @click=${onClear}>
${clear}
</span>`
)}
</div>
`;

const badge = <I>({
value,
onDeselect,
}: {
value: I[];
onDeselect: Deselect<I>;
}) => {
const len = value?.length;
return when(
len > 0,
() => html`<div class="badge" slot="suffix" part="badge">
${len}
<span
class="badge-clear"
part="chip-clear"
@click=${() => onDeselect(value)}
>${clear}</span
>
</div>`
);
};

interface Props<I> {
value: I[];
isOne: boolean;
Expand All @@ -83,22 +13,23 @@ interface Props<I> {
}

export const selection = <I>({
value,
isOne,
value: values,
onDeselect,
textual,
disabled,
}: Props<I>) => {
if (isOne || value.length === 1) {
const val = value[0];
return when(val, () =>
chip({
text: textual(val),
onClear: () => onDeselect(val),
disabled,
isOne,
})
);
}
return badge({ value, onDeselect });
};
}: Props<I>) => [
...values.map((value) =>
chip({
content: textual(value),
onClear: () => onDeselect(value),
disabled,
slot: 'control',
})
),
chip({
content: html`<span></span>`,
className: 'badge',
disabled: true,
slot: 'control',
}),
];
Loading

0 comments on commit 0e0dc9d

Please sign in to comment.