ui cleanup
This commit is contained in:
@@ -19,7 +19,6 @@ import { useNavigation, NavMode } from "@/context/NavigationContext";
|
||||
import { useAudio } from "@/hooks/useAudio";
|
||||
import { useAudioNavStore, AudioSource } from "@/stores/audio-nav";
|
||||
import { useFeedStore } from "@/stores/feed";
|
||||
import type { Episode } from "@/types/episode";
|
||||
import { useToast } from "@/ui/toast";
|
||||
import { emit } from "@/utils/event-bus";
|
||||
import { LayerGraph } from "@/utils/layer-graph";
|
||||
@@ -200,8 +199,16 @@ export function Shell() {
|
||||
|
||||
useKeyboard(
|
||||
(evt: any) => {
|
||||
// Input fields (search boxes, dialogs) own their keys.
|
||||
if (nav.inputFocused() && nav.mode() !== NavMode.COMMAND) return;
|
||||
// Input fields (search boxes, dialogs) own their keys — except Escape,
|
||||
// which defocuses the input so j/k/h navigation resumes (search: h back
|
||||
// to the tab root, j/k to move the recent-searches list).
|
||||
if (nav.inputFocused() && nav.mode() !== NavMode.COMMAND) {
|
||||
if (evt.name === "escape") {
|
||||
evt.preventDefault();
|
||||
nav.setInputFocused(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (nav.mode() === NavMode.COMMAND) {
|
||||
handleCommandKey(evt);
|
||||
return;
|
||||
@@ -460,8 +467,9 @@ export function playEpisodeAndSwitch(
|
||||
) {
|
||||
audio.play(episode);
|
||||
nav.setActiveTab(TABS.PLAYER);
|
||||
nav.enterTabContent(); // PLAYER is a depth-tab — drop into its content pane.
|
||||
useAudioNavStore().setSource(AudioSource.FEED);
|
||||
}
|
||||
|
||||
// Re-export Episode type for callers building pane trees.
|
||||
export type { Episode };
|
||||
export type { Episode } from "@/types/episode";
|
||||
|
||||
@@ -2,12 +2,17 @@
|
||||
* TabListPane — the tab list as a pane you can drop into the UP | CURRENT |
|
||||
* PREVIEW flow (replaces the old fixed chrome tab column).
|
||||
*
|
||||
* Renders one row per tab (digit + label): the ACTIVE tab gets a ● marker and
|
||||
* accent fg; the CURSOR row (the one j/k hovers) gets the primary highlight.
|
||||
* `focused` only matters to the surrounding frame (the CURRENT column draws
|
||||
* its own accent ring in YaziPaneRow); when rendered as the muted UP/parent
|
||||
* column (`muted`), the cursor highlight is suppressed and only the active ●
|
||||
* shows, so it reads as the read-only parent listing.
|
||||
* Renders one row per tab (digit + label) using the same selection UI every
|
||||
* other yazi pane uses: the CURSOR row (the one j/k hovers) gets a `❯` marker
|
||||
* and the focus background (`theme.primary` when this pane is the CURRENT
|
||||
* column, `theme.border` when it is the muted UP/parent column). The ACTIVE
|
||||
* tab (the one whose content is open) always carries a `●` marker in accent so
|
||||
* it stays readable in both positions.
|
||||
*
|
||||
* `muted` marks the parent-column rendering: the highlight is dimmed (border
|
||||
* bg, text fg) rather than suppressed, so the Up pane still shows the cursor
|
||||
* and active tab — matching how every other pane's parent column renders its
|
||||
* focused row.
|
||||
*/
|
||||
|
||||
import { For } from "solid-js";
|
||||
@@ -34,18 +39,32 @@ export function TabListPane(props: { muted?: boolean }) {
|
||||
const nav = useNavigation();
|
||||
|
||||
const cursor = () => nav.tabCursor();
|
||||
const active = () => nav.activeTab();
|
||||
const muted = () => props.muted ?? false;
|
||||
const activeTab = () => nav.activeTab();
|
||||
/** `active=true` when this pane is the CURRENT column (Shell root);
|
||||
* `false` when it is the muted UP/parent column (pages' parent pane). */
|
||||
const active = () => !props.muted;
|
||||
|
||||
// Same focus-bg / focus-fg contract every other pane uses.
|
||||
const focusBg = (t: TABS) =>
|
||||
t === cursor() && active()
|
||||
? theme.primary
|
||||
: t === cursor()
|
||||
? theme.border
|
||||
: undefined;
|
||||
const focusFg = (t: TABS) =>
|
||||
t === cursor() && active() ? theme.surface : theme.text;
|
||||
|
||||
return (
|
||||
<For each={TAB_ORDER}>
|
||||
{(tab) => {
|
||||
const isCursor = () => cursor() === tab && !muted();
|
||||
const isActive = () => active() === tab;
|
||||
const fg = () =>
|
||||
const isCursor = () => cursor() === tab;
|
||||
const isActive = () => activeTab() === tab;
|
||||
// The active tab is only accented in the Up/parent position — when this
|
||||
// pane is CURRENT, the cursor highlight is the only highlight.
|
||||
const labelFg = () =>
|
||||
isCursor()
|
||||
? theme.textSelectedPrimary
|
||||
: isActive()
|
||||
? focusFg(tab)
|
||||
: isActive() && !active()
|
||||
? theme.accent
|
||||
: theme.text;
|
||||
return (
|
||||
@@ -53,21 +72,13 @@ export function TabListPane(props: { muted?: boolean }) {
|
||||
width="100%"
|
||||
height={1}
|
||||
flexDirection="row"
|
||||
backgroundColor={isCursor() ? theme.primary : "transparent"}
|
||||
paddingRight={1}
|
||||
backgroundColor={focusBg(tab)}
|
||||
>
|
||||
<text
|
||||
width={2}
|
||||
fg={isCursor() ? theme.textSelectedPrimary : "transparent"}
|
||||
>
|
||||
{isActive() ? "●" : " "}
|
||||
</text>
|
||||
<text
|
||||
width={2}
|
||||
fg={isCursor() ? theme.textSelectedPrimary : theme.textMuted}
|
||||
>
|
||||
{tab}
|
||||
</text>
|
||||
<text fg={fg()} paddingLeft={1}>
|
||||
{/* ── selection marker (j/k cursor) ─────────────────────────── */}
|
||||
<text fg={focusFg(tab)}>{isCursor() ? "❯" : " "}</text>
|
||||
<text fg={isCursor() ? focusFg(tab) : theme.textMuted}>{tab}</text>
|
||||
<text fg={labelFg()} paddingLeft={1}>
|
||||
{TAB_LABEL[tab]}
|
||||
</text>
|
||||
</box>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
* parent — the previous-depth list. Renders a muted `—` placeholder and
|
||||
* KEEPS its 1/7 slot when blank (never collapses to width 0).
|
||||
* current — the current-depth list. The only focusable content column; it
|
||||
* carries the accent focus ring when `focused` is truthy.
|
||||
* carries the active-border focus ring when `focused` is truthy.
|
||||
* preview — detail of the hovered item in `current`; always muted border.
|
||||
*
|
||||
* The primitive is purely structural: callers pass their own JSX per column
|
||||
@@ -31,7 +31,7 @@
|
||||
* />
|
||||
*/
|
||||
|
||||
import { createMemo } from "solid-js";
|
||||
import { createMemo, Show } from "solid-js";
|
||||
import type { JSX } from "solid-js";
|
||||
import type { RGBA } from "@opentui/core";
|
||||
import { useTheme } from "@/context/ThemeContext";
|
||||
@@ -47,15 +47,19 @@ export type YaziPaneRowProps = {
|
||||
parent?: PaneContent;
|
||||
/** Current column content (the focused list). */
|
||||
current?: PaneContent;
|
||||
/** Preview column content (detail of the hovered item). */
|
||||
/** 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;
|
||||
currentLabel?: PaneLabel;
|
||||
previewLabel?: PaneLabel;
|
||||
/** Whether the current column carries the accent focus ring. Defaults to
|
||||
/** Whether the current column carries the active-border focus ring. Defaults to
|
||||
* true; pass `false` (or a signal) when the row is inactive. Parent and
|
||||
* preview columns always render muted borders. */
|
||||
focused?: boolean | (() => boolean);
|
||||
/** Number of visible columns. `3` (default) = parent|current|preview;
|
||||
* `2` = parent|current (preview omitted, current grows to fill). */
|
||||
panes?: 2 | 3;
|
||||
};
|
||||
|
||||
// ── Helpers ─────────────────────────────────────────────────────────────────
|
||||
@@ -107,7 +111,12 @@ function YaziPane(props: {
|
||||
const scrollFocused = createMemo(() => props.scrollFocused());
|
||||
|
||||
return (
|
||||
<box flexDirection="column" flexGrow={props.grow} flexBasis={0} height="100%">
|
||||
<box
|
||||
flexDirection="column"
|
||||
flexGrow={props.grow}
|
||||
flexBasis={0}
|
||||
height="100%"
|
||||
>
|
||||
{/* ── slim header label row ─────────────────────────────────────────── */}
|
||||
<box height={1} paddingLeft={1} backgroundColor={theme.background}>
|
||||
<text fg={theme.textSecondary}>{props.label()}</text>
|
||||
@@ -143,10 +152,10 @@ function YaziPane(props: {
|
||||
export function YaziPaneRow(props: YaziPaneRowProps) {
|
||||
const { theme } = useTheme();
|
||||
|
||||
/** true → the current column gets the accent focus ring. */
|
||||
/** true → the current column gets the active-border focus ring. */
|
||||
const focused = createMemo(() => {
|
||||
const f = props.focused;
|
||||
return typeof f === "function" ? f() : f ?? true;
|
||||
return typeof f === "function" ? f() : (f ?? true);
|
||||
});
|
||||
|
||||
// Normalize static JSX and accessor children into reactive accessors
|
||||
@@ -159,6 +168,15 @@ export function YaziPaneRow(props: YaziPaneRowProps) {
|
||||
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).
|
||||
const panes = createMemo(() => props.panes ?? 3);
|
||||
const currentGrow = createMemo(() =>
|
||||
panes() === 2
|
||||
? PANE_RATIO.current + PANE_RATIO.preview
|
||||
: PANE_RATIO.current,
|
||||
);
|
||||
|
||||
return (
|
||||
<box flexDirection="row" flexGrow={1} width="100%" height="100%">
|
||||
{/* ── parent (1/7) — previous-depth list; always muted ─────────────── */}
|
||||
@@ -169,22 +187,24 @@ export function YaziPaneRow(props: YaziPaneRowProps) {
|
||||
borderColor={() => theme.border}
|
||||
scrollFocused={() => false}
|
||||
/>
|
||||
{/* ── current (3/7) — the focused list; accent ring when focused ───── */}
|
||||
{/* ── current — the focused list; active-border ring when focused ──────────── */}
|
||||
<YaziPane
|
||||
grow={PANE_RATIO.current}
|
||||
grow={currentGrow()}
|
||||
label={currentLabel}
|
||||
content={currentContent}
|
||||
borderColor={() => (focused() ? theme.accent : theme.border)}
|
||||
borderColor={() => (focused() ? theme.borderActive : theme.border)}
|
||||
scrollFocused={() => focused()}
|
||||
/>
|
||||
{/* ── preview (3/7) — hovered-item detail; always muted ────────────── */}
|
||||
<YaziPane
|
||||
grow={PANE_RATIO.preview}
|
||||
label={previewLabel}
|
||||
content={previewContent}
|
||||
borderColor={() => theme.border}
|
||||
scrollFocused={() => false}
|
||||
/>
|
||||
<Show when={panes() === 3}>
|
||||
<YaziPane
|
||||
grow={PANE_RATIO.preview}
|
||||
label={previewLabel}
|
||||
content={previewContent}
|
||||
borderColor={() => theme.border}
|
||||
scrollFocused={() => false}
|
||||
/>
|
||||
</Show>
|
||||
</box>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user