From 63e1573dae2dce64d1ab872447071460f0bbad27 Mon Sep 17 00:00:00 2001 From: syntaxbullet Date: Sat, 5 Sep 2026 08:11:00 +0200 Subject: [PATCH] Give each habit completion type a distinct layout --- src/components/design-system/HabitChart.tsx | 37 +++++++++-------- styles/design-system.css | 44 +++++++++++++++------ 2 files changed, 52 insertions(+), 29 deletions(-) diff --git a/src/components/design-system/HabitChart.tsx b/src/components/design-system/HabitChart.tsx index aafbc96..6900603 100644 --- a/src/components/design-system/HabitChart.tsx +++ b/src/components/design-system/HabitChart.tsx @@ -3,7 +3,7 @@ import { ChevronDown, ListChecks } from "lucide-react"; import { CalendarHeatmap } from "./CalendarHeatmap"; import { demoCalendar } from "./calendar-model"; -/** Open chart section: intentionally no card surface or enclosing border. */ +/** Completion controls define each goal surface; history stays a separate full-width disclosure. */ export function HabitChart({ name, method, @@ -47,22 +47,27 @@ export function HabitChart({ const Heading = `h${headingLevel}` as const; if (trackingMethod) return (
-
-
- {trackingMethod === "manual" && children} - - {trackingMethod === "manual" ? : name} - +
+
+
+
+ {trackingMethod === "manual" && children} + + {trackingMethod === "manual" ? : name} + +
+ {headingActions} +
+ {(!due || schedule !== "Every day") &&

{due ? schedule : `Not scheduled today · ${schedule}`}

} + {trackingMethod === "tasks" && due && target > 0 &&
+ + {value} / {target} +
}
- {headingActions} -
- {(!due || schedule !== "Every day") &&

{due ? schedule : `Not scheduled today · ${schedule}`}

} - {editor} - {trackingMethod === "count" &&
{children}{unit}
} - {trackingMethod === "tasks" &&
- {due && target > 0 &&

{value} / {target} completed

} - {tasks} -
} + {trackingMethod === "count" &&
{children}{unit}
} + {editor} + {trackingMethod === "tasks" &&
{tasks}
} + {calendar}
); diff --git a/styles/design-system.css b/styles/design-system.css index 689639c..c9944fa 100644 --- a/styles/design-system.css +++ b/styles/design-system.css @@ -1591,33 +1591,51 @@ /* Each goal puts its completion control before secondary history. */ .ds-visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; } -.ds-habit-list > .ds-goal { display: flex; flex-direction: column; align-items: stretch; gap: 0; padding: 24px 0; } +.ds-habit-list > .ds-goal { display: flex; flex-direction: column; align-items: stretch; gap: 0; padding: 20px 0; } +.ds-goal-body { min-width: 0; } +.ds-goal-overview { min-width: 0; } .ds-goal-heading { display: flex; align-items: center; justify-content: space-between; gap: 16px; } .ds-goal-title { display: flex; align-items: center; gap: 12px; min-width: 0; } -.ds-goal-title h3 { overflow-wrap: anywhere; min-width: 0; } +.ds-goal-title :is(h3, h4) { overflow-wrap: anywhere; min-width: 0; } .ds-goal-title label { cursor: pointer; } .ds-goal-title > .ds-checkbox { flex: 0 0 44px; justify-content: center; gap: 0; } -.ds-goal--manual .ds-goal-title:has(input:checked) h3 { color: var(--ds-secondary); } -.ds-goal-schedule { @apply type-small; color: var(--ds-secondary); margin-top: 4px; } +.ds-goal--manual .ds-goal-body { padding: 4px 0; } +.ds-goal--manual .ds-goal-title:has(input:checked) :is(h3, h4) { color: var(--ds-secondary); } +.ds-goal--manual .ds-goal-title input { width: 26px; height: 26px; } +.ds-goal--manual .ds-goal-title input:checked { background: var(--habit-color); border-color: var(--habit-color); } +.ds-goal-schedule { @apply type-small; color: var(--ds-secondary); margin-top: 6px; } .ds-goal--manual .ds-goal-schedule { padding-left: 56px; } -.ds-goal-count { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin-top: 16px; } -.ds-goal-count .ds-counter { padding: 6px; gap: 8px; background: var(--ds-surface); } -.ds-goal-count .ds-counter-input { @apply type-title; width: 5ch; min-height: 52px; background: var(--ds-paper); } -.ds-goal-count .ds-counter .ds-button { min-height: 48px; min-width: 48px; } -.ds-goal-unit { @apply type-body; color: var(--ds-secondary); overflow-wrap: anywhere; } -.ds-habit-list .ds-goal .ds-habit-history { align-self: flex-end; margin-top: 8px; } +.ds-goal--count .ds-goal-body { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, auto); align-items: center; gap: 24px; padding: 24px; border-radius: 8px; border: 1px solid color-mix(in srgb, var(--habit-color) 20%, var(--ds-rule)); background: color-mix(in srgb, var(--habit-color) 5%, var(--ds-paper)); } +.ds-goal--count .ds-goal-body > .ds-inline-editor { grid-column: 1 / -1; } +.ds-goal-count { display: flex; flex-direction: column; align-items: center; gap: 8px; min-width: 0; } +.ds-goal-count .ds-counter { justify-content: center; padding: 6px; gap: 6px; background: var(--ds-paper); border-color: color-mix(in srgb, var(--habit-color) 20%, var(--ds-rule)); } +.ds-goal-count .ds-counter-input { font-size: 36px; line-height: 1.2; width: 4ch; min-height: 60px; background: transparent; } +.ds-goal-count .ds-counter .ds-button { min-height: 44px; min-width: 44px; } +.ds-goal-count .ds-counter > .ds-muted { font-size: 18px; padding-right: 0; } +.ds-goal-unit { @apply type-small; color: var(--ds-secondary); overflow-wrap: anywhere; text-align: center; } +.ds-habit-list .ds-goal .ds-habit-history { align-self: flex-end; margin-top: 10px; } .ds-habit-list .ds-goal .ds-habit-history:has([aria-expanded="true"]) { align-self: stretch; } -.ds-goal-task-progress { @apply type-small; color: var(--ds-secondary); margin: 8px 0; } +.ds-goal--tasks .ds-goal-body { border: 1px solid var(--ds-rule); border-radius: 8px; overflow: clip; } +.ds-goal--tasks .ds-goal-overview { padding: 20px 24px 16px; background: var(--ds-surface); border-bottom: 1px solid var(--ds-rule); } +.ds-goal-task-progress { display: flex; align-items: center; gap: 12px; color: var(--ds-secondary); margin-top: 12px; @apply type-small; font-variant-numeric: tabular-nums; } +.ds-goal-task-progress progress { appearance: none; width: 100px; height: 4px; border: 0; border-radius: 4px; overflow: hidden; background: var(--ds-rule); accent-color: var(--habit-color); } +.ds-goal-task-progress progress::-webkit-progress-bar { background: var(--ds-rule); } +.ds-goal-task-progress progress::-webkit-progress-value { background: var(--habit-color); } +.ds-goal-task-progress progress::-moz-progress-bar { background: var(--habit-color); } +.ds-goal-tasks { padding: 4px 16px 8px; } .ds-routine-items { list-style: none; padding: 0; margin: 0; } .ds-routine-items .ds-editable-task:first-child { border-top: 0; } -.ds-routine-items .ds-editable-task-row { padding: 4px 8px; gap: 12px; } +.ds-routine-items .ds-editable-task-row { padding: 4px 8px; gap: 12px; border-radius: 4px; } .ds-routine-items .ds-editable-task-row > .ds-checkbox { min-height: 48px; } .ds-routine-items .ds-editable-task-row:has(input:checked) .ds-checkbox-label { color: var(--ds-secondary); text-decoration: line-through; text-decoration-color: var(--ds-rule); } .ds-routine-items .ds-editable-task-row:hover { background: var(--ds-surface); } -.ds-goal-tasks .ds-task-add-action { padding-bottom: 0; } +.ds-goal-tasks .ds-task-add-action { padding: 8px; border-top: 1px solid var(--ds-rule); } .ds-habit-starter-options { margin-top: 0; } @media (max-width: 700px) { .ds-goal-heading { gap: 8px; } + .ds-goal--count .ds-goal-body { grid-template-columns: minmax(0, 1fr); gap: 20px; padding: 20px 16px; } + .ds-goal--tasks .ds-goal-overview { padding: 16px; } + .ds-goal-tasks { padding-inline: 8px; } .ds-goal-count { gap: 8px; } .ds-goal-count .ds-counter { gap: 4px; } .ds-habit-list .ds-goal .ds-habit-history { align-self: flex-start; }