continued out the reuse

This commit is contained in:
2026-02-12 00:11:56 -05:00
parent 72000b362d
commit 276732d2a9
9 changed files with 145 additions and 49 deletions

View File

@@ -6,7 +6,11 @@ export function SelectableBox({
selected, selected,
children, children,
...props ...props
}: { selected: () => boolean; children: JSXElement } & BoxOptions) { }: {
selected: () => boolean;
children: JSXElement;
} & BoxOptions) {
const { theme } = useTheme(); const { theme } = useTheme();
return ( return (
@@ -21,18 +25,54 @@ export function SelectableBox({
); );
} }
enum ColorSet {
PRIMARY,
SECONDARY,
TERTIARY,
DEFAULT,
}
function getTextColor(set: ColorSet, selected: () => boolean) {
const { theme } = useTheme();
switch (set) {
case ColorSet.PRIMARY:
return selected() ? theme.textSelectedPrimary : theme.textPrimary;
case ColorSet.SECONDARY:
return selected() ? theme.textSelectedSecondary : theme.textSecondary;
case ColorSet.TERTIARY:
return selected() ? theme.textSelectedTertiary : theme.textTertiary;
default:
return theme.textPrimary;
}
}
export function SelectableText({ export function SelectableText({
selected, selected,
children, children,
primary,
secondary,
tertiary,
...props ...props
}: { }: {
selected: () => boolean; selected: () => boolean;
primary?: boolean;
secondary?: boolean;
tertiary?: boolean;
children: JSXElement; children: JSXElement;
} & TextOptions) { } & TextOptions) {
const { theme } = useTheme();
return ( return (
<text fg={selected() ? theme.surface : theme.text} {...props}> <text
fg={getTextColor(
primary
? ColorSet.PRIMARY
: secondary
? ColorSet.SECONDARY
: tertiary
? ColorSet.TERTIARY
: ColorSet.DEFAULT,
selected,
)}
{...props}
>
{children} {children}
</text> </text>
); );

View File

@@ -22,7 +22,7 @@ import {
type TerminalColors, type TerminalColors,
} from "@opentui/core"; } from "@opentui/core";
type ThemeResolved = { export type ThemeResolved = {
primary: RGBA; primary: RGBA;
secondary: RGBA; secondary: RGBA;
accent: RGBA; accent: RGBA;
@@ -32,7 +32,13 @@ type ThemeResolved = {
info: RGBA; info: RGBA;
text: RGBA; text: RGBA;
textMuted: RGBA; textMuted: RGBA;
selectedListItemText: RGBA; textPrimary: RGBA;
textSecondary: RGBA;
textTertiary: RGBA;
textSelectedPrimary: RGBA;
textSelectedSecondary: RGBA;
textSelectedTertiary: RGBA;
background: RGBA; background: RGBA;
backgroundPanel: RGBA; backgroundPanel: RGBA;
backgroundElement: RGBA; backgroundElement: RGBA;
@@ -77,6 +83,7 @@ type ThemeResolved = {
syntaxPunctuation: RGBA; syntaxPunctuation: RGBA;
muted?: RGBA; muted?: RGBA;
surface?: RGBA; surface?: RGBA;
selectedListItemText?: RGBA;
layerBackgrounds?: { layerBackgrounds?: {
layer0: RGBA; layer0: RGBA;
layer1: RGBA; layer1: RGBA;

View File

@@ -29,7 +29,7 @@ export function PodcastCard(props: PodcastCardProps) {
onMouseDown={props.onSelect} onMouseDown={props.onSelect}
> >
<box flexDirection="row" gap={2} alignItems="center"> <box flexDirection="row" gap={2} alignItems="center">
<SelectableText selected={() => props.selected}> <SelectableText selected={() => props.selected} primary>
<strong>{props.podcast.title}</strong> <strong>{props.podcast.title}</strong>
</SelectableText> </SelectableText>
@@ -42,7 +42,7 @@ export function PodcastCard(props: PodcastCardProps) {
<Show when={props.podcast.author && !props.compact}> <Show when={props.podcast.author && !props.compact}>
<SelectableText <SelectableText
selected={() => props.selected} selected={() => props.selected}
fg={theme.textMuted} tertiary
> >
by {props.podcast.author} by {props.podcast.author}
</SelectableText> </SelectableText>
@@ -52,7 +52,7 @@ export function PodcastCard(props: PodcastCardProps) {
<Show when={props.podcast.description && !props.compact}> <Show when={props.podcast.description && !props.compact}>
<SelectableText <SelectableText
selected={() => props.selected} selected={() => props.selected}
fg={theme.text} tertiary
> >
{props.podcast.description!.length > 80 {props.podcast.description!.length > 80
? props.podcast.description!.slice(0, 80) + "..." ? props.podcast.description!.slice(0, 80) + "..."

View File

@@ -54,26 +54,26 @@ export function FeedItem(props: FeedItemProps) {
> >
<SelectableText <SelectableText
selected={() => props.isSelected} selected={() => props.isSelected}
fg={theme.primary} primary
> >
{props.isSelected ? ">" : " "} {props.isSelected ? ">" : " "}
</SelectableText> </SelectableText>
<SelectableText <SelectableText
selected={() => props.isSelected} selected={() => props.isSelected}
fg={visibilityColor()} tertiary
> >
{visibilityIcon()} {visibilityIcon()}
</SelectableText> </SelectableText>
<SelectableText <SelectableText
selected={() => props.isSelected} selected={() => props.isSelected}
fg={theme.text} primary
> >
{props.feed.customName || props.feed.podcast.title} {props.feed.customName || props.feed.podcast.title}
</SelectableText> </SelectableText>
{props.showEpisodeCount && ( {props.showEpisodeCount && (
<SelectableText <SelectableText
selected={() => props.isSelected} selected={() => props.isSelected}
fg={theme.textMuted} tertiary
> >
({episodeCount()}) ({episodeCount()})
</SelectableText> </SelectableText>
@@ -95,25 +95,25 @@ export function FeedItem(props: FeedItemProps) {
<box flexDirection="row" gap={1}> <box flexDirection="row" gap={1}>
<SelectableText <SelectableText
selected={() => props.isSelected} selected={() => props.isSelected}
fg={theme.primary} primary
> >
{props.isSelected ? ">" : " "} {props.isSelected ? ">" : " "}
</SelectableText> </SelectableText>
<SelectableText <SelectableText
selected={() => props.isSelected} selected={() => props.isSelected}
fg={visibilityColor()} tertiary
> >
{visibilityIcon()} {visibilityIcon()}
</SelectableText> </SelectableText>
<SelectableText <SelectableText
selected={() => props.isSelected} selected={() => props.isSelected}
fg={theme.warning} secondary
> >
{pinnedIndicator()} {pinnedIndicator()}
</SelectableText> </SelectableText>
<SelectableText <SelectableText
selected={() => props.isSelected} selected={() => props.isSelected}
fg={theme.text} primary
> >
<strong>{props.feed.customName || props.feed.podcast.title}</strong> <strong>{props.feed.customName || props.feed.podcast.title}</strong>
</SelectableText> </SelectableText>
@@ -123,7 +123,7 @@ export function FeedItem(props: FeedItemProps) {
{props.showEpisodeCount && ( {props.showEpisodeCount && (
<SelectableText <SelectableText
selected={() => props.isSelected} selected={() => props.isSelected}
fg={theme.textMuted} tertiary
> >
{episodeCount()} episodes ({unplayedCount()} new) {episodeCount()} episodes ({unplayedCount()} new)
</SelectableText> </SelectableText>
@@ -131,7 +131,7 @@ export function FeedItem(props: FeedItemProps) {
{props.showLastUpdated && ( {props.showLastUpdated && (
<SelectableText <SelectableText
selected={() => props.isSelected} selected={() => props.isSelected}
fg={theme.textMuted} tertiary
> >
Updated: {formatDate(props.feed.lastUpdated)} Updated: {formatDate(props.feed.lastUpdated)}
</SelectableText> </SelectableText>
@@ -143,7 +143,7 @@ export function FeedItem(props: FeedItemProps) {
selected={() => props.isSelected} selected={() => props.isSelected}
paddingLeft={4} paddingLeft={4}
paddingTop={0} paddingTop={0}
fg={theme.textMuted} tertiary
> >
{props.feed.podcast.description.slice(0, 60)} {props.feed.podcast.description.slice(0, 60)}
{props.feed.podcast.description.length > 60 ? "..." : ""} {props.feed.podcast.description.length > 60 ? "..." : ""}

View File

@@ -80,9 +80,9 @@ export function FeedPage(props: PageProps) {
<For each={Object.entries(episodesByDate()).sort(([a], [b]) => b.localeCompare(a))}> <For each={Object.entries(episodesByDate()).sort(([a], [b]) => b.localeCompare(a))}>
{([date, episode], groupIndex) => ( {([date, episode], groupIndex) => (
<> <>
<box flexDirection="column" gap={0} paddingLeft={1} paddingRight={1} paddingTop={1} paddingBottom={1}> <box flexDirection="column" gap={0} paddingLeft={1} paddingRight={1} paddingTop={1} paddingBottom={1}>
<text fg={theme.primary}>{date}</text> <SelectableText selected={() => false} primary>{date}</SelectableText>
</box> </box>
<SelectableBox <SelectableBox
selected={() => groupIndex() === selectedIndex()} selected={() => groupIndex() === selectedIndex()}
flexDirection="column" flexDirection="column"
@@ -93,23 +93,25 @@ export function FeedPage(props: PageProps) {
paddingBottom={0} paddingBottom={0}
onMouseDown={() => setSelectedIndex(groupIndex())} onMouseDown={() => setSelectedIndex(groupIndex())}
> >
<SelectableText selected={() => groupIndex() === selectedIndex()}> <SelectableText selected={() => groupIndex() === selectedIndex()} primary>
{groupIndex() === selectedIndex() ? ">" : " "} {groupIndex() === selectedIndex() ? ">" : " "}
</SelectableText> </SelectableText>
<SelectableText <SelectableText
selected={() => groupIndex() === selectedIndex()} selected={() => groupIndex() === selectedIndex()}
fg={theme.text} primary
> >
{episode.episode.title} {episode.episode.title}
</SelectableText> </SelectableText>
<box flexDirection="row" gap={2} paddingLeft={2}> <box flexDirection="row" gap={2} paddingLeft={2}>
<text fg={theme.primary}>{episode.feed.podcast.title}</text> <SelectableText selected={() => groupIndex() === selectedIndex()} primary>
<text fg={theme.textMuted}> {episode.feed.podcast.title}
</SelectableText>
<SelectableText selected={() => groupIndex() === selectedIndex()} tertiary>
{formatDate(episode.episode.pubDate)} {formatDate(episode.episode.pubDate)}
</text> </SelectableText>
<text fg={theme.textMuted}> <SelectableText selected={() => groupIndex() === selectedIndex()} tertiary>
{formatDuration(episode.episode.duration)} {formatDuration(episode.episode.duration)}
</text> </SelectableText>
</box> </box>
</SelectableBox> </SelectableBox>
</> </>

View File

@@ -27,19 +27,19 @@ export function ResultCard(props: ResultCardProps) {
justifyContent="space-between" justifyContent="space-between"
alignItems="center" alignItems="center"
> >
<box flexDirection="row" gap={2} alignItems="center"> <box flexDirection="row" gap={2} alignItems="center">
<SelectableText <SelectableText
selected={() => props.selected} selected={() => props.selected}
fg={theme.primary} primary
> >
<strong>{podcast().title}</strong> <strong>{podcast().title}</strong>
</SelectableText> </SelectableText>
<SourceBadge <SourceBadge
sourceId={props.result.sourceId} sourceId={props.result.sourceId}
sourceName={props.result.sourceName} sourceName={props.result.sourceName}
sourceType={props.result.sourceType} sourceType={props.result.sourceType}
/> />
</box> </box>
<Show when={podcast().isSubscribed}> <Show when={podcast().isSubscribed}>
<text fg={theme.success}>[Subscribed]</text> <text fg={theme.success}>[Subscribed]</text>
</Show> </Show>

View File

@@ -16,10 +16,18 @@ export const BASE_THEME_COLORS: ThemeColors = {
secondary: "#a9b1d6", secondary: "#a9b1d6",
accent: "#f6c177", accent: "#f6c177",
text: "#e6edf3", text: "#e6edf3",
textPrimary: "#e6edf3",
textSecondary: "#a9b1d6",
textTertiary: "#7d8590",
textSelectedPrimary: "#1b1f27",
textSelectedSecondary: "#e6edf3",
textSelectedTertiary: "#a9b1d6",
muted: "#7d8590", muted: "#7d8590",
warning: "#f0b429", warning: "#f0b429",
error: "#f47067", error: "#f47067",
success: "#3fb950", success: "#3fb950",
_hasSelectedListItemText: true,
thinkingOpacity: 0.5,
} }
// Base layer backgrounds // Base layer backgrounds
@@ -61,16 +69,22 @@ export const THEMES_DESKTOP: DesktopTheme = {
secondary: "#cba6f7", secondary: "#cba6f7",
accent: "#f9e2af", accent: "#f9e2af",
text: "#cdd6f4", text: "#cdd6f4",
textPrimary: "#cdd6f4",
textSecondary: "#cba6f7",
textTertiary: "#7f849c",
textSelectedPrimary: "#1e1e2e",
textSelectedSecondary: "#cdd6f4",
textSelectedTertiary: "#cba6f7",
muted: "#7f849c", muted: "#7f849c",
warning: "#fab387", warning: "#fab387",
error: "#f38ba8", error: "#f38ba8",
success: "#a6e3a1", success: "#a6e3a1",
layerBackgrounds: { layerBackgrounds: {
layer0: "transparent", layer0: "transparent",
layer1: "#181825", layer1: "#181825",
layer2: "#11111b", layer2: "#11111b",
layer3: "#0a0a0f", layer3: "#0a0a0f",
}, },
}, },
}, },
{ {
@@ -82,6 +96,12 @@ export const THEMES_DESKTOP: DesktopTheme = {
secondary: "#83a598", secondary: "#83a598",
accent: "#fe8019", accent: "#fe8019",
text: "#ebdbb2", text: "#ebdbb2",
textPrimary: "#ebdbb2",
textSecondary: "#83a598",
textTertiary: "#928374",
textSelectedPrimary: "#282828",
textSelectedSecondary: "#ebdbb2",
textSelectedTertiary: "#83a598",
muted: "#928374", muted: "#928374",
warning: "#fabd2f", warning: "#fabd2f",
error: "#fb4934", error: "#fb4934",
@@ -103,6 +123,12 @@ export const THEMES_DESKTOP: DesktopTheme = {
secondary: "#bb9af7", secondary: "#bb9af7",
accent: "#e0af68", accent: "#e0af68",
text: "#c0caf5", text: "#c0caf5",
textPrimary: "#c0caf5",
textSecondary: "#bb9af7",
textTertiary: "#565f89",
textSelectedPrimary: "#1a1b26",
textSelectedSecondary: "#c0caf5",
textSelectedTertiary: "#bb9af7",
muted: "#565f89", muted: "#565f89",
warning: "#e0af68", warning: "#e0af68",
error: "#f7768e", error: "#f7768e",
@@ -124,6 +150,12 @@ export const THEMES_DESKTOP: DesktopTheme = {
secondary: "#81a1c1", secondary: "#81a1c1",
accent: "#ebcb8b", accent: "#ebcb8b",
text: "#eceff4", text: "#eceff4",
textPrimary: "#eceff4",
textSecondary: "#81a1c1",
textTertiary: "#4c566a",
textSelectedPrimary: "#2e3440",
textSelectedSecondary: "#eceff4",
textSelectedTertiary: "#81a1c1",
muted: "#4c566a", muted: "#4c566a",
warning: "#ebcb8b", warning: "#ebcb8b",
error: "#bf616a", error: "#bf616a",

View File

@@ -23,11 +23,20 @@ export type ThemeColors = {
secondary: ColorValue; secondary: ColorValue;
accent: ColorValue; accent: ColorValue;
text: ColorValue; text: ColorValue;
textPrimary?: ColorValue;
textSecondary?: ColorValue;
textTertiary?: ColorValue;
textSelectedPrimary?: ColorValue;
textSelectedSecondary?: ColorValue;
textSelectedTertiary?: ColorValue;
muted: ColorValue; muted: ColorValue;
warning: ColorValue; warning: ColorValue;
error: ColorValue; error: ColorValue;
success: ColorValue; success: ColorValue;
layerBackgrounds?: LayerBackgrounds; layerBackgrounds?: LayerBackgrounds;
_hasSelectedListItemText?: boolean;
thinkingOpacity?: number;
selectedListItemText?: ColorValue;
}; };
export type ThemeVariant = { export type ThemeVariant = {

View File

@@ -23,4 +23,10 @@ export type ThemeJson = {
export type ThemeColors = Record<string, RGBA> & { export type ThemeColors = Record<string, RGBA> & {
_hasSelectedListItemText: boolean _hasSelectedListItemText: boolean
thinkingOpacity: number thinkingOpacity: number
textPrimary?: ColorValue
textSecondary?: ColorValue
textTertiary?: ColorValue
textSelectedPrimary?: ColorValue
textSelectedSecondary?: ColorValue
textSelectedTertiary?: ColorValue
} }