fix and expand table styles. man do I ever love scroll animations!!!

This commit is contained in:
Chris Kruining 2024-12-11 10:07:48 +01:00
parent 41bd3463e7
commit f21fa5e224
No known key found for this signature in database
GPG key ID: EB894A3560CCCAD2
2 changed files with 109 additions and 34 deletions

View file

@ -1,26 +1,29 @@
.table { .table {
--shadow: hsla(0 0% 0% / .1) 0 .5em 2em;
position: relative; position: relative;
display: grid; display: block grid;
grid-template-columns: repeat(var(--columns), minmax(0, auto)); grid-template-columns: repeat(var(--columns), minmax(max-content, auto));
align-content: start; align-content: start;
padding-inline: 1px; padding-inline: 1px;
margin-inline: -1px; margin-inline: -1px;
overflow: auto;
background-color: inherit;
isolation: isolate;
block-size: 100%; block-size: 100%;
&.selectable {
grid-template-columns: 2em repeat(calc(var(--columns) - 1), minmax(0, auto));
}
& input[type="checkbox"] { & input[type="checkbox"] {
margin: .1em; margin: .1em;
} }
& .cell { & .cell {
display: grid; display: block grid;
padding: var(--padding-m); padding: var(--padding-m);
border: 1px solid transparent; border: 1px solid transparent;
border-radius: var(--radii-m); border-radius: var(--radii-m);
background: inherit;
white-space: nowrap;
&:has(textarea:focus) { &:has(textarea:focus) {
border-color: var(--info); border-color: var(--info);
@ -29,11 +32,32 @@
& > span { & > span {
align-self: center; align-self: center;
} }
&:first-of-type {
position: sticky;
inset-inline-start: calc(var(--padding-m) * var(--depth));
padding-inline-start: calc(var(--padding-m) * (1 + var(--depth)));
z-index: 1;
&::after {
content: '';
position: absolute;
inset-inline-start: 100%;
inset-block-start: -2px;
display: block;
inline-size: 2em;
block-size: calc(3px + 100%);
background: linear-gradient(90deg, hsla(0 0% 0% / .05), transparent);
animation: column-scroll-shadow linear both;
animation-timeline: scroll(inline);
animation-range: 0 2em;
}
}
} }
& :is(.header, .main, .footer) { & :is(.header, .main, .footer) {
grid-column: span calc(2 + var(--columns)); grid-column: span calc(2 + var(--columns));
display: grid; display: block grid;
grid-template-columns: subgrid; grid-template-columns: subgrid;
} }
@ -41,35 +65,49 @@
position: sticky; position: sticky;
inset-block-start: 0; inset-block-start: 0;
border-block-end: 1px solid var(--surface-300); border-block-end: 1px solid var(--surface-300);
background-color: inherit;
z-index: 2;
animation: header-scroll-shadow linear both;
animation-timeline: scroll();
animation-range: 0 2em;
& > aside {
position: sticky;
inset-inline-start: 0;
background: inherit;
padding: var(--padding-m);
z-index: 1;
}
} }
& .main { & .main {
overflow: clip auto; background-color: inherit;
} }
& .row { & .row {
--bg: var(--text);
--alpha: 0; --alpha: 0;
grid-column: span calc(2 + var(--columns)); grid-column: span calc(2 + var(--columns));
display: grid; display: block grid;
grid-template-columns: subgrid; grid-template-columns: subgrid;
border: 1px solid transparent; border: 1px solid transparent;
background-color: color(from var(--bg) srgb r g b / var(--alpha)); background-color: inherit;
background-image: linear-gradient(0deg, oklch(from var(--info) l c h / var(--alpha)), oklch(from var(--info) l c h / var(--alpha)));
&:has(> .cell > :checked) { &:has(> aside > :checked) {
--bg: var(--info);
--alpha: .1; --alpha: .1;
border-color: var(--bg); border-color: var(--info);
& span { & span {
font-variation-settings: 'GRAD' 1000; font-variation-settings: 'GRAD' 1000;
} }
& + :has(> .cell> :checked) { /* Remove the top border when directly preceded by a selected row */
& + :has(> aside > :checked) {
border-block-start-color: transparent; border-block-start-color: transparent;
} }
&:has(+ .row > .cell > :checked) { /* Remove the top border when directly succeeded by a selected row */
&:has(+ .row > aside > :checked) {
border-block-end-color: transparent; border-block-end-color: transparent;
} }
} }
@ -77,15 +115,25 @@
&:hover { &:hover {
--alpha: .2 !important; --alpha: .2 !important;
} }
& > aside {
position: sticky;
inset-inline-start: 0;
background: inherit;
padding: var(--padding-m);
z-index: 1;
}
} }
& details { & details {
display: contents; display: contents;
background-color: inherit;
&::details-content { &::details-content {
grid-column: span calc(2 + var(--columns)); grid-column: span calc(2 + var(--columns));
display: grid; display: block grid;
grid-template-columns: subgrid; grid-template-columns: subgrid;
background-color: inherit;
} }
&:not([open])::details-content { &:not([open])::details-content {
@ -93,19 +141,54 @@
} }
& > summary { & > summary {
position: sticky;
inset-inline-start: 0;
grid-column: 1;
padding: .5em; padding: .5em;
padding-inline-start: calc(var(--depth) * 1em + .5em); padding-inline-start: calc(var(--depth) * 1em + .5em);
} }
& > .row > .cell { & > .row > .cell {
padding-inline-start: calc(var(--depth) * 1em + var(--padding-m)); padding-inline-start: calc(var(--depth) * 1em + var(--padding-m));
} }
} }
&.selectable {
grid-template-columns: 2em repeat(calc(var(--columns) - 1), minmax(max-content, auto));
& .cell:first-of-type {
inset-inline-start: calc(2em + var(--padding-m) * var(--depth));
}
& details > summary {
inset-inline-start: 2em;
grid-column: 2;
}
}
} }
@property --depth { @property --depth {
syntax: "<number>"; syntax: "<number>";
inherits: false; inherits: false;
initial-value: 0; initial-value: 0;
}
@keyframes header-scroll-shadow {
from {
box-shadow: none;
}
to {
box-shadow: var(--shadow);
}
}
@keyframes column-scroll-shadow {
from {
background: linear-gradient(90deg, transparent, transparent);
}
to {
background: linear-gradient(90deg, hsla(0 0% 0% / .05), transparent);
}
} }

View file

@ -102,14 +102,14 @@ function Head<T extends Record<string, any>>(props: {}) {
return <header class={css.header}> return <header class={css.header}>
<Show when={table.selectionMode() !== SelectionMode.None}> <Show when={table.selectionMode() !== SelectionMode.None}>
<div class={css.cell}> <aside>
<input <input
type="checkbox" type="checkbox"
checked={context.selection().length > 0 && context.selection().length === context.length()} checked={context.selection().length > 0 && context.selection().length === context.length()}
indeterminate={context.selection().length !== 0 && context.selection().length !== context.length()} indeterminate={context.selection().length !== 0 && context.selection().length !== context.length()}
on:input={(e: InputEvent) => e.target.checked ? context.selectAll() : context.clear()} on:input={(e: InputEvent) => e.target.checked ? context.selectAll() : context.clear()}
/> />
</div> </aside>
</Show> </Show>
<For each={table.columns()}>{ <For each={table.columns()}>{
@ -137,15 +137,15 @@ function Row<T extends Record<string, any>>(props: { key: string, value: T, dept
const values = createMemo(() => Object.entries(props.value)); const values = createMemo(() => Object.entries(props.value));
const isSelected = context.isSelected(props.key); const isSelected = context.isSelected(props.key);
return <div class={css.row} use:selectable={{ value: props.value, key: props.key }}> return <div class={css.row} style={{ '--depth': props.depth }} use:selectable={{ value: props.value, key: props.key }}>
<Show when={table.selectionMode() !== SelectionMode.None}> <Show when={table.selectionMode() !== SelectionMode.None}>
<div class={css.cell}> <aside>
<input type="checkbox" checked={isSelected()} on:input={() => context.select([props.key])} on:pointerdown={e => e.stopPropagation()} /> <input type="checkbox" checked={isSelected()} on:input={() => context.select([props.key])} on:pointerdown={e => e.stopPropagation()} />
</div> </aside>
</Show> </Show>
<For each={values()}>{ <For each={values()}>{
([k, v]) => <div style={k === props.groupedBy ? { '--depth': props.depth } : {}} class={css.cell}>{table.cellRenderers()[k]?.({ value: v }) ?? v}</div> ([k, v]) => <div class={css.cell}>{table.cellRenderers()[k]?.({ value: v }) ?? v}</div>
}</For> }</For>
</div>; </div>;
}; };
@ -153,16 +153,8 @@ function Row<T extends Record<string, any>>(props: { key: string, value: T, dept
function Group<T extends Record<string, any>>(props: { key: string, groupedBy: keyof T, nodes: Node<T>[], depth: number }) { function Group<T extends Record<string, any>>(props: { key: string, groupedBy: keyof T, nodes: Node<T>[], depth: number }) {
const table = useTable(); const table = useTable();
const gridColumn = createMemo(() => {
const groupedBy = props.groupedBy;
const columns = table.columns();
const selectable = table.selectionMode() !== SelectionMode.None;
return columns.findIndex(({ id }) => id === groupedBy) + (selectable ? 2 : 1);
});
return <details open> return <details open>
<summary style={{ '--depth': props.depth, 'grid-column-start': gridColumn() }}>{props.key}</summary> <summary style={{ '--depth': props.depth }}>{props.key}</summary>
<For each={props.nodes}>{ <For each={props.nodes}>{
node => <Node node={node} depth={props.depth + 1} groupedBy={props.groupedBy} /> node => <Node node={node} depth={props.depth + 1} groupedBy={props.groupedBy} />