Skip to content

Commit

Permalink
feat(chips-combobox): add component
Browse files Browse the repository at this point in the history
  • Loading branch information
LuLaValva committed Jan 18, 2024
1 parent 2bbe429 commit fa6f613
Show file tree
Hide file tree
Showing 11 changed files with 343 additions and 22 deletions.
10 changes: 7 additions & 3 deletions src/components/ebay-chip/index.marko
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
import { processHtmlAttributes } from "../../common/html-attributes";
import type { WithNormalizedProps } from "../../global";
import type { AttrBoolean, AttrString } from "marko/tags-html";

static interface ChipInput extends Omit<Marko.Input<"span">, `on${string}`> {
renderBody?: Marko.Body;
"a11y-delete-button"?: string;
disabled?: AttrBoolean;
"a11y-delete-button"?: AttrString;
"on-delete"?: () => void;
}

export interface Input extends WithNormalizedProps<ChipInput> {}

$ const { renderBody, a11yDeleteButton, ...htmlInput } = input;
$ const { renderBody, disabled, a11yDeleteButton, ...htmlInput } = input;

<span class="chip" ...processHtmlAttributes(htmlInput)>
<span class="chip__text" id:scoped="title">
<span class="chip__text" id:scoped="text">
<${renderBody}/>
</span>
<if(a11yDeleteButton)>
<button
type="button"
class="chip__button"
aria-label=a11yDeleteButton
disabled=disabled
aria-describedby:scoped="text"
on-click("emit", "delete")
>
Expand Down
16 changes: 16 additions & 0 deletions src/components/ebay-chips-combobox/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<h1 style="display: flex; justify-content: space-between; align-items: center;">
<span>
ebay-combobox
</span>
<span style="font-weight: normal; font-size: medium; margin-bottom: -15px;">
DS v2.0.0
</span>
</h1>

The `<ebay-combobox>` is a combination of a text `<input>`, and a listbox (`aria-role="listbox"`). It supports both written text by the user, as well as text selected from the listbox options.

## Examples and Documentation

- [Storybook](https://ebay.github.io/ebayui-core/?path=/story/form-input-ebay-combobox)
- [Storybook Docs](https://ebay.github.io/ebayui-core/?path=/docs/form-input-ebay-combobox)
- [Code Examples](https://github.com/eBay/ebayui-core/tree/master/src/components/ebay-combobox/examples)
9 changes: 9 additions & 0 deletions src/components/ebay-chips-combobox/browser.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"requireRemap": [
{
"from": "./style",
"to": "../../common/empty",
"if-flag": "ebayui-no-skin"
}
]
}
116 changes: 116 additions & 0 deletions src/components/ebay-chips-combobox/chips-combobox.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { addRenderBodies } from "../../../.storybook/utils";
import { tagToString } from "../../../.storybook/storybook-code-source";
import Readme from "./README.md";
import Combobox from "./index.marko";

const Template = (args) => ({
input: addRenderBodies(args),
});

export default {
title: "form input/ebay-chips-combobox",
component: Combobox,
parameters: {
docs: {
description: {
component: Readme,
},
},
},

argTypes: {
disabled: {
type: "boolean",
control: { type: "boolean" },
description: "sets the disabled attribute of the input",
},
expanded: {
control: { type: "boolean" },
description: "sets whether the listbox is expanded",
},
fluid: {
control: { type: "boolean" },
type: "boolean",
description:
"If true, combobox will span the entire width of it's container",
},
error: {
control: { type: "boolean" },
type: "boolean",
description: "sets the error state of the input",
},
listSelection: {
control: { type: "text" },
description:
"default is `automatic`; available values are `automatic`, `manual`. If set to automatic will automatically fill in the input with the currently highlighted item when using the up/down keys.",
},
a11yDeleteButton: {
control: { type: "text" },
description: "The aria-label for the delete button on each chip.",
},
roledescription: {
control: { type: "text" },
description:
"The role description for accessibility. Default text is set and will be in english. Pass this to override for different locales",
},
options: {
type: "array",
control: { type: "object" },
description: "array of autofill options to display",
},

onChange: {
action: "on-change",
table: {
category: "Events",
defaultValue: {
summary: "{ selected }",
},
},

description: "fires when the selected chips change",
},
onCollapse: {
action: "on-collapse",
table: {
category: "Events",
},
description: " collapsed content",
},
onExpand: {
action: "on-expand",
table: {
category: "Events",
},
description: " expanded content",
},
},
};

export const Isolated = Template.bind({});
Isolated.args = {
options: [
"football",
"baseball",
"basketball",
"hockey",
"soccer",
"volleyball",
"golf",
"tennis",
],
};
Isolated.parameters = {
docs: {
source: {
code: tagToString("ebay-combobox", Isolated.args, {
options: "option",
}),
},
},
expanded: {
table: {
category: "disabled",
},
},
};
72 changes: 72 additions & 0 deletions src/components/ebay-chips-combobox/component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { AttrString } from "marko/tags-html";
import type { WithNormalizedProps } from "../../global";
import { handleEnterKeydown } from "../../common/event-utils";

interface ChipsComboboxEvent {
selected: string[];
}

interface ChipsComboboxInput extends Omit<Marko.Input<"input">, `on${string}`> {
expanded?: boolean;
fluid?: boolean;
error?: boolean;
"list-selection"?: "manual" | "automatic";
options: string[];
selected?: string[];
roledescription?: AttrString;
"a11y-delete-button"?: AttrString;
"on-expand"?: () => void;
"on-collapse"?: () => void;
"on-change"?: (event: ChipsComboboxEvent) => void;
}

export interface Input extends WithNormalizedProps<ChipsComboboxInput> {}

interface State {
selected: string[];
}

export default class Combobox extends Marko.Component<Input, State> {
onCreate(input: Input) {
this.state = {
selected: input.selected ?? [],
};
}

onInput(input: Input) {
if (input.selected === null) {
this.state.selected = [];
} else if (input.selected) {
this.state.selected = input.selected;
}
}

handleKeydown(e: KeyboardEvent) {
handleEnterKeydown(e, () => {
const value = (e.target as HTMLInputElement).value;
if (value) {
e.preventDefault();
this.selectChip(value);
}
});
}

selectChip(text: string) {
if (!this.state.selected.includes(text)) {
this.state.selected = [...this.state.selected, text];
this.emit("change", {
selected: this.state.selected,
} satisfies ChipsComboboxEvent);
}
}

handleDelete(index: number) {
this.state.selected = [
...this.state.selected.slice(0, index),
...this.state.selected.slice(index + 1),
];
this.emit("change", {
selected: this.state.selected,
} satisfies ChipsComboboxEvent);
}
}
42 changes: 42 additions & 0 deletions src/components/ebay-chips-combobox/index.marko
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
$ const {
options,
selected: inputSelected,
class: inputClass,
fluid,
error,
value,
disabled,
a11yDeleteButton = "Remove",
...comboboxInput
} = input as Omit<typeof input, `on${string}`>;

<span class=["chips-combobox", fluid && "chips-combobox--fluid", error && "chips-combobox--error"] aria-disabled=disabled && "true">
<if(state.selected && state.selected.length)>
<ul class="chips-combobox__items">
<for|option, i| of=state.selected>
<li>
<ebay-chip a11y-delete-button=a11yDeleteButton on-delete("handleDelete", i) disabled=disabled>
${option}
</ebay-chip>
</li>
</for>
</ul>
</if>
<ebay-combobox
class=["chips-combobox__combobox", inputClass]
onKeydown("handleKeydown")
onOption-click("selectChip")
onExpand("emit", "expand")
onCollapse("emit", "collapse")
disabled=disabled
chevron-size="large"
...comboboxInput
autocomplete="list"
>
<for|option| of=options>
<if(!state.selected || !state.selected.some((val) => val === option))>
<@option text=option/>
</if>
</for>
</ebay-combobox>
</span>
47 changes: 47 additions & 0 deletions src/components/ebay-chips-combobox/marko-tag.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"attribute-groups": ["html-attributes"],
"@*": {
"targetProperty": null,
"type": "expression"
},
"@html-attributes": "expression",
"@borderless": "boolean",
"@autocomplete": "string",
"@expanded": "boolean",
"@list-selection": {
"enum": ["automatic", "manual"]
},
"@disabled": "#html-disabled",
"@form": "#html-form",
"@name": "#html-name",
"@placeholder": "#html-placeholder",
"@required": "#html-required",
"@value": "#html-value",
"@role": "never",
"@aria-autocomplete": "never",
"@aria-haspopup": "never",
"@floating-label": "string",
"@actionable <actionable>": {
"attribute-groups": ["html-attributes"],
"@*": {
"targetProperty": null,
"type": "expression"
},
"@html-attributes": "expression"
},
"@options <option>[]": {
"attribute-groups": ["html-attributes"],
"@*": {
"targetProperty": null,
"type": "expression"
},
"@html-attributes": "expression",
"@value": "string",
"@text": "string",
"@selected": "boolean",
"@tabindex": "never",
"@role": "never",
"@aria-selected": "never",
"@sticky": "boolean"
}
}
1 change: 1 addition & 0 deletions src/components/ebay-chips-combobox/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import "@ebay/skin/chips-combobox";
Loading

0 comments on commit fa6f613

Please sign in to comment.