mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-27 11:42:52 +00:00
* feat(app): add AMOLED and all four Catppuccin themes Add an AMOLED theme (pure black background) and the four official Catppuccin flavors (Latte light, Frappé/Macchiato/Mocha dark) to the appearance picker. Generalize the dark-theme builder to support a light surface variant, register the new themes with Unistyles, and patch all registered theme keys when applying font/syntax appearance settings. * fix(app): localize Catppuccin + AMOLED theme labels across language packs The initial theme commit pasted the English brand/flavor labels into every locale. Localize them following each pack's convention: transliterate Catppuccin and the coffee-flavor names into Arabic/Russian scripts, use katakana/hanzi equivalents in ja/zh-CN, and standard loanwords in fr/es/pt-BR. AMOLED is a universal acronym and stays as-is except where the pack transliterates (Arabic). * fix(app): make Latte terminal ANSI colors readable * test(app): cover appearance theme picker options * refactor(app): scope theme PR to AMOLED * refactor(app): rename AMOLED theme to Pure Black * fix(app): refine Pure black appearance * test(app): wait for sidebar toggle icon * test(app): remove hover timing race --------- Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
209 lines
4.9 KiB
TypeScript
209 lines
4.9 KiB
TypeScript
import { useMemo, type ReactNode } from "react";
|
|
import {
|
|
ScrollView,
|
|
Text,
|
|
View,
|
|
type StyleProp,
|
|
type TextStyle,
|
|
type ViewStyle,
|
|
} from "react-native";
|
|
import { StyleSheet } from "react-native-unistyles";
|
|
|
|
import { isWeb } from "@/constants/platform";
|
|
import { CODE_SURFACE_DATASET } from "@/styles/code-surface";
|
|
import { inlineUnistylesStyle } from "@/styles/unistyles-inline-style";
|
|
|
|
type CodeSurfaceTone = "surface0" | "surface1" | "surface2";
|
|
|
|
interface ScrollableCodeSurfaceProps {
|
|
children: ReactNode;
|
|
maxHeight?: number;
|
|
horizontal?: boolean;
|
|
selectable?: boolean;
|
|
tone?: CodeSurfaceTone;
|
|
bordered?: boolean;
|
|
style?: StyleProp<ViewStyle>;
|
|
scrollStyle?: StyleProp<ViewStyle>;
|
|
contentContainerStyle?: StyleProp<ViewStyle>;
|
|
textStyle?: StyleProp<TextStyle>;
|
|
testID?: string;
|
|
accessibilityLabel?: string;
|
|
}
|
|
|
|
interface SurfaceCardProps {
|
|
children: ReactNode;
|
|
tone?: CodeSurfaceTone;
|
|
bordered?: boolean;
|
|
style?: StyleProp<ViewStyle>;
|
|
testID?: string;
|
|
accessibilityLabel?: string;
|
|
codeSurface?: boolean;
|
|
}
|
|
|
|
export function SurfaceCard({
|
|
children,
|
|
tone = "surface1",
|
|
bordered = true,
|
|
style,
|
|
testID,
|
|
accessibilityLabel,
|
|
codeSurface = false,
|
|
}: SurfaceCardProps) {
|
|
const surfaceStyle = getSurfaceStyle(tone);
|
|
const containerStyle = useMemo(
|
|
() => [styles.container, surfaceStyle, bordered && styles.bordered, style],
|
|
[bordered, style, surfaceStyle],
|
|
);
|
|
|
|
return (
|
|
<View
|
|
style={containerStyle}
|
|
dataSet={codeSurface ? CODE_SURFACE_DATASET : undefined}
|
|
testID={testID}
|
|
accessible={accessibilityLabel != null}
|
|
accessibilityLabel={accessibilityLabel}
|
|
>
|
|
{children}
|
|
</View>
|
|
);
|
|
}
|
|
|
|
export function ScrollableCodeSurface({
|
|
children,
|
|
maxHeight,
|
|
horizontal = true,
|
|
selectable = true,
|
|
tone = "surface1",
|
|
bordered = true,
|
|
style,
|
|
scrollStyle,
|
|
contentContainerStyle,
|
|
textStyle,
|
|
testID,
|
|
accessibilityLabel,
|
|
}: ScrollableCodeSurfaceProps) {
|
|
const maxHeightStyle = useMemo(
|
|
() => (maxHeight == null ? null : inlineUnistylesStyle({ maxHeight })),
|
|
[maxHeight],
|
|
);
|
|
const outerScrollStyle = useMemo(
|
|
() => [maxHeightStyle, scrollStyle],
|
|
[maxHeightStyle, scrollStyle],
|
|
);
|
|
const contentStyle = useMemo(
|
|
() => [styles.content, contentContainerStyle],
|
|
[contentContainerStyle],
|
|
);
|
|
const codeTextStyle = useMemo(() => [styles.text, textStyle], [textStyle]);
|
|
const codeContent =
|
|
typeof children === "string" ? (
|
|
<PlainCodeText text={children} selectable={selectable} textStyle={codeTextStyle} />
|
|
) : (
|
|
<Text selectable={selectable} style={codeTextStyle} dataSet={CODE_SURFACE_DATASET}>
|
|
{children}
|
|
</Text>
|
|
);
|
|
|
|
return (
|
|
<SurfaceCard
|
|
tone={tone}
|
|
bordered={bordered}
|
|
style={style}
|
|
testID={testID}
|
|
accessibilityLabel={accessibilityLabel}
|
|
codeSurface
|
|
>
|
|
<ScrollView
|
|
style={outerScrollStyle}
|
|
contentContainerStyle={contentStyle}
|
|
nestedScrollEnabled
|
|
showsVerticalScrollIndicator
|
|
>
|
|
{horizontal ? (
|
|
<ScrollView horizontal nestedScrollEnabled showsHorizontalScrollIndicator>
|
|
{codeContent}
|
|
</ScrollView>
|
|
) : (
|
|
codeContent
|
|
)}
|
|
</ScrollView>
|
|
</SurfaceCard>
|
|
);
|
|
}
|
|
|
|
function PlainCodeText({
|
|
text,
|
|
selectable,
|
|
textStyle,
|
|
}: {
|
|
text: string;
|
|
selectable: boolean;
|
|
textStyle: StyleProp<TextStyle>;
|
|
}) {
|
|
const lines = useMemo(() => {
|
|
let offset = 0;
|
|
return text.split("\n").map((line) => {
|
|
const key = `${offset}:${line.length}`;
|
|
offset += line.length + 1;
|
|
return { key, line };
|
|
});
|
|
}, [text]);
|
|
return (
|
|
<View dataSet={CODE_SURFACE_DATASET}>
|
|
{lines.map(({ key, line }) => (
|
|
<Text key={key} selectable={selectable} style={textStyle} dataSet={CODE_SURFACE_DATASET}>
|
|
{line.length === 0 ? "\u200B" : line}
|
|
</Text>
|
|
))}
|
|
</View>
|
|
);
|
|
}
|
|
|
|
function getSurfaceStyle(tone: CodeSurfaceTone): StyleProp<ViewStyle> {
|
|
switch (tone) {
|
|
case "surface0":
|
|
return styles.surface0;
|
|
case "surface2":
|
|
return styles.surface2;
|
|
case "surface1":
|
|
return styles.surface1;
|
|
}
|
|
}
|
|
|
|
const styles = StyleSheet.create((theme) => ({
|
|
container: {
|
|
overflow: "hidden",
|
|
},
|
|
bordered: {
|
|
borderRadius: theme.borderRadius.lg,
|
|
borderWidth: theme.borderWidth[1],
|
|
borderColor: theme.colors.border,
|
|
},
|
|
surface0: {
|
|
backgroundColor: theme.colors.surface0,
|
|
},
|
|
surface1: {
|
|
backgroundColor: theme.colors.surface1,
|
|
},
|
|
surface2: {
|
|
backgroundColor: theme.colors.surface2,
|
|
},
|
|
content: {
|
|
paddingVertical: theme.spacing[3],
|
|
paddingHorizontal: theme.spacing[4],
|
|
},
|
|
text: {
|
|
backgroundColor: "transparent",
|
|
fontFamily: theme.fontFamily.mono,
|
|
fontSize: theme.fontSize.code,
|
|
color: theme.colors.foreground,
|
|
lineHeight: 18,
|
|
...(isWeb
|
|
? {
|
|
whiteSpace: "pre",
|
|
overflowWrap: "normal",
|
|
}
|
|
: null),
|
|
},
|
|
}));
|