Restore center-pane borders, move title to top-left slot
- Current pane gets muted left/right borders only (no full box, no accent ring); border colors are passed only when a border is requested, since opentui flips borderless boxes to bordered when borderColor is supplied. - Remove the Up / <current tab> / Detail titles above the panes; the current pane's title now renders once, top-left in the parent column's header slot. - Drop the parentLabel/previewLabel props from PaneRow and all callers. - Remove the tab/depth indicator from the bottom-left of the status bar. - Tests measure column widths from the border glyphs and assert the left/right edges render muted regardless of focus.
This commit is contained in:
@@ -10,25 +10,26 @@
|
||||
* Column semantics (per the yazi depth model):
|
||||
* parent — the previous-depth list. Renders a muted `—` placeholder and
|
||||
* KEEPS its 1/5 slot when blank (never collapses to width 0).
|
||||
* Borderless (no left/right/top/bottom edge).
|
||||
* Borderless (no left/right/top/bottom edge). Carries the single
|
||||
* header row: the CURRENT column's title renders top-left in the
|
||||
* parent's slot (the panes above current/preview were removed).
|
||||
* current — the current-depth list. The only focusable content column; it
|
||||
* is the ONLY bordered column (full border, always muted — no
|
||||
* active-border highlight).
|
||||
* preview — detail of the hovered item in `current`. Borderless.
|
||||
* is the ONLY bordered column — left/right edges only, always
|
||||
* muted (no active-border highlight, focused or not).
|
||||
* preview — detail of the hovered item in `current`. Borderless, no header.
|
||||
*
|
||||
* The primitive is purely structural: callers pass their own JSX per column
|
||||
* (static elements or accessors) plus header labels. Theme colors are resolved
|
||||
* internally via `useTheme()`. Only the current column's `<scrollbox>` receives
|
||||
* `focused`, so scroll focus follows the cursor (j/k stay in the current pane).
|
||||
* (static elements or accessors) plus the current-column title. Theme colors
|
||||
* are resolved internally via `useTheme()`. Only the current column's
|
||||
* `<scrollbox>` receives `focused`, so scroll focus follows the cursor (j/k
|
||||
* stay in the current pane).
|
||||
*
|
||||
* Example:
|
||||
* <PaneRow
|
||||
* parent={parentList}
|
||||
* current={currentList}
|
||||
* preview={detail}
|
||||
* parentLabel="Up"
|
||||
* currentLabel="List · 42"
|
||||
* previewLabel="Detail"
|
||||
* focused={isActive}
|
||||
* />
|
||||
*/
|
||||
@@ -52,9 +53,9 @@ export type PaneRowProps = {
|
||||
/** Preview column content (detail of the hovered item). Omit/undefined
|
||||
* together with `panes={2}` to render a 2-pane parent|current row. */
|
||||
preview?: PaneContent;
|
||||
parentLabel?: PaneLabel;
|
||||
/** Title of the current column — rendered once, top-left in the parent
|
||||
* pane's header slot (the per-pane Up/Detail headers are gone). */
|
||||
currentLabel?: PaneLabel;
|
||||
previewLabel?: PaneLabel;
|
||||
/** Whether the current column's `<scrollbox>` receives scroll focus. Defaults to
|
||||
* true; pass `false` (or a signal) when the row is inactive. Does NOT change
|
||||
* border colors — the current column's border is always muted. */
|
||||
@@ -116,23 +117,32 @@ function Pane(props: {
|
||||
flexBasis={0}
|
||||
height="100%"
|
||||
>
|
||||
{/* ── slim header label row ─────────────────────────────────────────── */}
|
||||
<box
|
||||
height={1}
|
||||
paddingLeft={1}
|
||||
backgroundColor={
|
||||
themeContext.transparentBackground()
|
||||
? "transparent"
|
||||
: theme.background
|
||||
}
|
||||
>
|
||||
<text fg={theme.textSecondary}>{props.label()}</text>
|
||||
</box>
|
||||
{/* ── bordered scrollbox ────────────────────────────────────────────── */}
|
||||
{/* ── title row: rendered only when the pane carries a label ────────── */}
|
||||
<Show when={props.label() !== ""}>
|
||||
<box
|
||||
height={1}
|
||||
paddingLeft={1}
|
||||
backgroundColor={
|
||||
themeContext.transparentBackground()
|
||||
? "transparent"
|
||||
: theme.background
|
||||
}
|
||||
>
|
||||
<text fg={theme.textSecondary}>{props.label()}</text>
|
||||
</box>
|
||||
</Show>
|
||||
{/* ── scrollbox; border always muted (focused or not) ──────────────── */}
|
||||
<scrollbox
|
||||
height="100%"
|
||||
focused={scrollFocused()}
|
||||
border={props.border}
|
||||
// Only supply colors when a border is requested — opentui flips a
|
||||
// borderless box to bordered when borderColor/focusedBorderColor
|
||||
// are passed, which would frame the parent/preview panes too.
|
||||
borderColor={props.border === false ? undefined : theme.border}
|
||||
focusedBorderColor={
|
||||
props.border === false ? undefined : theme.border
|
||||
}
|
||||
backgroundColor={
|
||||
themeContext.transparentBackground()
|
||||
? "transparent"
|
||||
@@ -159,9 +169,9 @@ export function PaneRow(props: PaneRowProps) {
|
||||
const currentContent = normalizeContent(props.current);
|
||||
const previewContent = normalizeContent(props.preview);
|
||||
|
||||
const parentLabel = createMemo(() => resolveLabel(props.parentLabel));
|
||||
// The single title: the CURRENT column's label, rendered in the parent
|
||||
// pane's header slot (top-left). Current/preview panes have no headers.
|
||||
const currentLabel = createMemo(() => resolveLabel(props.currentLabel));
|
||||
const previewLabel = createMemo(() => resolveLabel(props.previewLabel));
|
||||
|
||||
// 2-pane mode (parent|current) grows the current column to fill the
|
||||
// preview slot. Defaults to 3 (parent|current|preview).
|
||||
@@ -174,27 +184,27 @@ export function PaneRow(props: PaneRowProps) {
|
||||
|
||||
return (
|
||||
<box flexDirection="row" flexGrow={1} width="100%" height="100%">
|
||||
{/* ── parent (1/5) — previous-depth list; always muted ─────────────── */}
|
||||
{/* ── parent (1/5) — previous-depth list; title row top-left ───────── */}
|
||||
<Pane
|
||||
grow={PANE_RATIO.parent}
|
||||
label={parentLabel}
|
||||
label={currentLabel}
|
||||
content={parentContent}
|
||||
border={false}
|
||||
scrollFocused={() => false}
|
||||
/>
|
||||
{/* ── current — the focused list; no border, no highlight ─────────── */}
|
||||
{/* ── current — the focused list; left/right borders only ─────────── */}
|
||||
<Pane
|
||||
grow={currentGrow()}
|
||||
label={currentLabel}
|
||||
label={() => ""}
|
||||
content={currentContent}
|
||||
border={false}
|
||||
border={["left", "right"]}
|
||||
scrollFocused={() => focused()}
|
||||
/>
|
||||
{/* ── preview (2/5) — hovered-item detail; always muted ────────────── */}
|
||||
{/* ── preview (2/5) — hovered-item detail; no border, no header ────── */}
|
||||
<Show when={panes() === 3}>
|
||||
<Pane
|
||||
grow={PANE_RATIO.preview}
|
||||
label={previewLabel}
|
||||
label={() => ""}
|
||||
content={previewContent}
|
||||
border={false}
|
||||
scrollFocused={() => false}
|
||||
|
||||
@@ -23,20 +23,11 @@ import { useAppStore } from "@/stores/app";
|
||||
import { useToast } from "@/ui/toast";
|
||||
import { emit, on } from "@/utils/event-bus";
|
||||
import { LayerGraph } from "@/utils/layer-graph";
|
||||
import { TABS, TabPaneCount } from "@/utils/navigation";
|
||||
import { TABS } from "@/utils/navigation";
|
||||
import { createDispatcher } from "@/utils/dispatch";
|
||||
import { TabListPane } from "@/components/TabPanel";
|
||||
import { PaneRow } from "@/components/PaneRow";
|
||||
|
||||
const TAB_LABEL: Record<TABS, string> = {
|
||||
[TABS.FEED]: "Feed",
|
||||
[TABS.MYSHOWS]: "My Shows",
|
||||
[TABS.DISCOVER]: "Discover",
|
||||
[TABS.SEARCH]: "Search",
|
||||
[TABS.PLAYER]: "Player",
|
||||
[TABS.SETTINGS]: "Settings",
|
||||
};
|
||||
|
||||
export function Shell() {
|
||||
const theme = useTheme();
|
||||
const t = theme.theme;
|
||||
@@ -271,9 +262,7 @@ export function Shell() {
|
||||
<text fg={t.textMuted}>j/k move · l/Enter open a tab</text>
|
||||
</box>
|
||||
}
|
||||
parentLabel="Up"
|
||||
currentLabel="Tabs"
|
||||
previewLabel=""
|
||||
focused
|
||||
/>
|
||||
</Show>
|
||||
@@ -296,15 +285,6 @@ export function Shell() {
|
||||
<text fg={t.accent} paddingLeft={1}>
|
||||
{modeLabel()}
|
||||
</text>
|
||||
<text fg={t.textMuted} paddingLeft={1}>
|
||||
{nav.atRootTab()
|
||||
? "Tabs · root"
|
||||
: `${TAB_LABEL[nav.activeTab()]} · ${
|
||||
nav.isDepthTab()
|
||||
? `depth ${nav.currentDepth()}`
|
||||
: `pane ${nav.activePane()}/${TabPaneCount[nav.activeTab()]}`
|
||||
}`}
|
||||
</text>
|
||||
<Show when={nav.selectedIds().length > 0}>
|
||||
<text fg={t.warning} paddingLeft={1}>
|
||||
● {nav.selectedIds().length}
|
||||
|
||||
@@ -376,9 +376,7 @@ function DiscoverPage() {
|
||||
parent={parentContent}
|
||||
current={currentContent}
|
||||
preview={previewContent}
|
||||
parentLabel={() => (depth() >= 1 ? "Categories" : "Up")}
|
||||
currentLabel={currentLabel}
|
||||
previewLabel="Detail"
|
||||
focused={isActive}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -301,9 +301,7 @@ function FeedPage() {
|
||||
parent={parentContent}
|
||||
current={currentContent}
|
||||
preview={previewContent}
|
||||
parentLabel="Up"
|
||||
currentLabel={currentLabel}
|
||||
previewLabel="Detail"
|
||||
focused={isActive}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -432,9 +432,7 @@ export function MyShowsPage() {
|
||||
parent={parentContent}
|
||||
current={currentContent}
|
||||
preview={previewContent}
|
||||
parentLabel={() => (depth() >= 1 ? "Shows" : "Up")}
|
||||
currentLabel={currentLabel}
|
||||
previewLabel="Detail"
|
||||
focused={isActive}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -119,7 +119,6 @@ export function PlayerPage() {
|
||||
<PaneRow
|
||||
parent={parentContent}
|
||||
current={currentContent}
|
||||
parentLabel="Up"
|
||||
currentLabel="Player"
|
||||
panes={2}
|
||||
focused={isActive}
|
||||
|
||||
@@ -431,9 +431,7 @@ function SearchPage() {
|
||||
parent={parentContent}
|
||||
current={currentContent}
|
||||
preview={previewContent}
|
||||
parentLabel={() => (depth() >= 1 ? "Query" : "Up")}
|
||||
currentLabel={currentLabel}
|
||||
previewLabel="Detail"
|
||||
focused={isActive}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -267,12 +267,6 @@ export function SettingsPage() {
|
||||
if (d === 1) return sectionForDepth1()?.label ?? "Items";
|
||||
return editorItem()?.label ?? "Editor";
|
||||
};
|
||||
const parentLabel = () => {
|
||||
const d = depth();
|
||||
if (d === 1) return "Sections";
|
||||
if (d === 2) return sectionForDepth1()?.label ?? "";
|
||||
return "Up";
|
||||
};
|
||||
|
||||
// ── parent pane: previous-depth list (blank at depth 0) ────────────────
|
||||
// Sibling <Show> blocks per depth (mirrors the preview pane) so Solid
|
||||
@@ -384,9 +378,7 @@ export function SettingsPage() {
|
||||
parent={parentContent}
|
||||
current={currentContent}
|
||||
preview={previewContent}
|
||||
parentLabel={parentLabel}
|
||||
currentLabel={currentLabel}
|
||||
previewLabel="Detail"
|
||||
focused={isActive}
|
||||
/>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user