feat: labeled loading spinners + [Fetch More] pagination on the feed list
Loading indicators: the braille spinner now carries a contextual label (Refreshing…, Fetching…, Loading more…, Discovering…, Searching…) and is shown in every loading state that previously rendered nothing — Discover results, Search results fallback, and the empty Feed list. Feed pagination: a focusable "[Fetch More]" row at the bottom of the flat feed list advances every feed's loaded window by 50 episodes via the new loadMoreAllFeeds/hasMoreAcrossAll store API. Behavior is a setting (Fetch More: manual|auto, default manual) persisted in config.json; auto fetches when focus reaches the bottom row. The button row is excluded from episode focus so no episode is double-highlighted while it is active.
This commit is contained in:
@@ -1,23 +1,30 @@
|
||||
import { createSignal, createMemo, onCleanup } from "solid-js";
|
||||
import { createSignal, createMemo, Show, onCleanup } from "solid-js";
|
||||
import { useTheme } from "@/context/ThemeContext";
|
||||
|
||||
const spinnerChars = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
||||
|
||||
export function LoadingIndicator() {
|
||||
const { theme } = useTheme();
|
||||
const [index, setIndex] = createSignal(0);
|
||||
/**
|
||||
* Animated braille spinner with an optional label (e.g. "Refreshing…").
|
||||
* The spinner is rendered in the theme primary color; the label in muted.
|
||||
*/
|
||||
export function LoadingIndicator(props: { label?: string }) {
|
||||
const { theme } = useTheme();
|
||||
const [index, setIndex] = createSignal(0);
|
||||
|
||||
const interval = setInterval(() => {
|
||||
setIndex((i) => (i + 1) % spinnerChars.length);
|
||||
}, 65);
|
||||
const interval = setInterval(() => {
|
||||
setIndex((i) => (i + 1) % spinnerChars.length);
|
||||
}, 65);
|
||||
|
||||
onCleanup(() => clearInterval(interval));
|
||||
onCleanup(() => clearInterval(interval));
|
||||
|
||||
const currentChar = createMemo(() => spinnerChars[index()]);
|
||||
const currentChar = createMemo(() => spinnerChars[index()]);
|
||||
|
||||
return (
|
||||
<box flexDirection="row" justifyContent="flex-end" alignItems="flex-start">
|
||||
<text fg={theme.primary} content={currentChar()} />
|
||||
</box>
|
||||
);
|
||||
return (
|
||||
<box flexDirection="row" gap={1} alignItems="flex-start">
|
||||
<text fg={theme.primary} content={currentChar()} />
|
||||
<Show when={props.label}>
|
||||
<text fg={theme.muted || theme.text} content={props.label} />
|
||||
</Show>
|
||||
</box>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user