Give each habit completion type a distinct layout
This commit is contained in:
@@ -3,7 +3,7 @@ import { ChevronDown, ListChecks } from "lucide-react";
|
|||||||
import { CalendarHeatmap } from "./CalendarHeatmap";
|
import { CalendarHeatmap } from "./CalendarHeatmap";
|
||||||
import { demoCalendar } from "./calendar-model";
|
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({
|
export function HabitChart({
|
||||||
name,
|
name,
|
||||||
method,
|
method,
|
||||||
@@ -47,22 +47,27 @@ export function HabitChart({
|
|||||||
const Heading = `h${headingLevel}` as const;
|
const Heading = `h${headingLevel}` as const;
|
||||||
if (trackingMethod) return (
|
if (trackingMethod) return (
|
||||||
<section className={`ds-habit-chart ds-goal ds-goal--${trackingMethod}`} id={id} aria-labelledby={headingId} style={{ "--habit-color": color } as CSSProperties}>
|
<section className={`ds-habit-chart ds-goal ds-goal--${trackingMethod}`} id={id} aria-labelledby={headingId} style={{ "--habit-color": color } as CSSProperties}>
|
||||||
<header className="ds-goal-heading">
|
<div className="ds-goal-body">
|
||||||
<div className="ds-goal-title">
|
<div className="ds-goal-overview">
|
||||||
{trackingMethod === "manual" && children}
|
<header className="ds-goal-heading">
|
||||||
<Heading id={headingId} className="type-title">
|
<div className="ds-goal-title">
|
||||||
{trackingMethod === "manual" ? <label htmlFor={checkInId}>{name}</label> : name}
|
{trackingMethod === "manual" && children}
|
||||||
</Heading>
|
<Heading id={headingId} className="type-title">
|
||||||
|
{trackingMethod === "manual" ? <label htmlFor={checkInId}>{name}</label> : name}
|
||||||
|
</Heading>
|
||||||
|
</div>
|
||||||
|
{headingActions}
|
||||||
|
</header>
|
||||||
|
{(!due || schedule !== "Every day") && <p className="ds-goal-schedule">{due ? schedule : `Not scheduled today · ${schedule}`}</p>}
|
||||||
|
{trackingMethod === "tasks" && due && target > 0 && <div className="ds-goal-task-progress">
|
||||||
|
<progress aria-label={`${name} tasks completed`} value={Math.max(0, Math.min(value, target))} max={target} />
|
||||||
|
<span role="status">{value} / {target}</span>
|
||||||
|
</div>}
|
||||||
</div>
|
</div>
|
||||||
{headingActions}
|
{trackingMethod === "count" && <div className="ds-goal-count">{children}<span className="ds-goal-unit">{unit}</span></div>}
|
||||||
</header>
|
{editor}
|
||||||
{(!due || schedule !== "Every day") && <p className="ds-goal-schedule">{due ? schedule : `Not scheduled today · ${schedule}`}</p>}
|
{trackingMethod === "tasks" && <div className="ds-goal-tasks">{tasks}</div>}
|
||||||
{editor}
|
</div>
|
||||||
{trackingMethod === "count" && <div className="ds-goal-count">{children}<span className="ds-goal-unit">{unit}</span></div>}
|
|
||||||
{trackingMethod === "tasks" && <div className="ds-goal-tasks">
|
|
||||||
{due && target > 0 && <p className="ds-goal-task-progress" role="status">{value} / {target} completed</p>}
|
|
||||||
{tasks}
|
|
||||||
</div>}
|
|
||||||
{calendar}
|
{calendar}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1591,33 +1591,51 @@
|
|||||||
|
|
||||||
/* Each goal puts its completion control before secondary history. */
|
/* 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-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-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 { 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 label { cursor: pointer; }
|
||||||
.ds-goal-title > .ds-checkbox { flex: 0 0 44px; justify-content: center; gap: 0; }
|
.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--manual .ds-goal-body { padding: 4px 0; }
|
||||||
.ds-goal-schedule { @apply type-small; color: var(--ds-secondary); margin-top: 4px; }
|
.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--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-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-counter { padding: 6px; gap: 8px; background: var(--ds-surface); }
|
.ds-goal--count .ds-goal-body > .ds-inline-editor { grid-column: 1 / -1; }
|
||||||
.ds-goal-count .ds-counter-input { @apply type-title; width: 5ch; min-height: 52px; background: var(--ds-paper); }
|
.ds-goal-count { display: flex; flex-direction: column; align-items: center; gap: 8px; min-width: 0; }
|
||||||
.ds-goal-count .ds-counter .ds-button { min-height: 48px; min-width: 48px; }
|
.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-unit { @apply type-body; color: var(--ds-secondary); overflow-wrap: anywhere; }
|
.ds-goal-count .ds-counter-input { font-size: 36px; line-height: 1.2; width: 4ch; min-height: 60px; background: transparent; }
|
||||||
.ds-habit-list .ds-goal .ds-habit-history { align-self: flex-end; margin-top: 8px; }
|
.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-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 { list-style: none; padding: 0; margin: 0; }
|
||||||
.ds-routine-items .ds-editable-task:first-child { border-top: 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 > .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: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-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; }
|
.ds-habit-starter-options { margin-top: 0; }
|
||||||
@media (max-width: 700px) {
|
@media (max-width: 700px) {
|
||||||
.ds-goal-heading { gap: 8px; }
|
.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 { gap: 8px; }
|
||||||
.ds-goal-count .ds-counter { gap: 4px; }
|
.ds-goal-count .ds-counter { gap: 4px; }
|
||||||
.ds-habit-list .ds-goal .ds-habit-history { align-self: flex-start; }
|
.ds-habit-list .ds-goal .ds-habit-history { align-self: flex-start; }
|
||||||
|
|||||||
Reference in New Issue
Block a user