Compare commits

...

38 Commits

Author SHA1 Message Date
Adorian Doran
ea25477e3d ui/pager: fix some issues 2026-02-16 02:06:39 +02:00
Adorian Doran
82576c9703 ui/pager: rewrite the pagination button distribution algorithm to better control the length 2026-02-16 01:51:32 +02:00
Adorian Doran
0b8cba78d5 ui/pager: remove unused styles 2026-02-15 21:36:53 +02:00
Adorian Doran
d8806eaa04 ui/pager: replace the page number links with buttons 2026-02-15 21:31:58 +02:00
Adorian Doran
b8bc85856b ui/pager: add prev/next page navigation buttons 2026-02-15 21:12:42 +02:00
Adorian Doran
7551d0e044 ui/cards: fix attribute name 2026-02-15 20:32:01 +02:00
Adorian Doran
489a88b8da ui/cards: refactor 2026-02-15 20:23:19 +02:00
Adorian Doran
48013dc264 ui/cards: use a better HTML structure 2026-02-15 20:14:14 +02:00
Adorian Doran
4ee9d45dfc ui/cards: implement card titles (heading) 2026-02-15 20:06:01 +02:00
Adorian Doran
e00150a876 ui/cards: do not include nesting level CSS variables if not necessary 2026-02-15 19:46:08 +02:00
Adorian Doran
71668f8f8d List collections & search results: overhaul the list UI (#8705) 2026-02-15 19:32:21 +02:00
Adorian Doran
b71424d239 Merge branch 'main' into feat/list-view-overhaul 2026-02-15 19:20:41 +02:00
Adorian Doran
d1a3bceaa6 client/list view: refactor 2026-02-15 19:15:58 +02:00
Adorian Doran
483c57029a client/list view: extract event handler 2026-02-15 19:10:31 +02:00
Adorian Doran
7e6daf5b36 style/list view: refactor 2026-02-15 18:55:23 +02:00
Adorian Doran
b658253687 style/card: refactor 2026-02-15 18:55:04 +02:00
Adorian Doran
80b488deec style/list view: tweak indentation size 2026-02-15 18:34:54 +02:00
Adorian Doran
10cf1a371e style/card: improve 2026-02-15 18:34:00 +02:00
Adorian Doran
f645d9d721 style/list view: tweak 2026-02-15 18:20:45 +02:00
Adorian Doran
900bfdff9d style/list view: refactor 2026-02-15 18:18:50 +02:00
Adorian Doran
108ca5afb5 client/list view: add support for archived items 2026-02-15 18:16:46 +02:00
Adorian Doran
3df03a551c client/list view: add support for archived items 2026-02-15 18:15:07 +02:00
Adorian Doran
6ffbe19667 client/list view: add support for tinted notes 2026-02-15 18:07:35 +02:00
Adorian Doran
fea8de89c6 style/list view: handle the title and attributes when overflowing the container 2026-02-15 14:08:50 +02:00
Adorian Doran
4afbabb977 style/list view: tweak 2026-02-15 13:51:13 +02:00
Adorian Doran
b2ebaf111f style/card: add legacy theme fallback 2026-02-15 13:43:06 +02:00
Adorian Doran
9c2b01e3c9 style/list view: tweak 2026-02-15 13:34:45 +02:00
Adorian Doran
dca201ce42 style/list view: tweak 2026-02-15 13:31:45 +02:00
Adorian Doran
3774ea3768 style/list view: tweak 2026-02-15 02:28:52 +02:00
Adorian Doran
0a9c6a3119 client/list view: add a menu button for list items 2026-02-15 02:26:23 +02:00
Adorian Doran
a6cbde88bb client/list view: add an alternate style for search results 2026-02-15 01:57:10 +02:00
Adorian Doran
9c13f36ca0 client/list view: show the contents of a note only after its rendering completes 2026-02-14 16:39:20 +02:00
Adorian Doran
fe4a11c5ad client/list view: improve appearance 2026-02-14 16:22:13 +02:00
Adorian Doran
218343ca14 client/list view: use a sectioned card to display the list items 2026-02-14 01:14:54 +02:00
Adorian Doran
61953fd713 client/ui/cards: make a property optional 2026-02-14 00:10:54 +02:00
Adorian Doran
62ddf3a11b client/ui/cards: automatically determine the opacity of the background color of nested card sections 2026-02-14 00:08:59 +02:00
Adorian Doran
be12658864 client/ui/cards: add support for custom class names and action callbacks 2026-02-14 00:08:00 +02:00
Adorian Doran
b618e5a00f client: define the foundation of sectioned cards with multi-level nesting 2026-02-13 22:50:03 +02:00
10 changed files with 463 additions and 80 deletions

View File

@@ -291,6 +291,15 @@
--ck-editor-toolbar-button-on-shadow: 1px 1px 2px rgba(0, 0, 0, .75);
--ck-editor-toolbar-dropdown-button-open-background: #ffffff14;
--note-list-view-icon-color: var(--left-pane-icon-color);
--note-list-view-large-icon-background: var(--note-icon-background-color);
--note-list-view-large-icon-color: var(--note-icon-color);
--note-list-view-search-result-highlight-background: transparent;
--note-list-view-search-result-highlight-color: var(--quick-search-result-highlight-color);
--note-list-view-content-background: rgba(0, 0, 0, .2);
--note-list-view-content-search-result-highlight-background: var(--quick-search-result-highlight-color);
--note-list-view-content-search-result-highlight-color: black;
--calendar-coll-event-background-saturation: 25%;
--calendar-coll-event-background-lightness: 20%;
--calendar-coll-event-background-color: #3c3c3c;
@@ -304,7 +313,8 @@
* Dark color scheme tweaks
*/
#left-pane .fancytree-node.tinted {
#left-pane .fancytree-node.tinted,
.nested-note-list-item.use-note-color {
--custom-color: var(--dark-theme-custom-color);
/* The background color of the active item in the note tree.
@@ -354,7 +364,8 @@ body .todo-list input[type="checkbox"]:not(:checked):before {
}
.note-split.with-hue,
.quick-edit-dialog-wrapper.with-hue {
.quick-edit-dialog-wrapper.with-hue,
.nested-note-list-item.with-hue {
--note-icon-custom-background-color: hsl(var(--custom-color-hue), 15.8%, 30.9%);
--note-icon-custom-color: hsl(var(--custom-color-hue), 100%, 76.5%);
--note-icon-hover-custom-background-color: hsl(var(--custom-color-hue), 28.3%, 36.7%);

View File

@@ -289,6 +289,15 @@
--ck-editor-toolbar-button-on-shadow: none;
--ck-editor-toolbar-dropdown-button-open-background: #0000000f;
--note-list-view-icon-color: var(--left-pane-icon-color);
--note-list-view-large-icon-background: var(--note-icon-background-color);
--note-list-view-large-icon-color: var(--note-icon-color);
--note-list-view-search-result-highlight-background: transparent;
--note-list-view-search-result-highlight-color: var(--quick-search-result-highlight-color);
--note-list-view-content-background: #b1b1b133;
--note-list-view-content-search-result-highlight-background: var(--quick-search-result-highlight-color);
--note-list-view-content-search-result-highlight-color: white;
--calendar-coll-event-background-lightness: 95%;
--calendar-coll-event-background-saturation: 80%;
--calendar-coll-event-background-color: #eaeaea;
@@ -298,7 +307,8 @@
--calendar-coll-today-background-color: #00000006;
}
#left-pane .fancytree-node.tinted {
#left-pane .fancytree-node.tinted,
.nested-note-list-item.use-note-color {
--custom-color: var(--light-theme-custom-color);
/* The background color of the active item in the note tree.
@@ -324,7 +334,8 @@
}
.note-split.with-hue,
.quick-edit-dialog-wrapper.with-hue {
.quick-edit-dialog-wrapper.with-hue,
.nested-note-list-item.with-hue {
--note-icon-custom-background-color: hsl(var(--custom-color-hue), 44.5%, 43.1%);
--note-icon-custom-color: hsl(var(--custom-color-hue), 91.3%, 91%);
--note-icon-hover-custom-background-color: hsl(var(--custom-color-hue), 55.1%, 50.2%);

View File

@@ -751,12 +751,14 @@ body[dir=rtl] #left-pane span.fancytree-node.protected > span.fancytree-custom-i
}
}
#left-pane .fancytree-expander {
#left-pane .fancytree-expander,
.nested-note-list-item .note-expander {
opacity: 0.65;
transition: opacity 150ms ease-in;
}
#left-pane .fancytree-expander:hover {
#left-pane .fancytree-expander:hover,
.nested-note-list-item .note-expander:hover {
opacity: 1;
transition: opacity 300ms ease-out;
}

View File

@@ -2190,8 +2190,9 @@
"percentage": "%"
},
"pagination": {
"page_title": "Page of {{startIndex}} - {{endIndex}}",
"total_notes": "{{count}} notes"
"total_notes": "{{count}} notes",
"prev_page": "Previous page",
"next_page": "Next page"
},
"collections": {
"rendering_error": "Unable to show content due to an error."

View File

@@ -19,14 +19,3 @@ body.prefers-centered-content .note-list-widget:not(.full-height) {
.note-list-widget video {
height: 100%;
}
/* #region Pagination */
.note-list-pager {
font-size: 1rem;
span.current-page {
text-decoration: underline;
font-weight: bold;
}
}
/* #endregion */

View File

@@ -4,6 +4,8 @@ import FNote from "../../entities/fnote";
import froca from "../../services/froca";
import { useNoteLabelInt } from "../react/hooks";
import { t } from "../../services/i18n";
import ActionButton from "../react/ActionButton";
import Button from "../react/Button";
interface PaginationContext {
page: number;
@@ -17,46 +19,79 @@ interface PaginationContext {
export function Pager({ page, pageSize, setPage, pageCount, totalNotes }: Omit<PaginationContext, "pageNotes">) {
if (pageCount < 2) return;
let lastPrinted = false;
let children: ComponentChildren[] = [];
for (let i = 1; i <= pageCount; i++) {
if (pageCount < 20 || i <= 5 || pageCount - i <= 5 || Math.abs(page - i) <= 2) {
lastPrinted = true;
const startIndex = (i - 1) * pageSize + 1;
const endIndex = Math.min(totalNotes, i * pageSize);
if (i !== page) {
children.push((
<a
href="javascript:"
title={t("pagination.page_title", { startIndex, endIndex })}
onClick={() => setPage(i)}
>
{i}
</a>
))
} else {
// Current page
children.push(<span className="current-page">{i}</span>)
}
children.push(<>{" "}&nbsp;{" "}</>);
} else if (lastPrinted) {
children.push(<>{"... "}&nbsp;{" "}</>);
lastPrinted = false;
}
}
const children = createPageButtons(page, setPage, pageCount);
return (
<div class="note-list-pager">
{children}
<ActionButton
icon="bx bx-caret-left"
disabled={(page === 1)}
text={t("pagination.prev_page")}
onClick={() => {setPage(page - 1)}}
/>
{children}
<ActionButton
icon="bx bx-caret-right"
disabled={(page === pageCount)}
text={t("pagination.next_page")}
onClick={() => {setPage(page + 1)}}
/>
<span className="note-list-pager-total-count">({t("pagination.total_notes", { count: totalNotes })})</span>
</div>
)
}
function createPageButtons(page: number, setPage: Dispatch<StateUpdater<number>>, pageCount: number): ComponentChildren[] {
const maxButtonCount = 9;
const maxLeftRightSegmentLength = 2;
// The left-side segment
const leftLength = Math.min(pageCount, maxLeftRightSegmentLength);
const leftStart = 1;
// The middle segment
const middleMaxLength = maxButtonCount - maxLeftRightSegmentLength * 2;
const middleLength = Math.min(pageCount - leftLength, middleMaxLength);
let middleStart = page - Math.floor(middleLength / 2);
middleStart = Math.max(middleStart, leftLength + 1);
// The right-side segment
const rightLength = Math.min(pageCount - (middleLength + leftLength), maxLeftRightSegmentLength);
const rightStart = pageCount - rightLength + 1;
middleStart = Math.min(middleStart, rightStart - middleLength);
return [
...createSegment(leftStart, leftLength, page, setPage, false),
...createSegment(middleStart, middleLength, page, setPage, (middleStart - leftLength > 1)),
...createSegment(rightStart, rightLength, page, setPage, (rightStart - (middleStart + middleLength - 1) > 1)),
];
}
function createSegment(start: number, length: number, currentPage: number, setPage: Dispatch<StateUpdater<number>>, prependEllipsis: boolean): ComponentChildren[] {
const children: ComponentChildren[] = [];
if (prependEllipsis) {
children.push("...");
}
for (let i = 0; i < length; i++) {
const pageNum = start + i;
children.push((
<Button
text={pageNum.toString()}
disabled={(pageNum === currentPage)}
onClick={() => setPage(pageNum)}
/>
));
}
return children;
}
export function usePagination(note: FNote, noteIds: string[]): PaginationContext {
const [ page, setPage ] = useState(1);
const [ pageNotes, setPageNotes ] = useState<FNote[]>();

View File

@@ -100,23 +100,206 @@
overflow: auto;
}
.note-expander {
font-size: x-large;
position: relative;
top: 3px;
cursor: pointer;
/* #region List view */
@keyframes note-preview-show {
from {
opacity: 0;
} to {
opacity: 1;
}
}
.note-list-pager {
text-align: center;
.nested-note-list {
--card-nested-section-indent: 25px;
&.search-results {
--card-nested-section-indent: 32px;
}
}
.note-list.list-view .note-path {
margin-left: 0.5em;
vertical-align: middle;
opacity: 0.5;
/* List item */
.nested-note-list-item {
h5 {
display: flex;
align-items: center;
font-size: 1em;
font-weight: normal;
margin: 0;
}
.note-expander {
margin-inline-end: 4px;
font-size: x-large;
cursor: pointer;
}
.tn-icon {
margin-inline-end: 8px;
color: var(--note-list-view-icon-color);
font-size: 1.2em;
}
.note-book-title {
--link-hover-background: transparent;
--link-hover-color: currentColor;
color: inherit;
font-weight: normal;
}
.note-path {
margin-left: 0.5em;
vertical-align: middle;
opacity: 0.5;
}
.note-list-attributes {
flex-grow: 1;
margin-inline-start: 1em;
text-align: right;
font-size: .75em;
opacity: .75;
}
.nested-note-list-item-menu {
margin-inline-start: 8px;
flex-shrink: 0;
}
&.archived {
span.tn-icon + span,
.tn-icon {
opacity: .6;
}
}
&.use-note-color {
span.tn-icon + span,
.nested-note-list:not(.search-results) & .tn-icon,
.rendered-note-attributes {
color: var(--custom-color);
}
}
}
.nested-note-list:not(.search-results) h5 {
span.tn-icon + span,
.note-list-attributes {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
/* List item (search results view) */
.nested-note-list.search-results .nested-note-list-item {
span.tn-icon + span > span {
display: flex;
flex-direction: column-reverse;
align-items: flex-start;
}
small {
line-height: .85em;
}
.note-path {
margin-left: 0;
font-size: .85em;
line-height: .85em;
font-weight: 500;
letter-spacing: .5pt;
}
.tn-icon {
display: flex;
flex-shrink: 0;
justify-content: center;
align-items: center;
width: 1.75em;
height: 1.75em;
margin-inline-end: 12px;
border-radius: 50%;
background: var(--note-icon-custom-background-color, var(--note-list-view-large-icon-background));
font-size: 1.2em;
color: var(--note-icon-custom-color, var(--note-list-view-large-icon-color));
}
h5 .ck-find-result {
background: var(--note-list-view-search-result-highlight-background);
color: var(--note-list-view-search-result-highlight-color);
font-weight: 600;
text-decoration: underline;
}
}
/* Note content preview */
.nested-note-list .note-book-content {
display: none;
outline: 1px solid var(--note-list-view-content-background);
border-radius: 8px;
background-color: var(--note-list-view-content-background);
overflow: hidden;
user-select: text;
font-size: .85rem;
animation: note-preview-show .25s ease-out;
will-change: opacity;
&.note-book-content-ready {
display: block;
}
> .rendered-content > *:last-child {
margin-bottom: 0;
}
&.type-text {
padding: 8px 24px;
.ck-content > *:last-child {
margin-bottom: 0;
}
}
&.type-protectedSession {
padding: 20px;
}
&.type-image {
padding: 0;
}
&.type-pdf {
iframe {
height: 50vh;
}
.file-footer {
padding: 8px;
}
}
&.type-webView {
display: flex;
flex-direction: column;
justify-content: center;
min-height: 50vh;
}
.ck-find-result {
outline: 2px solid var(--note-list-view-content-search-result-highlight-background);
border-radius: 4px;
background: var(--note-list-view-content-search-result-highlight-background);
color: var(--note-list-view-content-search-result-highlight-color);
}
}
.note-content-preview:has(.note-book-content:empty) {
display: none;
}
/* #endregion */
/* #region Grid view */
.note-list.grid-view .note-list-container {
display: flex;

View File

@@ -1,4 +1,5 @@
import "./ListOrGridView.css";
import { Card, CardSection } from "../../react/Card";
import { useEffect, useRef, useState } from "preact/hooks";
@@ -14,6 +15,11 @@ import NoteLink from "../../react/NoteLink";
import { ViewModeProps } from "../interface";
import { Pager, usePagination } from "../Pagination";
import { filterChildNotes, useFilteredNoteIds } from "./utils";
import { JSX } from "preact/jsx-runtime";
import { clsx } from "clsx";
import ActionButton from "../../react/ActionButton";
import linkContextMenuService from "../../../menus/link_context_menu";
import { TargetedMouseEvent } from "preact";
export function ListView({ note, noteIds: unfilteredNoteIds, highlightedTokens }: ViewModeProps<{}>) {
const expandDepth = useExpansionDepth(note);
@@ -33,7 +39,7 @@ export function ListView({ note, noteIds: unfilteredNoteIds, highlightedTokens }
{ noteIds.length > 0 && <div class="note-list-wrapper">
{!hasCollectionProperties && <Pager {...pagination} />}
<div class="note-list-container use-tn-links">
<Card className={clsx("nested-note-list", {"search-results": (noteType === "search")})}>
{pageNotes?.map(childNote => (
<ListNoteCard
key={childNote.noteId}
@@ -41,7 +47,7 @@ export function ListView({ note, noteIds: unfilteredNoteIds, highlightedTokens }
expandDepth={expandDepth} highlightedTokens={highlightedTokens}
currentLevel={1} includeArchived={includeArchived} />
))}
</div>
</Card>
<Pager {...pagination} />
</div>}
@@ -93,27 +99,52 @@ function ListNoteCard({ note, parentNote, highlightedTokens, currentLevel, expan
// Reset expand state if switching to another note, or if user manually toggled expansion state.
useEffect(() => setExpanded(currentLevel <= expandDepth), [ note, currentLevel, expandDepth ]);
let subSections: JSX.Element | undefined = undefined;
if (isExpanded) {
subSections = <>
<CardSection className="note-content-preview">
<NoteContent note={note}
highlightedTokens={highlightedTokens}
noChildrenList
includeArchivedNotes={includeArchived} />
</CardSection>
<NoteChildren note={note}
parentNote={parentNote}
highlightedTokens={highlightedTokens}
currentLevel={currentLevel}
expandDepth={expandDepth}
includeArchived={includeArchived} />
</>
}
return (
<div
className={`note-book-card no-tooltip-preview ${isExpanded ? "expanded" : ""} ${note.isArchived ? "archived" : ""}`}
<CardSection
className={clsx("nested-note-list-item", "no-tooltip-preview", note.getColorClass(), {
"expanded": isExpanded,
"archived": note.isArchived
})}
subSections={subSections}
subSectionsVisible={isExpanded}
highlightOnHover
data-note-id={note.noteId}
>
<h5 className="note-book-header">
<span
className={`note-expander ${isExpanded ? "bx bx-chevron-down" : "bx bx-chevron-right"}`}
onClick={() => setExpanded(!isExpanded)}
/>
<h5>
<span className={`note-expander ${isExpanded ? "bx bx-chevron-down" : "bx bx-chevron-right"}`}
onClick={() => setExpanded(!isExpanded)}/>
<Icon className="note-icon" icon={note.getIcon()} />
<NoteLink className="note-book-title" notePath={notePath} noPreview showNotePath={parentNote.type === "search"} highlightedTokens={highlightedTokens} />
<NoteLink className="note-book-title"
notePath={notePath}
noPreview
showNotePath={parentNote.type === "search"}
highlightedTokens={highlightedTokens} />
<NoteAttributes note={note} />
<ActionButton className="nested-note-list-item-menu"
icon="bx bx-dots-vertical-rounded" text=""
onClick={(e) => openNoteMenu(notePath, e)}
/>
</h5>
{isExpanded && <>
<NoteContent note={note} highlightedTokens={highlightedTokens} noChildrenList includeArchivedNotes={includeArchived} />
<NoteChildren note={note} parentNote={parentNote} highlightedTokens={highlightedTokens} currentLevel={currentLevel} expandDepth={expandDepth} includeArchived={includeArchived} />
</>}
</div>
</CardSection>
);
}
@@ -165,6 +196,9 @@ export function NoteContent({ note, trim, noChildrenList, highlightedTokens, inc
const contentRef = useRef<HTMLDivElement>(null);
const highlightSearch = useImperativeSearchHighlighlighting(highlightedTokens);
const [ready, setReady] = useState(false);
const [noteType, setNoteType] = useState<string>("none");
useEffect(() => {
content_renderer.getRenderedContent(note, {
trim,
@@ -179,17 +213,19 @@ export function NoteContent({ note, trim, noChildrenList, highlightedTokens, inc
} else {
contentRef.current.replaceChildren();
}
contentRef.current.classList.add(`type-${type}`);
highlightSearch(contentRef.current);
setNoteType(type);
setReady(true);
})
.catch(e => {
console.warn(`Caught error while rendering note '${note.noteId}' of type '${note.type}'`);
console.error(e);
contentRef.current?.replaceChildren(t("collections.rendering_error"));
setReady(true);
});
}, [ note, highlightedTokens ]);
return <div ref={contentRef} className="note-book-content" />;
return <div ref={contentRef} className={clsx("note-book-content", `type-${noteType}`, {"note-book-content-ready": ready})} />;
}
function NoteChildren({ note, parentNote, highlightedTokens, currentLevel, expandDepth, includeArchived }: {
@@ -238,3 +274,8 @@ function useExpansionDepth(note: FNote) {
return parseInt(expandDepth, 10);
}
function openNoteMenu(notePath, e: TargetedMouseEvent<HTMLElement>) {
linkContextMenuService.openContextMenu(notePath, e);
e.stopPropagation()
}

View File

@@ -0,0 +1,47 @@
:where(.tn-card) {
--card-border-radius: 8px;
--card-padding-block: 8px;
--card-padding-inline: 16px;
--card-section-gap: 3px;
--card-nested-section-indent: 30px;
}
.tn-card-heading {
margin-bottom: 10px;
font-size: .75rem;
font-weight: 600;
letter-spacing: .4pt;
text-transform: uppercase;
}
.tn-card-body {
display: flex;
flex-direction: column;
gap: var(--card-section-gap);
.tn-card-section {
padding: var(--card-padding-block) var(--card-padding-inline);
border: 1px solid var(--card-border-color, var(--main-border-color));
background: var(--card-background-color);
&:first-of-type {
border-top-left-radius: var(--card-border-radius);
border-top-right-radius: var(--card-border-radius);
}
&:last-of-type {
border-bottom-left-radius: var(--card-border-radius);
border-bottom-right-radius: var(--card-border-radius);
}
&.tn-card-section-nested {
padding-left: calc(var(--card-padding-inline) + var(--card-nested-section-indent) * var(--tn-card-section-nesting-level));
background-color: color-mix(in srgb, var(--card-background-color) calc(100% / (var(--tn-card-section-nesting-level) + 1)) , transparent);
}
&.tn-card-section-highlight-on-hover:hover {
background-color: var(--card-background-hover-color);
transition: background-color .2s ease-out;
}
}
}

View File

@@ -0,0 +1,63 @@
import "./Card.css";
import { ComponentChildren, createContext } from "preact";
import { JSX } from "preact";
import { useContext } from "preact/hooks";
import clsx from "clsx";
// #region Card
export interface CardProps {
className?: string;
heading?: string;
}
export function Card(props: {children: ComponentChildren} & CardProps) {
return <div className={clsx("tn-card", props.className)}>
{props.heading && <h5 class="tn-card-heading">{props.heading}</h5>}
<div className="tn-card-body">
{props.children}
</div>
</div>;
}
// #endregion
// #region Card Section
export interface CardSectionProps {
className?: string;
subSections?: JSX.Element | JSX.Element[];
subSectionsVisible?: boolean;
highlightOnHover?: boolean;
onAction?: () => void;
}
interface CardSectionContextType {
nestingLevel: number;
}
const CardSectionContext = createContext<CardSectionContextType | undefined>(undefined);
export function CardSection(props: {children: ComponentChildren} & CardSectionProps) {
const parentContext = useContext(CardSectionContext);
const nestingLevel = (parentContext && parentContext.nestingLevel + 1) ?? 0;
return <>
<section className={clsx("tn-card-section", props.className, {
"tn-card-section-nested": nestingLevel > 0,
"tn-card-section-highlight-on-hover": props.highlightOnHover || props.onAction
})}
style={{"--tn-card-section-nesting-level": (nestingLevel) ? nestingLevel : null}}
onClick={props.onAction}>
{props.children}
</section>
{props.subSectionsVisible && props.subSections &&
<CardSectionContext.Provider value={{nestingLevel}}>
{props.subSections}
</CardSectionContext.Provider>
}
</>;
}
// #endregion