Files
paseo/packages/app/src/components/task-list-row.tsx
T
Mohamed Boudra 5ec26f0c09 feat(app): align interface typography to base size
Treat the appearance setting as the rendered base size so the type ramp scales predictably. Native starts one pixel larger while web and desktop preserve the authored 14px base, with legacy values migrated on load.
2026-08-17 22:55:46 +02:00

71 lines
2.5 KiB
TypeScript

import { Circle, CircleCheck, CircleDot } from "lucide-react-native";
import { memo } from "react";
import { Text, View } from "react-native";
import { StyleSheet, withUnistyles } from "react-native-unistyles";
import type { Theme } from "@/styles/theme";
import type { TodoEntry } from "@/types/stream";
const ThemedCircle = withUnistyles(Circle);
const ThemedCircleCheck = withUnistyles(CircleCheck);
const ThemedCircleDot = withUnistyles(CircleDot);
const extraMutedIcon = (theme: Theme) => ({ color: theme.colors.foregroundExtraMuted });
const runningIcon = (theme: Theme) => ({ color: theme.colors.statusDotRunning });
function TaskStatusIcon({ isCompleted, isRunning }: { isCompleted: boolean; isRunning: boolean }) {
if (isCompleted) {
return <ThemedCircleCheck size={16} uniProps={extraMutedIcon} />;
}
if (isRunning) {
return <ThemedCircleDot size={16} uniProps={runningIcon} />;
}
// A pending task's ring is a status mark, not a checkbox. At the muted step it carries the
// weight of an enabled control and invites a click that does nothing, so it sits one step back
// from the text it marks.
return <ThemedCircle size={16} uniProps={extraMutedIcon} />;
}
export const TaskListRow = memo(function TaskListRow({ task }: { task: TodoEntry }) {
const isCompleted = task.completed || task.status === "completed";
const isRunning = !isCompleted && task.status === "in_progress";
const text = isRunning && task.activeForm ? task.activeForm : task.text;
return (
<View style={styles.row} accessibilityLabel={text}>
<TaskStatusIcon isCompleted={isCompleted} isRunning={isRunning} />
<Text
numberOfLines={1}
style={[styles.text, isRunning && styles.runningText, isCompleted && styles.completedText]}
>
{text}
</Text>
</View>
);
});
const styles = StyleSheet.create((theme) => ({
row: {
flexDirection: "row",
alignItems: "center",
gap: theme.spacing[2],
},
// Grows and shrinks, but keeps an `auto` basis: a zero-basis label reports no intrinsic width,
// and a container that sizes itself to its content — the composer track panel — measures the
// row as empty and truncates it against a surface that had room to spare.
text: {
flexGrow: 1,
flexShrink: 1,
flexBasis: "auto",
minWidth: 0,
color: theme.colors.foregroundMuted,
fontSize: theme.fontSize.base,
},
runningText: {
color: theme.colors.foreground,
},
completedText: {
color: theme.colors.foregroundExtraMuted,
textDecorationLine: "line-through",
},
}));