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,
children,
...props
}: { selected: () => boolean; children: JSXElement } & BoxOptions) {
}: {
selected: () => boolean;
children: JSXElement;
} & BoxOptions) {
const { theme } = useTheme();
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({
selected,
children,
primary,
secondary,
tertiary,
...props
}: {
selected: () => boolean;
primary?: boolean;
secondary?: boolean;
tertiary?: boolean;
children: JSXElement;
} & TextOptions) {
const { theme } = useTheme();
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}
</text>
);

View File

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

View File

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

View File

@@ -54,26 +54,26 @@ export function FeedItem(props: FeedItemProps) {
>
<SelectableText
selected={() => props.isSelected}
fg={theme.primary}
primary
>
{props.isSelected ? ">" : " "}
</SelectableText>
<SelectableText
selected={() => props.isSelected}
fg={visibilityColor()}
tertiary
>
{visibilityIcon()}
</SelectableText>
<SelectableText
selected={() => props.isSelected}
fg={theme.text}
primary
>
{props.feed.customName || props.feed.podcast.title}
</SelectableText>
{props.showEpisodeCount && (
<SelectableText
selected={() => props.isSelected}
fg={theme.textMuted}
tertiary
>
({episodeCount()})
</SelectableText>
@@ -95,25 +95,25 @@ export function FeedItem(props: FeedItemProps) {
<box flexDirection="row" gap={1}>
<SelectableText
selected={() => props.isSelected}
fg={theme.primary}
primary
>
{props.isSelected ? ">" : " "}
</SelectableText>
<SelectableText
selected={() => props.isSelected}
fg={visibilityColor()}
tertiary
>
{visibilityIcon()}
</SelectableText>
<SelectableText
selected={() => props.isSelected}
fg={theme.warning}
secondary
>
{pinnedIndicator()}
</SelectableText>
<SelectableText
selected={() => props.isSelected}
fg={theme.text}
primary
>
<strong>{props.feed.customName || props.feed.podcast.title}</strong>
</SelectableText>
@@ -123,7 +123,7 @@ export function FeedItem(props: FeedItemProps) {
{props.showEpisodeCount && (
<SelectableText
selected={() => props.isSelected}
fg={theme.textMuted}
tertiary
>
{episodeCount()} episodes ({unplayedCount()} new)
</SelectableText>
@@ -131,7 +131,7 @@ export function FeedItem(props: FeedItemProps) {
{props.showLastUpdated && (
<SelectableText
selected={() => props.isSelected}
fg={theme.textMuted}
tertiary
>
Updated: {formatDate(props.feed.lastUpdated)}
</SelectableText>
@@ -143,7 +143,7 @@ export function FeedItem(props: FeedItemProps) {
selected={() => props.isSelected}
paddingLeft={4}
paddingTop={0}
fg={theme.textMuted}
tertiary
>
{props.feed.podcast.description.slice(0, 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))}>
{([date, episode], groupIndex) => (
<>
<box flexDirection="column" gap={0} paddingLeft={1} paddingRight={1} paddingTop={1} paddingBottom={1}>
<text fg={theme.primary}>{date}</text>
</box>
<box flexDirection="column" gap={0} paddingLeft={1} paddingRight={1} paddingTop={1} paddingBottom={1}>
<SelectableText selected={() => false} primary>{date}</SelectableText>
</box>
<SelectableBox
selected={() => groupIndex() === selectedIndex()}
flexDirection="column"
@@ -93,23 +93,25 @@ export function FeedPage(props: PageProps) {
paddingBottom={0}
onMouseDown={() => setSelectedIndex(groupIndex())}
>
<SelectableText selected={() => groupIndex() === selectedIndex()}>
<SelectableText selected={() => groupIndex() === selectedIndex()} primary>
{groupIndex() === selectedIndex() ? ">" : " "}
</SelectableText>
<SelectableText
selected={() => groupIndex() === selectedIndex()}
fg={theme.text}
primary
>
{episode.episode.title}
</SelectableText>
<box flexDirection="row" gap={2} paddingLeft={2}>
<text fg={theme.primary}>{episode.feed.podcast.title}</text>
<text fg={theme.textMuted}>
<SelectableText selected={() => groupIndex() === selectedIndex()} primary>
{episode.feed.podcast.title}
</SelectableText>
<SelectableText selected={() => groupIndex() === selectedIndex()} tertiary>
{formatDate(episode.episode.pubDate)}
</text>
<text fg={theme.textMuted}>
</SelectableText>
<SelectableText selected={() => groupIndex() === selectedIndex()} tertiary>
{formatDuration(episode.episode.duration)}
</text>
</SelectableText>
</box>
</SelectableBox>
</>

View File

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

View File

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

View File

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

View File

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