Sidebar tree: stack parents above children, compact rows, icons

The tree reused .sidebar__page-item's flex ROW from the flat list, so
the nested children <ul> sat BESIDE the parent row and align-items:
center made the parent float vertically centered next to its subtree.
Stack the two in tree view instead: parent first, children indented
below (about two characters per level).

Rows get compacter (smaller font, tighter padding) so a deep imported
vault fits on screen, and folder pages now read differently from leaf
pages at a glance: lucide Folder/FolderOpen vs FileText, with the caret
glyph upgraded to a ChevronRight that keeps the existing CSS rotation.
Caret button, classes, and aria-labels stay untouched for the e2e
contract (#101 convention: no page title in the caret label).

Fixes #122

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fb2VzvcoBPHkjh8bZ6PzQn
This commit is contained in:
Claude Fable 5 2026-07-15 11:01:54 +02:00
parent b8546eb249
commit d4fb2a3c51
2 changed files with 40 additions and 5 deletions

View File

@ -8,6 +8,7 @@ import type {
} from '@dorfteich/shared'; } from '@dorfteich/shared';
import { buildTree, collectSubtreeIds, labelDepth } from '@dorfteich/shared'; import { buildTree, collectSubtreeIds, labelDepth } from '@dorfteich/shared';
import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useQuery, useQueryClient } from '@tanstack/react-query';
import { ChevronRight, FileText, Folder, FolderOpen } from 'lucide-react';
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
@ -527,11 +528,14 @@ function PageTreeLevel(props: PageTreeLevelProps): React.JSX.Element {
)} )}
onClick={() => onToggleCollapsed(node.id)} onClick={() => onToggleCollapsed(node.id)}
> >
<ChevronRight aria-hidden />
</button> </button>
) : ( ) : (
<span className="sidebar__caret sidebar__caret--leaf" aria-hidden /> <span className="sidebar__caret sidebar__caret--leaf" aria-hidden />
)} )}
<span className="sidebar__page-icon" aria-hidden>
{hasChildren ? isCollapsed ? <Folder /> : <FolderOpen /> : <FileText />}
</span>
<PageLink page={node} pondSlug={pondSlug} pageSlug={pageSlug} /> <PageLink page={node} pondSlug={pondSlug} pageSlug={pageSlug} />
{canReorder && ( {canReorder && (
<span className="sidebar__reorder"> <span className="sidebar__reorder">

View File

@ -198,23 +198,40 @@ button {
white-space: nowrap; white-space: nowrap;
} }
/* In the tree, the nested children <ul> is a SECOND flex item of the
.sidebar__page-item <li>. Stack it below the parent row (#122) the
base rule's flex row centered the parent vertically BESIDE its children. */
.sidebar__pages--tree .sidebar__page-item {
flex-direction: column;
align-items: stretch;
gap: 0;
}
.sidebar__tree-children { .sidebar__tree-children {
margin: 0 0 0 var(--space-3); /* Per-level indent: about two characters is plenty (#122). */
margin: 0 0 0 1.25em;
} }
.sidebar__caret { .sidebar__caret {
flex: 0 0 auto; flex: 0 0 auto;
display: inline-flex;
align-items: center;
justify-content: center;
width: 1rem; width: 1rem;
border: 0; border: 0;
background: none; background: none;
padding: 0; padding: 0;
cursor: pointer; cursor: pointer;
color: var(--color-text-muted); color: var(--color-text-muted);
font-size: 0.7rem;
transform: rotate(90deg); transform: rotate(90deg);
transition: transform 0.12s ease; transition: transform 0.12s ease;
} }
.sidebar__caret svg {
width: 0.9rem;
height: 0.9rem;
}
.sidebar__caret--collapsed { .sidebar__caret--collapsed {
transform: rotate(0deg); transform: rotate(0deg);
} }
@ -223,6 +240,19 @@ button {
cursor: default; cursor: default;
} }
/* Folder pages vs. leaf pages read differently at a glance (#122). */
.sidebar__page-icon {
flex: 0 0 auto;
display: inline-flex;
align-items: center;
color: var(--color-text-muted);
}
.sidebar__page-icon svg {
width: 0.875rem;
height: 0.875rem;
}
/* Label view (issue #108): pages grouped under the label tree. */ /* Label view (issue #108): pages grouped under the label tree. */
.sidebar__label-group { .sidebar__label-group {
margin-bottom: var(--space-2); margin-bottom: var(--space-2);
@ -322,11 +352,12 @@ button {
.sidebar__page { .sidebar__page {
display: block; display: block;
padding: var(--space-1) var(--space-2); /* Compact rows (#122): deep imported trees need to fit on one screen. */
padding: 0.125rem var(--space-2);
border-radius: var(--radius); border-radius: var(--radius);
color: var(--color-text); color: var(--color-text);
text-decoration: none; text-decoration: none;
font-size: 0.95rem; font-size: 0.875rem;
} }
.sidebar__page:hover { .sidebar__page:hover {