UI: light Things-3 design system — tokens, task rows, chips, home

Replaces the dark navy palette with a light grouped-background system:
- theme.ts: full token set (space/radius/type/shadow) + light+dark palettes
- ui.tsx: Screen/Title/SectionHeader/Card/Separator/Pill/Empty primitives
- TaskRow: rounded-square checkbox carrying priority colour, inset separators
- TaskListScreen: rows grouped into a card, drag lift, refined deferred footer
- Icon.tsx: dependency-free geometric icon set (emoji don't take a tint)
- home: large title, Next-up card, "needs attention" rows, icon tab bar
- Priority/Estimate/Date chips unified on the accent-soft active state

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Claude
2026-09-01 22:36:26 +10:00
parent 65efb632db
commit 1af86cd27c
11 changed files with 863 additions and 217 deletions

View File

@@ -1,12 +1,115 @@
export const C = {
bg: "#0F1115",
surface: "#1A1D24",
surface2: "#232733",
border: "#2C313D",
text: "#E6E8EC",
muted: "#8A909C",
accent: "#5B8DEF",
done: "#4CAF7D",
danger: "#E05B5B",
warn: "#E0A93E",
// Design tokens.
//
// The look is modelled on Things 3: a light, calm canvas, generous whitespace,
// hairline inset separators instead of boxes, and exactly one accent colour
// used sparingly. Everything visual in the app resolves through this file —
// no component hard-codes a hex value (priority flags are the one exception,
// and they live in `priority.ts` alongside their labels).
/** Named palette. `dark` is complete and ready to wire to a theme toggle. */
const light = {
/** Screen background behind grouped content. */
bg: "#F7F7F9",
/** Card / row surface that sits on `bg`. */
surface: "#FFFFFF",
/** Inputs, pressed states, subtle fills. */
surface2: "#EFEFF3",
/** Card outline. Deliberately barely-there. */
border: "#E6E6EB",
/** Inset hairline between rows inside a card. */
separator: "#ECECF0",
/** Primary reading colour. Near-black, never pure #000. */
text: "#16161A",
/** Supporting copy that should still be comfortably readable. */
textSecondary: "#6E6E76",
/** Metadata, placeholders, de-emphasised chrome. */
muted: "#9A9AA2",
accent: "#2F6FED",
/** Tinted accent wash for selected chips and active states. */
accentSoft: "#E8F0FE",
done: "#2FA36B",
danger: "#E5484D",
warn: "#D98324",
/** Text that sits on top of `accent`. */
onAccent: "#FFFFFF",
} as const;
const dark = {
bg: "#0C0C0F",
surface: "#17171B",
surface2: "#212127",
border: "#2A2A31",
separator: "#26262C",
text: "#F2F2F5",
textSecondary: "#A6A6B0",
muted: "#7A7A85",
accent: "#4C86F0",
accentSoft: "#1B2740",
done: "#3FBF84",
danger: "#F0605F",
warn: "#E0A93E",
onAccent: "#FFFFFF",
} as const;
export const PALETTES = { light, dark } as const;
/**
* Active palette. Styles are created at module scope (StyleSheet.create) so
* this is a static choice rather than a hook — switching themes at runtime
* would mean moving every stylesheet inside its component.
*/
export const C = light;
/** 4pt spacing grid. Screen gutter is `space.lg`. */
export const space = {
xs: 4,
sm: 8,
md: 12,
lg: 16,
xl: 20,
xxl: 28,
} as const;
export const radius = {
sm: 8,
md: 12,
/** Cards and grouped lists. */
lg: 16,
/** Chips and pills. */
pill: 999,
} as const;
/**
* Type scale. `title` is the large in-content screen heading that replaces a
* cramped nav-bar title; `row` is the task title size — 17pt, matching the
* platform body size Things and Todoist both settle on.
*/
export const type = {
title: { fontSize: 30, fontWeight: "700", letterSpacing: -0.6 },
section: { fontSize: 13, fontWeight: "600", letterSpacing: 0.3 },
row: { fontSize: 17, letterSpacing: -0.2 },
body: { fontSize: 15 },
meta: { fontSize: 13 },
chip: { fontSize: 14, fontWeight: "500" },
} as const;
/**
* Card elevation. Kept extremely soft — a Things-like surface reads as paper,
* not as a floating panel.
*/
export const shadow = {
card: {
shadowColor: "#0B0B12",
shadowOpacity: 0.05,
shadowRadius: 10,
shadowOffset: { width: 0, height: 2 },
elevation: 1,
},
fab: {
shadowColor: "#0B0B12",
shadowOpacity: 0.22,
shadowRadius: 14,
shadowOffset: { width: 0, height: 6 },
elevation: 6,
},
} as const;