Files
gitmost/apps/client/src/features/page/tree/styles/tree.module.css
T
vvzvlad b1d48d9d9a fix(client): compact page tree + fix selection highlight alignment
Reduce DocTree row stride from 32px to 26px for a denser sidebar tree,
and fix the selection/hover highlight that looked unbalanced at the
tighter spacing.

Root causes:
- The virtualized <li> had no explicit height, so `.node`'s height:100%
  collapsed to content height; combined with the asymmetric
  `[role="treeitem"] { padding-bottom: 2px }` rule, row content was
  pushed to the top of the highlight pill (icon glued to the top edge).
- NodeMenu / CreateNode action icons used the default Mantine ActionIcon
  size (md = 28px), overflowing the tighter 26px row stride onto
  neighbouring rows.

Changes:
- doc-tree.tsx: rowHeight 32 -> 26; give each row <li> a definite
  height = rowHeight.
- tree.module.css: rowWrapper fills the slot (height:100%); node pill is
  inset and vertically centered (height: calc(100% - 4px)); drop the
  asymmetric [role="treeitem"] padding-bottom.
- space-tree-node-menu.tsx / space-tree-row.tsx: action icons size={20}.
- share.module.css: drop now-dead .treeNode padding-bottom override.

Verified in an isolated browser harness: highlight content is centered
(2.8/2.8px) and nothing overflows the row stride.
2026-06-18 23:08:42 +03:00

196 lines
4.4 KiB
CSS

.tree {
border-radius: 0;
}
.treeContainer {
height: 100%;
min-width: 0;
/* DocTree renders a vanilla <ul role="tree"> with no internal virtualizer,
so the container must own the scroll. Without this the tree grows past
its parent and the page scrolls instead. */
overflow-y: auto;
overflow-x: hidden;
}
.node {
position: relative;
border-radius: 4px;
display: flex;
align-items: center;
/* Inset the highlight pill by 2px top + 2px bottom inside the full-height
row slot. rowWrapper (height: 100%, align-items: center) vertically
centers this pill in the stride, giving an even gap between rows and
symmetric padding around the row content. */
height: calc(100% - 4px);
width: 100%;
text-decoration: none;
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-0));
/* Gate hover styles to mouse-capable devices. Touch browsers synthesize
:hover on the first tap (sticky hover) and only fire click on the
second tap, requiring a double-tap to navigate. */
@media (hover: hover) {
&:hover {
background-color: light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-5));
}
&:hover .actions {
opacity: 1;
pointer-events: auto;
}
}
.actions {
display: inline-flex;
flex-shrink: 0;
align-items: center;
margin-left: 4px;
opacity: 0;
pointer-events: none;
}
&:focus-within .actions {
opacity: 1;
pointer-events: auto;
}
}
.icon {
margin: 0 rem(10px);
flex-shrink: 0;
}
.actionIcon {
color: light-dark(var(--mantine-color-dark-3), var(--mantine-color-gray-4));
}
.text {
flex: 1;
/* min-width: 0 lets a flex child shrink below its content size — required
for text-overflow: ellipsis on flex items. */
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: rem(14px);
font-weight: 500;
}
.arrow {
display: flex;
}
/* Strip the browser's default <ul> bullet + indent from the DocTree
<ul role="tree"> and nested <ul role="group"> nodes. The tree's own indent
is driven by paddingLeft on .rowWrapper. */
[role="tree"],
[role="tree"] [role="group"] {
list-style: none;
margin: 0;
padding: 0;
}
/* ---- pragmatic-tree additions ---- */
.rowWrapper {
position: relative;
display: flex;
align-items: center;
border-radius: 4px;
/* Fill the full row slot (the <li> is sized to the virtualizer stride) so
align-items: center can vertically center the inset `.node` pill. */
height: 100%;
}
.node[data-dragging="true"] {
opacity: 0.4;
}
.node:focus-visible {
outline: 2px solid light-dark(
var(--mantine-color-blue-5),
var(--mantine-color-blue-4)
);
outline-offset: -2px;
}
.node :focus-visible {
outline-offset: -2px;
}
.node[data-selected="true"] {
background-color: light-dark(
var(--mantine-color-gray-3),
var(--mantine-color-dark-6)
);
}
.node[data-selected="true"] .actions {
opacity: 1;
pointer-events: auto;
}
.node[data-receiving-drop="make-child"] {
background-color: light-dark(
var(--mantine-color-blue-1),
rgba(56, 139, 253, 0.15)
);
outline: 2px solid light-dark(
var(--mantine-color-blue-5),
var(--mantine-color-blue-7)
);
outline-offset: -1px;
}
.node[data-receiving-drop="make-child-blocked"] {
outline-color: light-dark(
var(--mantine-color-red-5),
var(--mantine-color-red-7)
);
}
.dropLine {
position: absolute;
left: var(--drop-line-indent, 0);
right: 8px;
height: 2px;
background: light-dark(
var(--mantine-color-blue-5),
var(--mantine-color-blue-4)
);
pointer-events: none;
z-index: 1;
}
.dropLine::before {
content: "";
position: absolute;
left: -4px;
top: -3px;
width: 8px;
height: 8px;
border: 2px solid currentColor;
border-radius: 50%;
color: light-dark(
var(--mantine-color-blue-5),
var(--mantine-color-blue-4)
);
background: var(--mantine-color-body);
}
.dropLine[data-blocked="true"] {
background: light-dark(
var(--mantine-color-red-5),
var(--mantine-color-red-4)
);
}
.dropLine[data-edge="top"] {
top: -1px;
}
.dropLine[data-edge="bottom"] {
bottom: -1px;
}