diff --git a/src/assets/style/smoothly.css b/src/assets/style/smoothly.css index fe2dd9fc9..6e707ccd4 100644 --- a/src/assets/style/smoothly.css +++ b/src/assets/style/smoothly.css @@ -58,5 +58,4 @@ --smoothly-shadow: 0 0 4px 2px rgba(var(--smoothly-color-contrast), 0.25); --smoothly-shadow-strong: 0 0 4px 4px rgba(var(--smoothly-light-color), 0.25); - --table-width: 100%; } diff --git a/src/components.d.ts b/src/components.d.ts index 6bd102fac..16c53064e 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -630,11 +630,15 @@ export namespace Components { "tooltip": string; } interface SmoothlyTable { + "cardAt"?: string; "columns": number; } interface SmoothlyTableBody { } interface SmoothlyTableCell { + "cardArea"?: "checkbox" | "primary" | "status" | "actions"; + "cardLabel"?: string; + "cardVisibility": "always" | "opened" | "hidden"; "span"?: number; } interface SmoothlyTableDemo { @@ -2881,11 +2885,15 @@ declare namespace LocalJSX { "tooltip"?: string; } interface SmoothlyTable { + "cardAt"?: string; "columns"?: number; } interface SmoothlyTableBody { } interface SmoothlyTableCell { + "cardArea"?: "checkbox" | "primary" | "status" | "actions"; + "cardLabel"?: string; + "cardVisibility"?: "always" | "opened" | "hidden"; "span"?: number; } interface SmoothlyTableDemo { diff --git a/src/components/table/cell/index.tsx b/src/components/table/cell/index.tsx index 6e534a702..f073ff9fe 100644 --- a/src/components/table/cell/index.tsx +++ b/src/components/table/cell/index.tsx @@ -7,10 +7,14 @@ import { Component, h, Host, Prop, VNode } from "@stencil/core" }) export class SmoothlyTableCell { @Prop({ reflect: true }) span?: number = 1 + @Prop({ reflect: true }) cardLabel?: string + @Prop({ reflect: true }) cardArea?: "checkbox" | "primary" | "status" | "actions" + @Prop({ reflect: true }) cardVisibility: "always" | "opened" | "hidden" = "always" render(): VNode | VNode[] { return ( - + + {typeof this.cardLabel == "string" && {this.cardLabel}} ) diff --git a/src/components/table/cell/style.css b/src/components/table/cell/style.css index 593d9eaad..b03cd76da 100644 --- a/src/components/table/cell/style.css +++ b/src/components/table/cell/style.css @@ -1,7 +1,4 @@ -:host { - grid-column: span var(--smoothly-table-cell-span, 1); - display: flex; - align-items: center; - padding: 0.5rem 1.1rem; - white-space: nowrap; +:host>.smoothly-card-field-label { + min-width: var(--smoothly-card-field-label-min-width, 12ch); + color: rgb(var(--smoothly-card-label-color, var(--smoothly-medium-color))); } diff --git a/src/components/table/demo/index.tsx b/src/components/table/demo/index.tsx index 3d29f4a3f..2d1028f4e 100644 --- a/src/components/table/demo/index.tsx +++ b/src/components/table/demo/index.tsx @@ -10,9 +10,9 @@ export class SmoothlyTableDemo { return ( - - - + {/* */} + {/* */} + {/* */} diff --git a/src/components/table/demo/nested-no-cell/index.tsx b/src/components/table/demo/nested-no-cell/index.tsx index 94f84de21..9691040fa 100644 --- a/src/components/table/demo/nested-no-cell/index.tsx +++ b/src/components/table/demo/nested-no-cell/index.tsx @@ -11,7 +11,7 @@ export class SmoothlyTableDemoNestedNoCell { return ( - +
Id
@@ -22,20 +22,35 @@ export class SmoothlyTableDemoNestedNoCell {
EyeColor
Gender
Company
+
{data.map(entry => ( -
{entry.id}
-
{entry.registered}
-
{entry.name}
-
{entry.age}
-
{entry.balance}
-
{entry.eyeColor}
-
{entry.gender}
-
{entry.company}
+ {entry.id} + {entry.registered} + + {entry.name} + + {entry.age} + + {entry.balance} + + + {entry.eyeColor} + + {entry.gender} + {entry.company} + + console.log("Delete", entry.name)} + /> +
))}
diff --git a/src/components/table/demo/nested-no-cell/inner/index.tsx b/src/components/table/demo/nested-no-cell/inner/index.tsx index 449aa275e..19a419dc1 100644 --- a/src/components/table/demo/nested-no-cell/inner/index.tsx +++ b/src/components/table/demo/nested-no-cell/inner/index.tsx @@ -27,10 +27,10 @@ export class SmoothlyTableDemoNestedNoCellInner { {this.data?.map(entry => ( -
{entry.id}
-
{entry.name}
-
{entry.age}
-
{entry.balance}
+
{entry.id}
+
{entry.name}
+
{entry.age}
+
{entry.balance}
))}
diff --git a/src/components/table/demo/nested-no-cell/inner/style.css b/src/components/table/demo/nested-no-cell/inner/style.css index a2ff0df2a..29602573e 100644 --- a/src/components/table/demo/nested-no-cell/inner/style.css +++ b/src/components/table/demo/nested-no-cell/inner/style.css @@ -1,5 +1,5 @@ :host { display: block; margin: 1em auto; - width: 60%; + max-width: min(30rem, 100%); } diff --git a/src/components/table/demo/simple/simple.tsx b/src/components/table/demo/simple/simple.tsx index 39e86fe81..eef4c23e9 100644 --- a/src/components/table/demo/simple/simple.tsx +++ b/src/components/table/demo/simple/simple.tsx @@ -34,23 +34,6 @@ export class SmoothlyTableDemoSimple { ))} - - !e.button && e.preventDefault()} target="_blank"> - Cell1 in expandable row - Cell2 in expandable row - -
- Content -
- of -
- the -
- expandable -
- row -
-
Cell5 diff --git a/src/components/table/demo/style.css b/src/components/table/demo/style.css index 86b793981..8a46cb11b 100644 --- a/src/components/table/demo/style.css +++ b/src/components/table/demo/style.css @@ -5,5 +5,6 @@ max-width: 80rem; width: 100%; margin: auto; - padding: 2rem; + padding-block: 2rem; + padding-inline: 0.5rem; } diff --git a/src/components/table/expandable/arrow.css b/src/components/table/expandable/arrow.css index 588d8c608..21233e28f 100644 --- a/src/components/table/expandable/arrow.css +++ b/src/components/table/expandable/arrow.css @@ -20,7 +20,7 @@ :host::slotted(smoothly-table-cell:first-child:hover)::before, :host::slotted(smoothly-table-cell:first-child:has(~ smoothly-table-cell:hover))::before, -:host>div.content:hover:first-child::before { +:host>div.smoothly-table-cell:hover:first-child::before { opacity: 1; border-bottom: 1px solid rgb(var(--smoothly-color-contrast)); border-right: 1px solid rgb(var(--smoothly-color-contrast)); diff --git a/src/components/table/expandable/cell/index.tsx b/src/components/table/expandable/cell/index.tsx index 201910f2c..5e0d33aa3 100644 --- a/src/components/table/expandable/cell/index.tsx +++ b/src/components/table/expandable/cell/index.tsx @@ -35,7 +35,7 @@ export class SmoothlyTableExpandableCell { this.clickHandler(e)}> -
+
(this.detailElement = el)}> diff --git a/src/components/table/expandable/cell/style.scss b/src/components/table/expandable/cell/style.scss index d59a0c79c..1ab20cba7 100644 --- a/src/components/table/expandable/cell/style.scss +++ b/src/components/table/expandable/cell/style.scss @@ -4,11 +4,12 @@ display: contents; } -:host>div { - padding: 0.5rem 1.1rem; +:host > .smoothly-table-cell { + padding-inline: var(--smoothly-table-cell-padding-inline, 1.1rem); + min-height: var(--smoothly-table-cell-min-height, 2.75rem); } -:host>div.content { +:host > div.smoothly-table-cell { grid-column: span var(--smoothly-table-cell-span, 1); display: flex; box-sizing: border-box; @@ -25,37 +26,37 @@ } } -:host[open]>div.content { +:host[open] > div.smoothly-table-cell { @include arrow-open; } -:host>div:first-child { +:host > div:first-child { cursor: pointer; } -:host>div.detail { +:host > div.detail { grid-column: 1 / -1; grid-row: 2; position: relative; } -:host[open]>div.content { +:host[open] > div.smoothly-table-cell { box-shadow: 1px 1px 1px -1px rgb(var(--smoothly-table-border)) inset, -3px -1px 1px -3px rgb(var(--smoothly-table-border)) inset; } -:host[open]>div { +:host[open] > div { background-color: rgb(var(--smoothly-table-expanded-background)); color: rgb(var(--smoothly-table-expanded-foreground)); stroke: rgb(var(--smoothly-table-expanded-foreground)); fill: rgb(var(--smoothly-table-expanded-foreground)); } -:host:not([open])>div.detail { +:host:not([open]) > div.detail { display: none; } -:host>div.detail::before { +:host > div.detail::before { content: ""; position: absolute; display: flex; @@ -70,7 +71,7 @@ 0px 0px 1px 0px rgb(var(--smoothly-table-border)); } -:host>div.detail::after { +:host > div.detail::after { content: ""; position: absolute; display: flex; diff --git a/src/components/table/expandable/row/index.tsx b/src/components/table/expandable/row/index.tsx index 8b3b95066..ab5704451 100644 --- a/src/components/table/expandable/row/index.tsx +++ b/src/components/table/expandable/row/index.tsx @@ -6,16 +6,34 @@ import { Component, Event, EventEmitter, h, Host, Prop, VNode, Watch } from "@st scoped: true, }) export class SmoothlyTableExpandableRow { - private div?: HTMLDivElement + private detailElement?: HTMLDivElement + private expandButton?: HTMLDivElement @Prop({ mutable: true, reflect: true }) open = false @Event() smoothlyTableExpandableRowChange: EventEmitter + isStacked(eventPath: EventTarget[]): boolean { + for (const el of eventPath) { + if (el instanceof HTMLElement && el.tagName.toLowerCase() == "smoothly-table") { + return el.classList.contains("cards") + } + } + return false + } + clickHandler(event: MouseEvent): void { - const clickedOnDetail = this.div && event.composedPath().includes(this.div) - if (!clickedOnDetail) { - const selection = window.getSelection()?.toString().trim() - if ((selection?.length ?? 0) == 0) + const isStacked = this.isStacked(event.composedPath()) + if (isStacked) { + const clickedOnExpandButton = this.expandButton && event.composedPath().includes(this.expandButton) + if (clickedOnExpandButton) { this.open = !this.open + } + } else { + const clickedOnDetail = this.detailElement && event.composedPath().includes(this.detailElement) + if (!clickedOnDetail) { + const selection = window.getSelection()?.toString().trim() + if ((selection?.length ?? 0) == 0) + this.open = !this.open + } } } @Watch("open") @@ -27,9 +45,10 @@ export class SmoothlyTableExpandableRow { return ( this.clickHandler(e)}> -
(this.div = e)}> +
(this.detailElement = e)}>
+
(this.expandButton = e)} class="smoothly-expand-button">
) } diff --git a/src/components/table/expandable/row/style.scss b/src/components/table/expandable/row/style.scss index 2e47cb155..e88537912 100644 --- a/src/components/table/expandable/row/style.scss +++ b/src/components/table/expandable/row/style.scss @@ -1,5 +1,3 @@ -@import "../arrow.scss"; - :host { grid-column: 1 / -1; display: grid; @@ -8,95 +6,65 @@ box-shadow: 0px 1px 1px -1px rgb(var(--smoothly-table-border)); } -:host>.smoothly-row { - display: grid; - grid-template-columns: subgrid; - grid-column: 1 / -1; -} - -:host::slotted(:not(.detail)):not(.smoothly-row), -:host>::slotted(.smoothly-row>*) { - grid: subgrid; - cursor: pointer; - grid-column: span var(--smoothly-table-cell-span, 1); - display: flex; - align-items: center; - padding: 0.5rem 1.1rem; - white-space: nowrap; -} - -:host:has(>:not(:last-child):hover), -:host>.smoothly-row:has(>:not(:last-child):hover) { - background-color: rgb(var(--smoothly-table-hover-background)); - color: rgb(var(--smoothly-table-hover-foreground)); -} - :host[open] { background-color: rgb(var(--smoothly-table-expanded-background)); - color: rgb(var(--smoothly-table-expanded-foreground)); - grid-template-rows: auto 1fr; - box-shadow: 0px 1px 1px -1px rgb(var(--smoothly-table-border)), - 0px 0px 1px rgb(var(--smoothly-table-border)) inset; - grid-template-rows: 0fr 1fr; } -:host(:not([open]))>div.detail { +:host(:not([open])) > div.detail { display: none; } -:host>div:last-child { +:host > div.detail { grid-column: 1 / -1; cursor: default; position: relative; - overflow: hidden; background-color: rgb(var(--smoothly-table-expanded-background)); color: rgb(var(--smoothly-table-expanded-foreground)); } -:host> :first-child, -:host>.smoothly-row> :first-child { - @include arrow; -} - -:host:has(>:hover)> :first-child, -:host>.smoothly-row:has(>:hover)> :first-child { - @include arrow-hover; -} - -:host[open]> :first-child, -:host[open]>.smoothly-row>:first-child { - @include arrow-open; -} - -:host[open]>div:last-child { - overflow: visible; - padding: 0.5rem 1.1rem; -} +/* Regular table (non-cards) */ -:host>div:last-child::before { - content: ""; +:host > div.detail::before, +:host > div.detail::after { position: absolute; display: flex; box-sizing: border-box; - top: 0; - bottom: 0; - left: -1em; + inset-block: 0; width: 1em; +} + +:host > div.detail::before { + left: -1em; background-color: rgb(var(--smoothly-table-expanded-background)); border-left: 0.3em solid rgb(var(--smoothly-table-detail-border, var(--smoothly-table-border))); box-shadow: 2px 0px 0px 0px rgb(var(--smoothly-table-expanded-background)), 0px 0px 1px 0px rgb(var(--smoothly-table-border)); } -:host>div:last-child::after { - content: ""; - position: absolute; - display: flex; - top: 0; - bottom: 0; +:host > div.detail::after { right: -1em; - width: 1em; background-color: rgb(var(--smoothly-table-expanded-background)); box-shadow: -2px 0px 0px 0px rgb(var(--smoothly-table-expanded-background)), 0px 0px 1px 0px rgb(var(--smoothly-table-border)); } + +/* Stacked Cards */ + +:host > .smoothly-expand-button { + grid-column: 1 / -1; + border-top: 1px solid rgb(var(--smoothly-card-border-color)); + position: relative; + height: 2.5rem; + + &::before { + content: ""; + top: 50%; + left: 50%; + position: absolute; + border-left: 3px solid rgb(var(--smoothly-expand-arrow-color, var(--smoothly-default-contrast))); + border-bottom: 3px solid rgb(var(--smoothly-expand-arrow-color, var(--smoothly-default-contrast))); + transform: translate(-50%, -75%) rotate(-45deg); + width: 0.75rem; + height: 0.75rem; + } +} diff --git a/src/components/table/index.tsx b/src/components/table/index.tsx index db90728a5..d9fd99e5d 100644 --- a/src/components/table/index.tsx +++ b/src/components/table/index.tsx @@ -1,13 +1,15 @@ -import { Component, h, Host, Listen, Prop, VNode } from "@stencil/core" +import { Component, h, Host, Listen, Prop, State, VNode } from "@stencil/core" import { SmoothlyTableExpandableCellCustomEvent } from "../../components" @Component({ tag: "smoothly-table", - styleUrl: "style.css", + styleUrl: "style.scss", scoped: true, }) export class SmoothlyTable { @Prop() columns = 1 + @Prop({ reflect: true }) cardAt?: string + @State() mode: "cards" | "table" = "table" @Listen("smoothlyTableExpandableRowChange") smoothlyTableExpandableRowChange(event: SmoothlyTableExpandableCellCustomEvent): void { @@ -19,9 +21,17 @@ export class SmoothlyTable { event.stopPropagation() } + componentWillLoad() { + if (this.cardAt) { + const mql = window.matchMedia(`(max-width: ${this.cardAt})`) + this.mode = mql.matches ? "cards" : "table" + mql.addEventListener("change", e => (this.mode = e.matches ? "cards" : "table")) + } + } + render(): VNode | VNode[] { return ( - + ) diff --git a/src/components/table/row/style.css b/src/components/table/row/style.css index 8a1749836..736a49272 100644 --- a/src/components/table/row/style.css +++ b/src/components/table/row/style.css @@ -8,7 +8,9 @@ :host::slotted(*) { grid-column: span var(--smoothly-table-cell-span, 1); align-items: center; - padding: 0.5rem 1.1rem; + padding-inline: var(--smoothly-table-cell-padding-inline, 1.1rem); + min-height: var(--smoothly-table-cell-min-height, 2.25rem); + box-sizing: border-box; white-space: nowrap; } diff --git a/src/components/table/style.css b/src/components/table/style.css deleted file mode 100644 index 1e22481be..000000000 --- a/src/components/table/style.css +++ /dev/null @@ -1,7 +0,0 @@ -:host { - display: grid; - grid-template-columns: repeat(var(--columns), auto); - color: rgb(var(--smoothly-table-foreground)); - stroke: rgb(var(--smoothly-table-foreground)); - fill: rgb(var(--smoothly-table-foreground)); -} diff --git a/src/components/table/style.scss b/src/components/table/style.scss new file mode 100644 index 000000000..5639359dd --- /dev/null +++ b/src/components/table/style.scss @@ -0,0 +1,167 @@ +@import "./expandable/arrow.scss"; + +:host { + color: rgb(var(--smoothly-table-foreground)); + stroke: rgb(var(--smoothly-table-foreground)); + fill: rgb(var(--smoothly-table-foreground)); +} + +/* Regular table (non-cards) */ + +:host(:not(.cards)) { + display: grid; + grid-template-columns: repeat(var(--columns), auto); + + & > smoothly-table-body > *, + & > smoothly-table-body { + & > smoothly-table-expandable-row { + + & > smoothly-table-cell > .smoothly-card-field-label { + display: none; + } + + & > div.detail::before, + & > div.detail::after { + content: ""; + } + + & > *.smoothly-table-cell { + grid: subgrid; + cursor: pointer; + grid-column: span var(--smoothly-table-cell-span, 1); + display: flex; + align-items: center; + padding-inline: var(--smoothly-table-cell-padding-inline, 1.1rem); + min-height: var(--smoothly-table-cell-min-height, 2.75rem); + white-space: nowrap; + } + + & > .smoothly-expand-button { + display: none; + visibility: hidden; + } + + &:has(>.smoothly-table-cell:hover) { + background-color: rgb(var(--smoothly-table-hover-background)); + color: rgb(var(--smoothly-table-hover-foreground)); + } + + & > :is(.smoothly-table-cell):first-child { + @include arrow; + } + + &:has(>:hover) > :is(.smoothly-table-cell):first-child { + @include arrow-hover; + } + + &[open] { + color: rgb(var(--smoothly-table-expanded-foreground)); + box-shadow: 0px 1px 1px -1px rgb(var(--smoothly-table-border)), + 0px 0px 1px rgb(var(--smoothly-table-border)) inset; + + & > :is(.smoothly-table-cell):first-child { + @include arrow-open; + } + + & > div.detail { + overflow: visible; + padding-block: var(--smoothly-table-detail-padding-block, 0.5rem); + padding-inline: var(--smoothly-table-detail-padding-inline, 1.1rem); + } + } + } + } +} + + +/* Stacked/Cards */ + +:host(.cards) { + display: grid; + + & > smoothly-table-head { + display: none; + } + + & > smoothly-table-body { + /* css-var card-gap */ + row-gap: var(--smoothly-card-row-gap, 1.5rem); + } + + & > smoothly-table-body > *, + & > smoothly-table-body { + + & > smoothly-table-row, + & > smoothly-table-expandable-row { + display: grid; + grid-template-columns: auto 1fr auto auto; + /* css-var card-shadow-color? */ + box-shadow: 0 1px 3px rgba(var(--smoothly-default-contrast), 0.12), + 0 1px 2px rgba(var(--smoothly-default-contrast), 0.24); + border: 1px solid rgb(var(--smoothly-table-border), 0.25); + border-radius: var(--smoothly-card-border-radius, 0.5rem); + text-overflow: ellipsis; + overflow: hidden; + + & > .smoothly-table-cell { + /* css-var card-field-padding-inline? */ + padding-inline: 0.75rem; + box-sizing: border-box; + display: flex; + align-items: center; + } + + & > .smoothly-table-cell:not([card-area]) { + grid-column: 1 / -1; + /* css-var card-field-min-height? */ + min-height: var(--smoothly-card-field-min-height, 2rem); + } + + & > .smoothly-table-cell[card-area] { + grid-row: 1; + /* css-var card-header-min-height? */ + min-height: var(--smoothly-card-header-min-height, 3rem); + + & > .smoothly-card-field-label { + display: none; + } + } + + & > .smoothly-table-cell[card-area=checkbox] { + grid-column: 1; + } + + & > .smoothly-table-cell[card-area=primary] { + grid-column: 2; + } + + & > .smoothly-table-cell[card-area=status] { + grid-column: 3; + } + + & > .smoothly-table-cell[card-area=actions] { + grid-column: 4; + } + + & > .smoothly-table-cell[card-visibility=hidden] { + display: none; + } + + &:not([open]) > .smoothly-table-cell[card-visibility=opened] { + display: none; + } + + &[open] > div.detail { + max-width: 100%; + overflow-y: auto; + padding-block: var(--smoothly-card-detail-padding-block, 0); + padding-inline: var(--smoothly-card-detail-padding-inline, 0); + } + + &[open] > .smoothly-expand-button::before { + transform: translate(-50%, -25%) rotate(135deg); + } + } + + } +}