diff --git a/apps/pwa/package.json b/apps/pwa/package.json index 4107481..23782ab 100644 --- a/apps/pwa/package.json +++ b/apps/pwa/package.json @@ -11,10 +11,17 @@ "test": "vitest run" }, "dependencies": { + "@schedule-x/calendar": "4.6.0", + "@schedule-x/event-modal": "4.6.0", + "@schedule-x/events-service": "4.6.0", + "@schedule-x/react": "4.1.0", + "@schedule-x/theme-default": "4.6.0", "@tanstack/react-query": "5.101.0", "ical.js": "2.2.1", + "lucide-react": "1.17.0", "react": "^19.0.0", "react-dom": "^19.0.0", + "temporal-polyfill": "0.3.2", "zustand": "5.0.14" }, "devDependencies": { diff --git a/apps/pwa/src/main.tsx b/apps/pwa/src/main.tsx index c252eb6..5af53a2 100644 --- a/apps/pwa/src/main.tsx +++ b/apps/pwa/src/main.tsx @@ -1,3 +1,13 @@ +// IMPORTANT — import order is load-bearing: +// 1. temporal-polyfill/global must register Temporal on globalThis BEFORE any +// Schedule-X code runs (Schedule-X v4 uses Temporal objects at module init). +// 2. Schedule-X theme-default CSS must be imported BEFORE tokens.css so that +// the project's --sx-color-* overrides in tokens.css win the cascade. +// 3. styles/index.css imports tokens.css which carries the --sx-color-* overrides. +import 'temporal-polyfill/global' +import '@schedule-x/theme-default/dist/index.css' +import './styles/index.css' + import React from 'react' import ReactDOM from 'react-dom/client' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' diff --git a/apps/pwa/src/styles/index.css b/apps/pwa/src/styles/index.css new file mode 100644 index 0000000..34042d1 --- /dev/null +++ b/apps/pwa/src/styles/index.css @@ -0,0 +1,29 @@ +/** + * FamilySync global styles entry point. + * + * Import order matters — tokens.css must be imported AFTER + * @schedule-x/theme-default/dist/index.css (handled in main.tsx) + * so the --sx-color-* overrides win the cascade. + */ + +@import './tokens.css'; + +/* ── Minimal Global Reset ─────────────────────────────────────────────────── */ + +*, +*::before, +*::after { + box-sizing: border-box; +} + +body { + margin: 0; + font-family: var(--font-family-base); + font-size: var(--text-body-size); + font-weight: var(--text-body-weight); + line-height: var(--text-body-line-height); + color: var(--color-text-primary); + background-color: var(--color-surface); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} diff --git a/apps/pwa/src/styles/tokens.css b/apps/pwa/src/styles/tokens.css new file mode 100644 index 0000000..0b64a41 --- /dev/null +++ b/apps/pwa/src/styles/tokens.css @@ -0,0 +1,135 @@ +/** + * FamilySync Design System — Clean Theme (Phase 2, D-01/D-02) + * + * All colors, spacing, typography, and breakpoints are declared here as CSS + * custom properties. No hard-coded hex or px values in component files. + * + * Import order in main.tsx: + * 1. temporal-polyfill/global + * 2. @schedule-x/theme-default/dist/index.css + * 3. ./styles/index.css (imports this file) + * + * The Schedule-X --sx-color-* overrides at the bottom must come AFTER the + * theme-default import so they win the cascade. + */ + +:root { + /* ───────────────────────────────────────────────────────────────────────── + * BASE SURFACE / BORDER / TEXT PALETTE + * ───────────────────────────────────────────────────────────────────────── */ + + --color-surface: #FFFFFF; + --color-surface-dim: #F7F7F8; + --color-surface-raised: #FFFFFF; + + --color-border: #E2E4E9; + --color-border-subtle: #ECEEF2; + + --color-text-primary: #111318; + --color-text-secondary: #6B7280; + --color-text-muted: #9CA3AF; + + --color-focus-ring: #4A90D9; + --color-overlay: rgba(0, 0, 0, 0.32); + + /* ───────────────────────────────────────────────────────────────────────── + * SEMANTIC CALENDAR COLORS + * 10% accent band — ONLY for event chip fills and color legend swatches. + * ───────────────────────────────────────────────────────────────────────── */ + + --color-member-0: #4A90D9; + --color-member-1: #50C878; + --color-member-2: #F5A623; + --color-member-3: #9B59B6; + --color-member-4: #E67E22; + --color-member-5: #1ABC9C; + + /* Shared-family calendar — rose, confirmed by user */ + --color-shared-family: #F25C7A; + + /* Destructive — declared for Phase 3 reuse; not used in Phase 2 (read-only) */ + --color-destructive: #DC2626; + + /* ───────────────────────────────────────────────────────────────────────── + * SPACING SCALE (multiples of 4px) + * ───────────────────────────────────────────────────────────────────────── */ + + --space-1: 4px; + --space-2: 8px; + --space-3: 12px; + --space-4: 16px; + --space-6: 24px; + --space-8: 32px; + --space-12: 48px; + + /* ───────────────────────────────────────────────────────────────────────── + * TYPOGRAPHY + * ───────────────────────────────────────────────────────────────────────── */ + + --font-family-base: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + + /* Body — 15px/400/1.5: popover description, agenda location lines */ + --text-body-size: 15px; + --text-body-weight: 400; + --text-body-line-height: 1.5; + + /* Label — 13px/400/1.4: event chip text, time labels, legend labels */ + --text-label-size: 13px; + --text-label-weight: 400; + --text-label-line-height: 1.4; + + /* Heading — 18px/600/1.25: popover title, view section headers */ + --text-heading-size: 18px; + --text-heading-weight: 600; + --text-heading-line-height: 1.25; + + /* Display — 24px/600/1.2: app name in nav bar (desktop), day number in day-view */ + --text-display-size: 24px; + --text-display-weight: 600; + --text-display-line-height: 1.2; + + /* ───────────────────────────────────────────────────────────────────────── + * BREAKPOINTS (reference; use in @media queries) + * ───────────────────────────────────────────────────────────────────────── */ + + --bp-phone: 0px; + --bp-tablet: 768px; + --bp-desktop: 1280px; + + /* ───────────────────────────────────────────────────────────────────────── + * SCHEDULE-X CSS TOKEN OVERRIDES + * + * Map all --sx-color-* vars to project tokens so no Schedule-X default + * colors bleed through. These must come after the @schedule-x/theme-default + * import (handled by import order in main.tsx). + * + * Source: https://schedule-x.dev/docs/calendar/configuration + * ───────────────────────────────────────────────────────────────────────── */ + + --sx-color-primary: var(--color-member-0); + --sx-color-on-primary: #ffffff; + --sx-color-primary-container: var(--color-surface-dim); + --sx-color-on-primary-container: var(--color-text-primary); + --sx-color-surface: var(--color-surface); + --sx-color-on-surface: var(--color-text-primary); + --sx-color-on-surface-variant: var(--color-text-secondary); + --sx-color-outline: var(--color-border); + --sx-color-neutral: var(--color-surface-dim); + --sx-color-neutral-variant: var(--color-border-subtle); + + --sx-font-family: var(--font-family-base); +} + +/* ───────────────────────────────────────────────────────────────────────── + * SKELETON SHIMMER ANIMATION + * Used by SkeletonCalendar.tsx — no third-party animation library. + * ───────────────────────────────────────────────────────────────────────── */ + +@keyframes shimmer { + 0% { + background-position: -200% 0; + } + 100% { + background-position: 200% 0; + } +} diff --git a/apps/pwa/src/styles/tokens.ts b/apps/pwa/src/styles/tokens.ts new file mode 100644 index 0000000..9f9bdbe --- /dev/null +++ b/apps/pwa/src/styles/tokens.ts @@ -0,0 +1,77 @@ +/** + * FamilySync Design System — TypeScript Token Object (Phase 2, D-01/D-02) + * + * Mirrors apps/pwa/src/styles/tokens.css. Used by components that need token + * values in inline-style props (e.g., dynamic CSS-in-JS, canvas, SVG). + * + * Keep names aligned with the CSS custom property names (camelCase of the CSS + * var name after `--`). + */ + +export const tokens = { + // ── Surface / Border / Text ────────────────────────────────────────────── + + colorSurface: '#FFFFFF', + colorSurfaceDim: '#F7F7F8', + colorSurfaceRaised: '#FFFFFF', + + colorBorder: '#E2E4E9', + colorBorderSubtle: '#ECEEF2', + + colorTextPrimary: '#111318', + colorTextSecondary: '#6B7280', + colorTextMuted: '#9CA3AF', + + colorFocusRing: '#4A90D9', + colorOverlay: 'rgba(0, 0, 0, 0.32)', + + // ── Calendar Colors ─────────────────────────────────────────────────────── + + colorMember0: '#4A90D9', + colorMember1: '#50C878', + colorMember2: '#F5A623', + colorMember3: '#9B59B6', + colorMember4: '#E67E22', + colorMember5: '#1ABC9C', + + colorSharedFamily: '#F25C7A', + colorDestructive: '#DC2626', + + // ── Spacing Scale ───────────────────────────────────────────────────────── + + space1: '4px', + space2: '8px', + space3: '12px', + space4: '16px', + space6: '24px', + space8: '32px', + space12: '48px', + + // ── Typography ──────────────────────────────────────────────────────────── + + fontFamilyBase: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif', + + textBodySize: '15px', + textBodyWeight: 400, + textBodyLineHeight: 1.5, + + textLabelSize: '13px', + textLabelWeight: 400, + textLabelLineHeight: 1.4, + + textHeadingSize: '18px', + textHeadingWeight: 600, + textHeadingLineHeight: 1.25, + + textDisplaySize: '24px', + textDisplayWeight: 600, + textDisplayLineHeight: 1.2, + + // ── Breakpoints ─────────────────────────────────────────────────────────── + + bpPhone: '0px', + bpTablet: '768px', + bpDesktop: '1280px', +} as const + +export type Tokens = typeof tokens diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 13bbc6f..64b6649 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -56,18 +56,39 @@ importers: apps/pwa: dependencies: + '@schedule-x/calendar': + specifier: 4.6.0 + version: 4.6.0(@preact/signals@2.9.1(preact@10.29.2))(preact@10.29.2)(temporal-polyfill@0.3.2) + '@schedule-x/event-modal': + specifier: 4.6.0 + version: 4.6.0(@preact/signals@2.9.1(preact@10.29.2))(preact@10.29.2) + '@schedule-x/events-service': + specifier: 4.6.0 + version: 4.6.0 + '@schedule-x/react': + specifier: 4.1.0 + version: 4.1.0(@schedule-x/calendar@4.6.0(@preact/signals@2.9.1(preact@10.29.2))(preact@10.29.2)(temporal-polyfill@0.3.2))(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@schedule-x/theme-default': + specifier: 4.6.0 + version: 4.6.0 '@tanstack/react-query': specifier: 5.101.0 version: 5.101.0(react@19.2.7) ical.js: specifier: 2.2.1 version: 2.2.1 + lucide-react: + specifier: 1.17.0 + version: 1.17.0(react@19.2.7) react: specifier: ^19.0.0 version: 19.2.7 react-dom: specifier: ^19.0.0 version: 19.2.7(react@19.2.7) + temporal-polyfill: + specifier: 0.3.2 + version: 0.3.2 zustand: specifier: 5.0.14 version: 5.0.14(@types/react@19.2.16)(react@19.2.7) @@ -730,6 +751,14 @@ packages: '@oxc-project/types@0.133.0': resolution: {integrity: sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==} + '@preact/signals-core@1.14.2': + resolution: {integrity: sha512-RZHdBj9ZF4n40Rp4jS052EHHjBWf96P9oNdXPfhQTovCuWY9iQn3Gq+gOTJSgBO9A/JBuPfMOWsSX/lIU9Pc/A==} + + '@preact/signals@2.9.1': + resolution: {integrity: sha512-xVqN8mJjbSN5IB/8Ubmd9NN+Ew6zJswoRxrjZbH3YsgkMshFeO6d8zxEFpHRTq9GJZx7cnPs2CnCpFqtGXGNsw==} + peerDependencies: + preact: '>= 10.25.0 || >=11.0.0-0' + '@rolldown/binding-android-arm64@1.0.3': resolution: {integrity: sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -831,6 +860,32 @@ packages: '@rolldown/pluginutils@1.0.1': resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + '@schedule-x/calendar@4.6.0': + resolution: {integrity: sha512-8Rf5bUf0DVphJB2BaudtG4IHkw21wgMJiiuk5o+7SIOq6YH4hkVJo+Kk12pWSfp2KTZ9mnnUuLV+szBVhiEPlg==} + peerDependencies: + '@preact/signals': ^2.0.2 + preact: ^10.19.2 + temporal-polyfill: 0.3.0 + + '@schedule-x/event-modal@4.6.0': + resolution: {integrity: sha512-ZWtOxXa84GPiY0aYkKfauJMT/VDnVZTQjyQRdqHoIxr9Bt4D6eebBamBO/KxbcdQfc2vERs+2WJPTvNnTqTOlw==} + peerDependencies: + '@preact/signals': ^2.0.2 + preact: ^10.19.2 + + '@schedule-x/events-service@4.6.0': + resolution: {integrity: sha512-czUSPdanESNYgckCxId3weoL3omSG6FKuJMYMefCyzx/NFBxJM83b6PKQQ+pJeRnPuyax+KNKFc/DQFCC05YLA==} + + '@schedule-x/react@4.1.0': + resolution: {integrity: sha512-l2hn1kdTModk4Lbt+nEr/VAcuNMA+w+hAAtgz8VLOj5UeieM7PWoK+cDapzPHCjUKisCSPd8QZfAX966Ma82kQ==} + peerDependencies: + '@schedule-x/calendar': ^3.1.0 || ^4.0.0 + react: ^16.7.0 || ^17 || ^18 || ^19 + react-dom: ^16.7.0 || ^17 || ^18 || ^19 + + '@schedule-x/theme-default@4.6.0': + resolution: {integrity: sha512-SM3bcvROJeG7ScH14lBIHZSTOBTR8h+oTswbpx1gpscddUfQfAnu7cFW4OJJI+ii+Si1IdTd4T3+rrnKd4HDDg==} + '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -1337,6 +1392,11 @@ packages: resolution: {integrity: sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==} engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'} + lucide-react@1.17.0: + resolution: {integrity: sha512-9FA9evdox/JQL5PT57fdA1x/yg8T7knJ98+zjTL3UfKza6pflQUUh3XtaQIHKvnsJw1lmsEyHVlt5jchYxOQ5w==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + lz-string@1.5.0: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true @@ -1400,6 +1460,9 @@ packages: resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} engines: {node: ^10 || ^12 || >=14} + preact@10.29.2: + resolution: {integrity: sha512-7tNmwg/7mzzAoB/8kSg6Hl37JraAZw3Z3A0JSY7VXlZwo82Xn0G7wKbNNs2qoF4ZEEsQGTwDAroNdqKs1ofJxQ==} + pretty-format@27.5.1: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} @@ -1488,6 +1551,12 @@ packages: symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + temporal-polyfill@0.3.2: + resolution: {integrity: sha512-TzHthD/heRK947GNiSu3Y5gSPpeUDH34+LESnfsq8bqpFhsB79HFBX8+Z834IVX68P3EUyRPZK5bL/1fh437Eg==} + + temporal-spec@0.3.1: + resolution: {integrity: sha512-B4TUhezh9knfSIMwt7RVggApDRJZo73uZdj8AacL2mZ8RP5KtLianh2MXxL06GN9ESYiIsiuoLQhgVfwe55Yhw==} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -2139,6 +2208,13 @@ snapshots: '@oxc-project/types@0.133.0': {} + '@preact/signals-core@1.14.2': {} + + '@preact/signals@2.9.1(preact@10.29.2)': + dependencies: + '@preact/signals-core': 1.14.2 + preact: 10.29.2 + '@rolldown/binding-android-arm64@1.0.3': optional: true @@ -2192,6 +2268,27 @@ snapshots: '@rolldown/pluginutils@1.0.1': {} + '@schedule-x/calendar@4.6.0(@preact/signals@2.9.1(preact@10.29.2))(preact@10.29.2)(temporal-polyfill@0.3.2)': + dependencies: + '@preact/signals': 2.9.1(preact@10.29.2) + preact: 10.29.2 + temporal-polyfill: 0.3.2 + + '@schedule-x/event-modal@4.6.0(@preact/signals@2.9.1(preact@10.29.2))(preact@10.29.2)': + dependencies: + '@preact/signals': 2.9.1(preact@10.29.2) + preact: 10.29.2 + + '@schedule-x/events-service@4.6.0': {} + + '@schedule-x/react@4.1.0(@schedule-x/calendar@4.6.0(@preact/signals@2.9.1(preact@10.29.2))(preact@10.29.2)(temporal-polyfill@0.3.2))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@schedule-x/calendar': 4.6.0(@preact/signals@2.9.1(preact@10.29.2))(preact@10.29.2)(temporal-polyfill@0.3.2) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + + '@schedule-x/theme-default@4.6.0': {} + '@standard-schema/spec@1.1.0': {} '@tanstack/query-core@5.101.0': {} @@ -2652,6 +2749,10 @@ snapshots: lru.min@1.1.4: {} + lucide-react@1.17.0(react@19.2.7): + dependencies: + react: 19.2.7 + lz-string@1.5.0: {} magic-string@0.30.21: @@ -2706,6 +2807,8 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + preact@10.29.2: {} + pretty-format@27.5.1: dependencies: ansi-regex: 5.0.1 @@ -2790,6 +2893,12 @@ snapshots: symbol-tree@3.2.4: {} + temporal-polyfill@0.3.2: + dependencies: + temporal-spec: 0.3.1 + + temporal-spec@0.3.1: {} + tinybench@2.9.0: {} tinyexec@1.2.4: {}