refactor(ui): one cardEdges helper for the three list card stacks

This commit is contained in:
2026-09-01 22:57:13 +10:00
parent d951971d59
commit 8019e7c4f0
4 changed files with 25 additions and 42 deletions

View File

@@ -2,10 +2,10 @@ import { Stack, useLocalSearchParams, useRouter } from "expo-router";
import { useState } from "react";
import { FlatList, Pressable, StyleSheet, Text, TextInput, View } from "react-native";
import Icon from "../../components/Icon";
import { Button, Chips, Empty, Separator } from "../../components/ui";
import { Button, cardEdges, Chips, Empty, Separator } from "../../components/ui";
import { haptic } from "../../lib/haptics";
import { createTask, dropProject, updateProject, updateTask } from "../../lib/store";
import { C, radius, shadow, space, type } from "../../lib/theme";
import { C, radius, space, type } from "../../lib/theme";
import { useStore } from "../../lib/useStore";
import type { ProjectStatus } from "../../lib/types";
@@ -60,8 +60,7 @@ export default function ProjectDetail() {
<Pressable
style={({ pressed }) => [
s.row,
index === 0 && s.first,
index === tasks.length - 1 && s.last,
...cardEdges(index, tasks.length),
pressed && { backgroundColor: C.surface2 },
]}
onPress={() => router.push(`/task/${item.id}`)}
@@ -122,13 +121,6 @@ const s = StyleSheet.create({
gap: space.md,
paddingVertical: space.md + 2,
paddingHorizontal: space.lg,
backgroundColor: C.surface,
},
first: { borderTopLeftRadius: radius.lg, borderTopRightRadius: radius.lg },
last: {
borderBottomLeftRadius: radius.lg,
borderBottomRightRadius: radius.lg,
...shadow.card,
},
checkbox: { width: 21, height: 21, borderRadius: 7, borderWidth: 1.5, borderColor: C.muted },
title: { ...type.row, color: C.text },

View File

@@ -1,8 +1,8 @@
import { SectionList, StyleSheet, View } from "react-native";
import TaskRow, { TaskSeparator } from "../components/TaskRow";
import { Empty, SectionHeader } from "../components/ui";
import { cardEdges, Empty, SectionHeader } from "../components/ui";
import { todayTasks } from "../lib/store";
import { C, radius, shadow, space } from "../lib/theme";
import { C, space } from "../lib/theme";
import { useStore } from "../lib/useStore";
export default function Today() {
@@ -22,13 +22,7 @@ export default function Today() {
// Each section is its own rounded card, so the first and last rows in a
// section round their outer corners.
renderItem={({ item, index, section }) => (
<View
style={[
s.row,
index === 0 && s.first,
index === section.data.length - 1 && s.last,
]}
>
<View style={cardEdges(index, section.data.length)}>
<TaskRow task={item} showStatus />
</View>
)}
@@ -47,11 +41,4 @@ export default function Today() {
const s = StyleSheet.create({
screen: { flex: 1, backgroundColor: C.bg },
content: { paddingHorizontal: space.lg, paddingBottom: 40 },
row: { backgroundColor: C.surface },
first: { borderTopLeftRadius: radius.lg, borderTopRightRadius: radius.lg },
last: {
borderBottomLeftRadius: radius.lg,
borderBottomRightRadius: radius.lg,
...shadow.card,
},
});

View File

@@ -7,6 +7,7 @@ import { C, radius, shadow, space, type } from "../lib/theme";
import { useStore } from "../lib/useStore";
import type { TaskStatus } from "../lib/types";
import TaskRow, { TaskSeparator } from "./TaskRow";
import { cardEdges } from "./ui";
// Deferred tasks are hidden from these lists until their date — you can't act
// on them yet, so they shouldn't cost attention. A one-line footer keeps them
@@ -57,14 +58,7 @@ export default function TaskListScreen({
const i = getIndex() ?? 0;
return (
<ScaleDecorator activeScale={1.02}>
<View
style={[
s.row,
i === 0 && s.first,
i === tasks.length - 1 && s.last,
isActive && s.lifted,
]}
>
<View style={[...cardEdges(i, tasks.length), isActive && s.lifted]}>
<TaskRow
task={item}
uncheckTo={status === "done" ? "next" : status}
@@ -108,13 +102,6 @@ export default function TaskListScreen({
const s = StyleSheet.create({
screen: { flex: 1, backgroundColor: C.bg },
content: { paddingHorizontal: space.lg, paddingTop: space.md, paddingBottom: 96 },
row: { backgroundColor: C.surface },
first: { borderTopLeftRadius: radius.lg, borderTopRightRadius: radius.lg },
last: {
borderBottomLeftRadius: radius.lg,
borderBottomRightRadius: radius.lg,
...shadow.card,
},
lifted: { borderRadius: radius.lg, ...shadow.fab },
empty: {
...type.body,

View File

@@ -36,6 +36,15 @@ export function Card({ children, style }: { children: React.ReactNode; style?: V
return <View style={[u.card, style]}>{children}</View>;
}
/**
* Row background plus rounded outer corners for the first and last row of a
* list, so a plain FlatList reads as one card without wrapping it in a `Card`
* (which would break drag-and-drop and section headers).
*/
export function cardEdges(index: number, count: number) {
return [u.cardRow, index === 0 && u.cardTop, index === count - 1 && u.cardBottom];
}
/** Inset hairline for use between rows inside a `Card`. */
export function Separator({ inset = 0 }: { inset?: number }) {
return <View style={[u.separator, { marginLeft: inset }]} />;
@@ -165,6 +174,14 @@ export function Button({
const u = StyleSheet.create({
screen: { flex: 1, backgroundColor: C.bg },
cardRow: { backgroundColor: C.surface },
cardTop: { borderTopLeftRadius: radius.lg, borderTopRightRadius: radius.lg },
cardBottom: {
borderBottomLeftRadius: radius.lg,
borderBottomRightRadius: radius.lg,
...shadow.card,
},
titleRow: {
flexDirection: "row",
alignItems: "center",