Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ebay-table): selection mode #2286

Open
wants to merge 24 commits into
base: 14.4.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/seven-adults-help.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ebay/ebayui-core": minor
---

feat(ebay-table): adding support for selection mode
94 changes: 90 additions & 4 deletions src/components/ebay-table/component.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { AttrTriState } from "marko/tags-html";
import { WithNormalizedProps } from "../../global";
import { CheckboxEvent } from "../ebay-checkbox/component-browser";

// export type ColumnType = "normal" | "numeric" | "row-header" | "none";
export type TableDensity = "compact" | "relaxed" | "none";
type TableColRowName = string | number;
export type TableSelectEvent = {
selected: Record<TableColRowName, boolean>;
allSelected?: AttrTriState;
};
Comment on lines +6 to +9
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this? Might as well just define this inline. Thats what we do in other places

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean AttrTriState or this TableSelectEvent? AttrTriState is the same as tri-state checkbox.

For this TableSelectEvent, I can do it inline. But I find it useful when ppl actually using typescript and need to handle the event.

import { TableSelectEvent } from "<ebay-table>";

class {
  handler(event: TableSelectEvent) {
  // handler
  }
}

<ebay-table on-select("handler") />

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

export interface TableHeader extends Omit<Marko.Input<"th">, `on${string}`> {
columnType?: string; // Use ColumnType after marko fix the ts error for attr tags
renderBody: Marko.Body;
Expand All @@ -11,13 +16,94 @@ export interface TableCell
renderBody: Marko.Body;
}
export interface TableRow extends Omit<Marko.Input<"tr">, `on${string}`> {
name?: TableColRowName;
selected?: boolean;
cell: Marko.RepeatableAttrTag<TableCell> | Marko.AttrTag<TableCell>[];
}
export interface TableInput extends Omit<Marko.Input<"div">, `on${string}`> {
header: Marko.RepeatableAttrTag<TableHeader> | Marko.AttrTag<TableHeader>[];
mode?: "none" | "selection";
allSelected?: AttrTriState;
row?: Marko.RepeatableAttrTag<TableRow> | Marko.AttrTag<TableRow>[];
density?: TableDensity;
density?: "compact" | "relaxed" | "none";
"a11y-select-all-text"?: string;
"a11y-select-row-text"?: string;
"on-select"?: (event: TableSelectEvent) => void;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on-select should also include the new select data as an argument

}
export interface Input extends WithNormalizedProps<TableInput> {}

export default class extends Marko.Component<Input> {}
interface State {
selected: Record<TableColRowName, boolean>;
allSelected: AttrTriState;
}

export default class EbayTable extends Marko.Component<Input, State> {
onCreate() {
this.state = {
selected: {},
allSelected: "false",
};
}

onInput(input: Input) {
this.state.selected = this.getSelectedRowStateFromInput(input);
this.state.allSelected = this.getAllSelectedState(input);
}

getSelectedRowStateFromInput(input: Input) {
const selected: Record<TableColRowName, boolean> = {};
if (input.row) {
for (const [i, row] of Object.entries([...input.row])) {
const name = row.name || i;
selected[name] = row.selected || false;
}
}
return selected;
}

getAllSelectedState(input: Input): AttrTriState {
if (input.allSelected) {
return input.allSelected;
}
let selectedCount = 0;
let rowCount = 0;
for (const [_name, selected] of Object.entries(this.state.selected)) {
if (selected) {
selectedCount++;
}
rowCount++;
}
if (selectedCount === 0) {
return "false";
}
if (selectedCount === rowCount) {
return "true";
}
return "mixed";
}

headerSelect() {
const { allSelected } = this.state;
this.state.selected = [...(this.input.row || [])].reduce(
(acc, { name }, i) => {
acc[name || i] = allSelected !== "true";
return acc;
},
{} as Record<TableColRowName, boolean>,
);
this.state.allSelected = allSelected !== "true" ? "true" : "false";
this.emit("select", {
selected: this.state.selected,
allSelected: this.state.allSelected,
});
}

rowSelect(name: TableColRowName, { checked }: CheckboxEvent) {
this.state.selected[name] = checked;
this.setStateDirty("selected");
this.state.allSelected = this.getAllSelectedState(this.input);
this.emit("select", {
selected: this.state.selected,
});
}
}
48 changes: 48 additions & 0 deletions src/components/ebay-table/examples/selection.marko
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import data from "./data.json";

<ebay-table mode="selection" on-select("emit", "select") ...input>
<@header column-type="row-header">
Seller
</@header>
<@header>Item</@header>
<@header>Status</@header>
<@header column-type="numeric">
List Price
</@header>
<@header column-type="numeric">
Quantity Available
</@header>
<@header>Orders</@header>
<@header column-type="numeric">
Watchers
</@header>
<@header column-type="numeric">
Protection
</@header>
<@header>Shipping</@header>
<@header>Delivery</@header>
<for|r, i| of=data>
<!-- first row selected by default -->
$ const selected = i === 0;
<@row name=`row_${i}` selected=selected>
<@cell>${r.seller}</@cell>
<@cell>${r.item.title}</@cell>
<@cell>
<ebay-signal status=r.statusType as any>
${r.status}
</ebay-signal>
</@cell>
<@cell>${r.listPrice}</@cell>
<@cell>${r.quantityAvailable}</@cell>
<@cell>
<a href="https://ebay.com">
${r.orders.number}
</a>
</@cell>
<@cell>${r.watchers}</@cell>
<@cell>${r.protection}</@cell>
<@cell>${r.shipping}</@cell>
<@cell>${r.delivery}</@cell>
</@row>
</for>
</ebay-table>
32 changes: 30 additions & 2 deletions src/components/ebay-table/index.marko
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@ $ const {
density,
header: headers,
row: rows,
allSelected,
a11ySelectAllText,
a11ySelectRowText,
mode = "none",
...divInput
} = input;

<div
...processHtmlAttributes(divInput)
class=[
"table",
mode === "selection" && "table--mode-selection",
density &&
validDensity.includes(density) &&
`table--density-${density}`,
Expand All @@ -23,6 +28,15 @@ $ const {
<table>
<thead>
<tr>
<if(mode === "selection")>
<th class="table__cell">
<ebay-tri-state-checkbox
aria-label=(a11ySelectAllText ?? "Select all rows")
checked=state.allSelected
on-change("headerSelect")
/>
</th>
</if>
<for|header| of=headers>
$ const {
columnType = "normal",
Expand All @@ -44,9 +58,23 @@ $ const {
</tr>
</thead>
<tbody>
<for|row| of=rows || []>
$ const { cell: cells, ...trInput } = row;
<for|row, rowIndex| of=rows || []>
$ const {
cell: cells,
name = rowIndex,
selected,
...trInput
} = row;
<tr ...processHtmlAttributes(trInput)>
<if(mode === "selection")>
<td class="table__cell">
<ebay-checkbox
aria-label=(a11ySelectRowText ?? "Select row")
checked=(state.selected[name])
on-change("rowSelect", name)
/>
</td>
</if>
<for|header, index| of=headers>
$ const cell = (
Array.isArray(cells) ? cells[index] : cells
Expand Down
5 changes: 5 additions & 0 deletions src/components/ebay-table/marko-tag.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
"@density": {
"enum": ["compact", "relaxed"]
},
"@all-selected": {
"enum": ["true", "false", "mixed"]
},
"@header <header>[]": {
"attribute-groups": ["html-attributes"],
"@*": {
Expand All @@ -26,6 +29,8 @@
"type": "expression"
},
"@html-attributes": "expression",
"@name": "string",
"@selected": "boolean",
"@cell <cell>[]": {
"attribute-groups": ["html-attributes"],
"@*": {
Expand Down
50 changes: 44 additions & 6 deletions src/components/ebay-table/table.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import table from "./index.marko";
import Readme from "./README.md";
import defaultTemplate from "./examples/default.marko";
import defaultCode from "./examples/default.marko?raw";
import selectionTemplate from "./examples/selection.marko";
import selectionCode from "./examples/selection.marko?raw";

export default {
title: "data-display/table",
Expand All @@ -20,6 +22,16 @@ export default {
description: "table density",
options: ["compact", "relaxed", "none"],
},
mode: {
control: { type: "select" },
description: "table mode",
options: ["selection", "none"],
},
selectionState: {
control: { type: "select" },
description: "Select all tri-state checkbox state",
options: ["none-selected", "indeterminate", "all-selected"],
},
header: {
name: "@header",
description: "header attribute tags",
Expand All @@ -42,6 +54,13 @@ export default {
category: "@header attribute tags",
},
},
selected: {
name: "selected",
control: { type: "boolean" },
table: {
category: "@header attribute tags",
},
},
cell: {
controls: { hideNoControlsWarning: true },
name: "@cell",
Expand All @@ -50,13 +69,32 @@ export default {
category: "@row attribute tags",
},
},
onSelect: {
action: "on-select",
description: "Triggered on selection",
table: {
category: "Events",
defaultValue: {
summary: "{ selected, allSelected }",
},
},
},
},
};

export const Default = buildExtensionTemplate(defaultTemplate, defaultCode);
export const Dense = buildExtensionTemplate(defaultTemplate, defaultCode, {
density: "compact",
});
export const Relaxed = buildExtensionTemplate(defaultTemplate, defaultCode, {
density: "relaxed",
});
export const TableDensity = buildExtensionTemplate(
defaultTemplate,
defaultCode,
{
density: "compact",
},
);
export const SelectionModeBasic = buildExtensionTemplate(
selectionTemplate,
selectionCode,
{
a11ySelectAllText: "Select all",
a11ySelectRowText: "Select row",
},
);
Loading
Loading