Files
minabot/scripts/share-card-preview.tsx
syntaxbullet 00729096bf feat: add theme provider and control components for light/dark mode
- Implemented ThemeProvider to manage theme state and preferences.
- Added ThemeControl component for users to switch between system, light, and dark themes.
- Integrated localStorage for theme preference persistence.
- Updated document styles based on theme changes.

test: add tests for permanent deletion of habits and related records

- Created tests to ensure permanent deletion of habits removes all dependent records.
- Verified that deletion requires ownership, authentication, and checks for archived habits.
- Added tests to confirm rollback behavior on deletion failures.

test: add tests for habit palette and progress card functionality

- Implemented tests for habit color progression and theme-based color shading.
- Added tests for progress card calculations and calendar display logic.

feat: define theme types and preferences

- Introduced Theme and ThemePreference types for better type safety.
- Created utility functions for resolving theme based on user preference and system settings.
- Defined theme palettes for light and dark modes to ensure adequate contrast.
2026-09-05 10:07:28 +02:00

31 lines
3.2 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Disposable visual fixtures: no user data and no outbound Discord calls.
import { ThemeProvider, ThemeControl, useTheme } from "../src/components/ThemeProvider";
import { createRoot } from "react-dom/client";
import { useEffect, useState } from "react";
import { addDays } from "../src/habits/calendar";
import { renderProgressCard } from "../src/lib/progress-card";
import type { ShareData } from "../src/sharing/contracts";
import "../styles/globals.css";
const names = ["A few pages, every day", "An afternoon walk", "A very long habit name that should truncate gracefully without hitting the count", "Drink enough water", "练习中文 · 日本語", "Make time to unwind"];
const colors = ["#90647e", "#397f76", "#ba793c", "#4b70b4", "#834f9e", "#bd5863"];
function Preview({ count, empty = false, privateCard = false }: { count: number; empty?: boolean; privateCard?: boolean }) {
const { theme } = useTheme();
const [url, setUrl] = useState("");
useEffect(() => {
let disposed = false, objectUrl = "";
const total = count === 6 ? 365 : 30;
const from = addDays("2026-09-04", 1 - total);
const data: ShareData = { from, to: "2026-09-04", timezone: "America/Argentina/Buenos_Aires", habits: names.slice(0, count).map((name, i) => {
const days = Array.from({length: total}, (_, n) => { const due = !empty && (n + i) % 7 !== 0; const ratio = due ? (n % 4 === 0 ? 0 : n % 3 === 0 ? .5 : 1) : null; return { date: addDays(from, n), due, complete: ratio === 1, ratio, color: ratio === 1 ? colors[i]! : ratio ? "#c7b0bf" : "#e4e7e0" }; });
return { name, color: colors[i]!, completed: days.filter(d => d.complete).length, scheduled: days.filter(d => d.due).length, days, legend: { empty: "#e4e7e0", partial: ["#c7b0bf"], complete: colors[i]! } };
}) };
void renderProgressCard(data, { id: "qa", discordId: "qa", username: "alice", displayName: "Alexandra with a very long display name", avatarUrl: null, timezone: data.timezone }, { name: !privateCard, avatar: !privateCard, habitNames: !privateCard, timezone: privateCard }, theme).then(result => {
if (disposed) return; objectUrl = URL.createObjectURL(result.blob); setUrl(objectUrl);
});
return () => { disposed = true; if (objectUrl) URL.revokeObjectURL(objectUrl); };
}, [theme]);
return <section style={{ background: "#313338", padding: 20, borderRadius: 8, width: 360 }}><h2 style={{ color: "white", marginBottom: 16 }}>{count} {count === 1 ? "habit" : "habits"}{empty ? " · no schedule" : ""}{privateCard ? " · private" : ""}</h2>{url && <img alt="QA progress card at 320 pixels" src={url} style={{ width: 320, display: "block" }} />}</section>;
}
createRoot(document.getElementById("root")!).render(<ThemeProvider><main className="ds-root" style={{ padding: 24 }}><ThemeControl /><h1>Card readability at 320px</h1><div style={{ display: "flex", flexWrap: "wrap", alignItems: "start", gap: 24, marginTop: 24 }}><Preview count={1}/><Preview count={6}/><Preview count={6} privateCard/><Preview count={1} empty/></div><h2 style={{ margin: "24px 0" }}>390 × 844 mobile composer</h2><iframe title="Mobile dashboard" src="http://127.0.0.1:3107/__preview/login" style={{ width: 390, height: 844, border: "1px solid #aaa" }}/></main></ThemeProvider>);