Files
paseo/packages/app/src/components/markdown/styles.ts
T
Mohamed Boudra b50acc9344 fix(app): restore assistant text selection in iPhone Safari (#7360)
The Markdown DOM container bypasses React Native Web style prefixing. Safari inherits the gesture host selection suppression unless the selectable boundary also sets WebkitUserSelect. Keep container styling in the existing style configuration so its selection contract is covered by the renderer tests.
2026-10-19 10:44:28 +07:00

105 lines
3.9 KiB
TypeScript

import type { MarkdownStyle } from "react-native-enriched-markdown";
import type { Theme } from "@/styles/theme";
import { isWeb } from "@/constants/platform";
// iOS Safari needs the prefix to override the gesture host's selection suppression.
const WEB_CONTAINER = { userSelect: "text", WebkitUserSelect: "text" } as const;
export function createMarkdownStyles(
theme: Theme,
{ compact = false, blockGap }: { compact?: boolean; blockGap: number },
): { markdownStyle: MarkdownStyle; selectionColor: string; containerStyle?: typeof WEB_CONTAINER } {
const fontSize = compact ? theme.fontSize.base : theme.fontSize.content;
// Every block is followed by the same gap; the library drops it after the last block.
const block = { marginTop: 0, marginBottom: blockGap };
const paragraph = {
fontSize,
fontFamily: theme.fontFamily.ui,
lineHeight: Math.round(fontSize * 1.4),
color: theme.colors.foreground,
...block,
};
const heading = (scale: number) => ({
...paragraph,
fontSize: Math.round(fontSize * scale),
lineHeight: Math.round(fontSize * scale * 1.3),
fontWeight: "bold",
});
return {
containerStyle: isWeb ? WEB_CONTAINER : undefined,
selectionColor: theme.colors.terminal.selectionBackground,
markdownStyle: {
paragraph,
h1: heading(1.6),
h2: heading(1.4),
h3: heading(1.2),
h4: heading(1.1),
h5: heading(1),
h6: { ...heading(1), color: theme.colors.foregroundMuted },
list: {
...paragraph,
bulletColor: theme.colors.foregroundMuted,
markerColor: theme.colors.foregroundMuted,
gapWidth: 7,
itemSpacing: 4,
marginLeft: 20,
},
blockquote: {
...paragraph,
color: `${theme.colors.foreground}cc`,
backgroundColor: theme.colors.surface1,
borderColor: theme.colors.surface2,
borderWidth: 3,
padding: 12,
gapWidth: 8,
admonitions: {
note: { color: theme.colors.accentBright, backgroundColor: theme.colors.surface2 },
tip: { color: theme.colors.statusSuccess, backgroundColor: theme.colors.surface2 },
important: { color: theme.colors.statusMerged, backgroundColor: theme.colors.surface2 },
warning: { color: theme.colors.statusWarning, backgroundColor: theme.colors.surface2 },
caution: { color: theme.colors.statusDanger, backgroundColor: theme.colors.surface2 },
},
},
link: { color: theme.colors.accentBright, underline: false },
linkVariants: {
"^(?!https?://).+": {
color: theme.colors.accentBright,
},
},
code: {
fontFamily: theme.fontFamily.mono === "ui-monospace" && !isWeb ? "" : theme.fontFamily.mono,
fontSize: theme.fontSize.code,
color: theme.colors.foreground,
backgroundColor: theme.colors.surface2,
borderColor: "transparent",
},
codeBlock: {
...paragraph,
fontFamily: theme.fontFamily.mono === "ui-monospace" && !isWeb ? "" : theme.fontFamily.mono,
fontSize: theme.fontSize.code,
backgroundColor: theme.colors.surface2,
borderColor: theme.colors.border,
borderRadius: 8,
padding: 12,
},
table: {
...paragraph,
borderColor: theme.colors.border,
headerBackgroundColor: theme.colors.surface2,
headerTextColor: theme.colors.foreground,
rowEvenBackgroundColor: theme.colors.surface0,
rowOddBackgroundColor: theme.colors.surface1,
},
taskList: {
checkedTextColor: theme.colors.foregroundMuted,
checkedColor: theme.colors.accent,
checkmarkColor: theme.colors.accentForeground,
borderColor: theme.colors.foregroundMuted,
},
inlineImage: { size: paragraph.lineHeight },
image: { ...block, height: 320, resizeMode: "contain", borderRadius: 8 },
thematicBreak: { ...block, color: theme.colors.border, height: 1 },
},
};
}