@aurostack/stacks 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +209 -0
- package/cli/src/extract.mjs +165 -0
- package/cli/src/fsx.mjs +197 -0
- package/cli/src/generate.mjs +235 -0
- package/cli/src/hooks.mjs +55 -0
- package/cli/src/manifest.mjs +163 -0
- package/cli/src/strip.mjs +208 -0
- package/cli/src/tokens.mjs +102 -0
- package/cli/stack.mjs +672 -0
- package/package.json +37 -0
- package/templates/nest-api/files/.bin/db.ts +123 -0
- package/templates/nest-api/files/.bin/dc.ts +74 -0
- package/templates/nest-api/files/.bin/helpers.ts +49 -0
- package/templates/nest-api/files/.bin/secrets.ts +68 -0
- package/templates/nest-api/files/.bin/start.ts +47 -0
- package/templates/nest-api/files/.dockerignore +7 -0
- package/templates/nest-api/files/.env.example +91 -0
- package/templates/nest-api/files/.env.test.example +75 -0
- package/templates/nest-api/files/.forgejo/workflows/build-and-push.yml +41 -0
- package/templates/nest-api/files/.forgejo/workflows/test-e2e.yml +83 -0
- package/templates/nest-api/files/.forgejo/workflows/test.yml +53 -0
- package/templates/nest-api/files/.npmrc.example +1 -0
- package/templates/nest-api/files/.nvmrc +1 -0
- package/templates/nest-api/files/.prettierrc +8 -0
- package/templates/nest-api/files/.swcrc +15 -0
- package/templates/nest-api/files/.watchmanconfig +3 -0
- package/templates/nest-api/files/.yarnrc.yml +1 -0
- package/templates/nest-api/files/Dockerfile +40 -0
- package/templates/nest-api/files/__mocks__/activation-twofa.js +7 -0
- package/templates/nest-api/files/__mocks__/activation.js +7 -0
- package/templates/nest-api/files/__mocks__/forgot-password.js +7 -0
- package/templates/nest-api/files/__mocks__/main-layout.js +7 -0
- package/templates/nest-api/files/__mocks__/password-changed.js +7 -0
- package/templates/nest-api/files/__mocks__/reset-password.js +7 -0
- package/templates/nest-api/files/__mocks__/set-password.js +7 -0
- package/templates/nest-api/files/__mocks__/welcome.js +7 -0
- package/templates/nest-api/files/_gitignore +62 -0
- package/templates/nest-api/files/compose.test.yml +33 -0
- package/templates/nest-api/files/compose.yml +39 -0
- package/templates/nest-api/files/emails/activation-twofa.tsx +58 -0
- package/templates/nest-api/files/emails/activation.tsx +36 -0
- package/templates/nest-api/files/emails/forgot-password.tsx +29 -0
- package/templates/nest-api/files/emails/main-layout.tsx +134 -0
- package/templates/nest-api/files/emails/password-changed.tsx +24 -0
- package/templates/nest-api/files/emails/reset-password.tsx +24 -0
- package/templates/nest-api/files/emails/set-password.tsx +25 -0
- package/templates/nest-api/files/emails/welcome.tsx +39 -0
- package/templates/nest-api/files/eslint.config.mjs +28 -0
- package/templates/nest-api/files/global.d.ts +5 -0
- package/templates/nest-api/files/nest-cli.json +8 -0
- package/templates/nest-api/files/package.json +150 -0
- package/templates/nest-api/files/prisma/schema/auth.prisma +75 -0
- package/templates/nest-api/files/prisma/schema/base.prisma +17 -0
- package/templates/nest-api/files/prisma/schema/enum.prisma +17 -0
- package/templates/nest-api/files/prisma/schema/notification.prisma +14 -0
- package/templates/nest-api/files/prisma.config.ts +13 -0
- package/templates/nest-api/files/public/.gitkeep +0 -0
- package/templates/nest-api/files/seeders/index.ts +27 -0
- package/templates/nest-api/files/seeders/util.ts +88 -0
- package/templates/nest-api/files/src/__tests__/app.controller.spec.ts +29 -0
- package/templates/nest-api/files/src/app.controller.ts +40 -0
- package/templates/nest-api/files/src/app.module.ts +268 -0
- package/templates/nest-api/files/src/app.resolver.ts +32 -0
- package/templates/nest-api/files/src/app.setup.ts +178 -0
- package/templates/nest-api/files/src/common/__tests__/auth.service.spec.ts +31 -0
- package/templates/nest-api/files/src/common/__tests__/cache.service.spec.ts +214 -0
- package/templates/nest-api/files/src/common/__tests__/config.service.spec.ts +56 -0
- package/templates/nest-api/files/src/common/__tests__/generator.service.spec.ts +114 -0
- package/templates/nest-api/files/src/common/__tests__/health.controller.spec.ts +72 -0
- package/templates/nest-api/files/src/common/__tests__/helpers.spec.ts +124 -0
- package/templates/nest-api/files/src/common/__tests__/mail.service.spec.ts +75 -0
- package/templates/nest-api/files/src/common/__tests__/prisma.indicator.spec.ts +39 -0
- package/templates/nest-api/files/src/common/__tests__/prisma.service.spec.ts +15 -0
- package/templates/nest-api/files/src/common/__tests__/records.spec.ts +87 -0
- package/templates/nest-api/files/src/common/__tests__/schemas.spec.ts +142 -0
- package/templates/nest-api/files/src/common/__tests__/throttler-storage.service.spec.ts +80 -0
- package/templates/nest-api/files/src/common/common.module.ts +72 -0
- package/templates/nest-api/files/src/common/constants/index.ts +2 -0
- package/templates/nest-api/files/src/common/constants/options.ts +52 -0
- package/templates/nest-api/files/src/common/constants/pino.ts +24 -0
- package/templates/nest-api/files/src/common/controllers/health.controller.ts +54 -0
- package/templates/nest-api/files/src/common/controllers/index.ts +2 -0
- package/templates/nest-api/files/src/common/controllers/metrics.controller.ts +21 -0
- package/templates/nest-api/files/src/common/decorators/index.ts +2 -0
- package/templates/nest-api/files/src/common/decorators/misc.decorator.ts +49 -0
- package/templates/nest-api/files/src/common/decorators/multi-part.decorator.ts +6 -0
- package/templates/nest-api/files/src/common/dto/filters.dto.ts +25 -0
- package/templates/nest-api/files/src/common/dto/index.ts +1 -0
- package/templates/nest-api/files/src/common/entity/index.ts +2 -0
- package/templates/nest-api/files/src/common/entity/ok.entity.ts +11 -0
- package/templates/nest-api/files/src/common/entity/pagination.entity.ts +24 -0
- package/templates/nest-api/files/src/common/exceptions/index.ts +1 -0
- package/templates/nest-api/files/src/common/exceptions/prisma.exception.ts +57 -0
- package/templates/nest-api/files/src/common/guards/feature-flag.guard.ts +51 -0
- package/templates/nest-api/files/src/common/guards/index.ts +1 -0
- package/templates/nest-api/files/src/common/interceptors/index.ts +1 -0
- package/templates/nest-api/files/src/common/interceptors/metrics.interceptor.ts +37 -0
- package/templates/nest-api/files/src/common/misc/helpers.ts +26 -0
- package/templates/nest-api/files/src/common/misc/index.ts +3 -0
- package/templates/nest-api/files/src/common/misc/origin-builder.ts +56 -0
- package/templates/nest-api/files/src/common/misc/prisma.indicator.ts +24 -0
- package/templates/nest-api/files/src/common/modules/index.ts +3 -0
- package/templates/nest-api/files/src/common/modules/logger.module.ts +104 -0
- package/templates/nest-api/files/src/common/modules/metrics.module.ts +28 -0
- package/templates/nest-api/files/src/common/modules/queue.module.ts +23 -0
- package/templates/nest-api/files/src/common/processors/index.ts +1 -0
- package/templates/nest-api/files/src/common/processors/mail/index.ts +47 -0
- package/templates/nest-api/files/src/common/processors/mail/process.ts +225 -0
- package/templates/nest-api/files/src/common/schemas/fields.ts +76 -0
- package/templates/nest-api/files/src/common/schemas/index.ts +1 -0
- package/templates/nest-api/files/src/common/services/auth-rate-limit.storage.ts +79 -0
- package/templates/nest-api/files/src/common/services/auth.service.ts +16 -0
- package/templates/nest-api/files/src/common/services/cache.service.ts +118 -0
- package/templates/nest-api/files/src/common/services/config.service.ts +388 -0
- package/templates/nest-api/files/src/common/services/feature-flag.service.ts +117 -0
- package/templates/nest-api/files/src/common/services/generator.service.ts +54 -0
- package/templates/nest-api/files/src/common/services/index.ts +10 -0
- package/templates/nest-api/files/src/common/services/logger.service.ts +14 -0
- package/templates/nest-api/files/src/common/services/mail.service.ts +44 -0
- package/templates/nest-api/files/src/common/services/prisma.service.ts +106 -0
- package/templates/nest-api/files/src/common/services/throttler-storage.service.ts +112 -0
- package/templates/nest-api/files/src/common/types/email.ts +55 -0
- package/templates/nest-api/files/src/common/types/index.ts +3 -0
- package/templates/nest-api/files/src/common/types/misc.ts +20 -0
- package/templates/nest-api/files/src/common/types/prisma.ts +18 -0
- package/templates/nest-api/files/src/common/utils/enum.ts +39 -0
- package/templates/nest-api/files/src/common/utils/helpers.ts +139 -0
- package/templates/nest-api/files/src/common/utils/index.ts +5 -0
- package/templates/nest-api/files/src/common/utils/ip.ts +22 -0
- package/templates/nest-api/files/src/common/utils/records.ts +66 -0
- package/templates/nest-api/files/src/common/utils/redis.keys.ts +31 -0
- package/templates/nest-api/files/src/lib/__tests__/access.spec.ts +33 -0
- package/templates/nest-api/files/src/lib/access.ts +114 -0
- package/templates/nest-api/files/src/lib/auth.ts +76 -0
- package/templates/nest-api/files/src/lib/rate-limit.ts +20 -0
- package/templates/nest-api/files/src/main.ts +28 -0
- package/templates/nest-api/files/src/media/__tests__/media.service.spec.ts +60 -0
- package/templates/nest-api/files/src/media/__tests__/s3.service.spec.ts +159 -0
- package/templates/nest-api/files/src/media/entity/file.entity.ts +7 -0
- package/templates/nest-api/files/src/media/entity/index.ts +1 -0
- package/templates/nest-api/files/src/media/media.module.ts +20 -0
- package/templates/nest-api/files/src/media/processors/index.ts +44 -0
- package/templates/nest-api/files/src/media/services/index.ts +2 -0
- package/templates/nest-api/files/src/media/services/media.service.ts +29 -0
- package/templates/nest-api/files/src/media/services/s3.service.ts +168 -0
- package/templates/nest-api/files/src/notifications/controllers/index.ts +1 -0
- package/templates/nest-api/files/src/notifications/controllers/notifications.controller.ts +72 -0
- package/templates/nest-api/files/src/notifications/dto/index.ts +1 -0
- package/templates/nest-api/files/src/notifications/dto/notification.dto.ts +11 -0
- package/templates/nest-api/files/src/notifications/entity/index.ts +1 -0
- package/templates/nest-api/files/src/notifications/entity/notification.entity.ts +37 -0
- package/templates/nest-api/files/src/notifications/notifications.module.ts +11 -0
- package/templates/nest-api/files/src/notifications/services/index.ts +1 -0
- package/templates/nest-api/files/src/notifications/services/notifications.service.ts +98 -0
- package/templates/nest-api/files/src/realtime/__tests__/events.gateway.spec.ts +88 -0
- package/templates/nest-api/files/src/realtime/__tests__/rooms.spec.ts +29 -0
- package/templates/nest-api/files/src/realtime/__tests__/ws-permissions.guard.spec.ts +54 -0
- package/templates/nest-api/files/src/realtime/adapters/redis-io.adapter.ts +57 -0
- package/templates/nest-api/files/src/realtime/constants/index.ts +68 -0
- package/templates/nest-api/files/src/realtime/decorators/index.ts +1 -0
- package/templates/nest-api/files/src/realtime/decorators/ws-permissions.decorator.ts +14 -0
- package/templates/nest-api/files/src/realtime/gateways/events.gateway.ts +187 -0
- package/templates/nest-api/files/src/realtime/guards/index.ts +2 -0
- package/templates/nest-api/files/src/realtime/guards/ws-auth.guard.ts +20 -0
- package/templates/nest-api/files/src/realtime/guards/ws-permissions.guard.ts +35 -0
- package/templates/nest-api/files/src/realtime/misc/session-from-socket.ts +54 -0
- package/templates/nest-api/files/src/realtime/realtime.module.ts +26 -0
- package/templates/nest-api/files/src/realtime/services/presence.service.ts +56 -0
- package/templates/nest-api/files/src/realtime/services/realtime.service.ts +72 -0
- package/templates/nest-api/files/src/users/__tests__/current-user.controller.spec.ts +77 -0
- package/templates/nest-api/files/src/users/__tests__/current-user.service.spec.ts +159 -0
- package/templates/nest-api/files/src/users/__tests__/user.entity.spec.ts +175 -0
- package/templates/nest-api/files/src/users/__tests__/users.controller.spec.ts +89 -0
- package/templates/nest-api/files/src/users/__tests__/users.service.spec.ts +398 -0
- package/templates/nest-api/files/src/users/controllers/current-user.controller.ts +82 -0
- package/templates/nest-api/files/src/users/controllers/index.ts +2 -0
- package/templates/nest-api/files/src/users/controllers/users.controller.ts +40 -0
- package/templates/nest-api/files/src/users/decorators/index.ts +1 -0
- package/templates/nest-api/files/src/users/decorators/permissions.decorator.ts +24 -0
- package/templates/nest-api/files/src/users/dto/avatar.dto.ts +13 -0
- package/templates/nest-api/files/src/users/dto/filters.dto.ts +8 -0
- package/templates/nest-api/files/src/users/dto/index.ts +2 -0
- package/templates/nest-api/files/src/users/entity/index.ts +1 -0
- package/templates/nest-api/files/src/users/entity/user.entity.ts +53 -0
- package/templates/nest-api/files/src/users/services/current-user.service.ts +46 -0
- package/templates/nest-api/files/src/users/services/index.ts +2 -0
- package/templates/nest-api/files/src/users/services/users.service.ts +88 -0
- package/templates/nest-api/files/src/users/users.module.ts +10 -0
- package/templates/nest-api/files/test/app.spec.ts +102 -0
- package/templates/nest-api/files/test/auth.spec.ts +108 -0
- package/templates/nest-api/files/test/current-user.spec.ts +154 -0
- package/templates/nest-api/files/test/factory/app.ts +172 -0
- package/templates/nest-api/files/test/factory/constants.ts +22 -0
- package/templates/nest-api/files/test/factory/dto.ts +27 -0
- package/templates/nest-api/files/test/factory/entity.ts +70 -0
- package/templates/nest-api/files/test/factory/gen.ts +75 -0
- package/templates/nest-api/files/test/factory/util.ts +21 -0
- package/templates/nest-api/files/test/notifications.spec.ts +176 -0
- package/templates/nest-api/files/test/rate-limit.spec.ts +180 -0
- package/templates/nest-api/files/test/setup.ts +1 -0
- package/templates/nest-api/files/test/users.spec.ts +437 -0
- package/templates/nest-api/files/tsconfig.build.json +13 -0
- package/templates/nest-api/files/tsconfig.json +35 -0
- package/templates/nest-api/files/vitest.config.ts +53 -0
- package/templates/nest-api/template.json +452 -0
- package/templates/node-worker/files/.bin/db.ts +123 -0
- package/templates/node-worker/files/.bin/helpers.ts +49 -0
- package/templates/node-worker/files/.bin/secrets.ts +68 -0
- package/templates/node-worker/files/.dockerignore +5 -0
- package/templates/node-worker/files/.env.example +21 -0
- package/templates/node-worker/files/.forgejo/workflows/build-and-push.yml +41 -0
- package/templates/node-worker/files/.nvmrc +1 -0
- package/templates/node-worker/files/.prettierignore +4 -0
- package/templates/node-worker/files/.prettierrc +8 -0
- package/templates/node-worker/files/.yarnrc.yml +5 -0
- package/templates/node-worker/files/Dockerfile +63 -0
- package/templates/node-worker/files/Dockerfile.dev +59 -0
- package/templates/node-worker/files/_gitignore +48 -0
- package/templates/node-worker/files/ecosystem.config.cjs +10 -0
- package/templates/node-worker/files/eslint.config.js +37 -0
- package/templates/node-worker/files/package.json +67 -0
- package/templates/node-worker/files/prisma/_gitignore +1 -0
- package/templates/node-worker/files/prisma/schema/base.prisma +36 -0
- package/templates/node-worker/files/prisma.config.ts +13 -0
- package/templates/node-worker/files/src/config.ts +55 -0
- package/templates/node-worker/files/src/db.ts +118 -0
- package/templates/node-worker/files/src/index.ts +7 -0
- package/templates/node-worker/files/src/logger.ts +45 -0
- package/templates/node-worker/files/src/types.ts +23 -0
- package/templates/node-worker/files/src/utils/browser.ts +16 -0
- package/templates/node-worker/files/src/utils/date.ts +68 -0
- package/templates/node-worker/files/src/utils/helpers.ts +17 -0
- package/templates/node-worker/files/src/utils/number.ts +47 -0
- package/templates/node-worker/files/src/utils/worker.ts +29 -0
- package/templates/node-worker/files/src/workers/constants.ts +6 -0
- package/templates/node-worker/files/src/workers/example/index.ts +27 -0
- package/templates/node-worker/files/src/workers/index.ts +1 -0
- package/templates/node-worker/files/tsconfig.json +30 -0
- package/templates/node-worker/template.json +135 -0
- package/templates/py-worker/files/.dockerignore +176 -0
- package/templates/py-worker/files/.env.example +20 -0
- package/templates/py-worker/files/.forgejo/workflows/build-amd-push.yml +39 -0
- package/templates/py-worker/files/Dockerfile +10 -0
- package/templates/py-worker/files/README.md +3 -0
- package/templates/py-worker/files/_gitignore +176 -0
- package/templates/py-worker/files/config.py +89 -0
- package/templates/py-worker/files/models/decorators.py +42 -0
- package/templates/py-worker/files/models/enums.py +32 -0
- package/templates/py-worker/files/models/mixins.py +61 -0
- package/templates/py-worker/files/requirements.txt +18 -0
- package/templates/py-worker/files/utils/db.py +67 -0
- package/templates/py-worker/files/utils/logger.py +97 -0
- package/templates/py-worker/files/utils/misc.py +45 -0
- package/templates/py-worker/files/utils/worker.py +80 -0
- package/templates/py-worker/files/worker.py +41 -0
- package/templates/py-worker/files/workers/hello/process.py +22 -0
- package/templates/py-worker/template.json +87 -0
- package/templates/react-app/README.md +78 -0
- package/templates/react-app/derive.sh +128 -0
- package/templates/react-app/files/.bin/openapi.js +32 -0
- package/templates/react-app/files/.bin/secrets.js +47 -0
- package/templates/react-app/files/.dockerignore +16 -0
- package/templates/react-app/files/.editorconfig +12 -0
- package/templates/react-app/files/.env.example +34 -0
- package/templates/react-app/files/.forgejo/workflows/build-and-push.yml +55 -0
- package/templates/react-app/files/.forgejo/workflows/ci.yml +39 -0
- package/templates/react-app/files/.husky/pre-commit +1 -0
- package/templates/react-app/files/.nvmrc +1 -0
- package/templates/react-app/files/.prettierignore +6 -0
- package/templates/react-app/files/.prettierrc +9 -0
- package/templates/react-app/files/.yarnrc.yml +2 -0
- package/templates/react-app/files/Dockerfile +52 -0
- package/templates/react-app/files/_gitignore +33 -0
- package/templates/react-app/files/build.sh +53 -0
- package/templates/react-app/files/docker/nginx.conf +134 -0
- package/templates/react-app/files/eslint.config.mjs +75 -0
- package/templates/react-app/files/index.html +62 -0
- package/templates/react-app/files/openapi-ts.config.ts +22 -0
- package/templates/react-app/files/package.json +80 -0
- package/templates/react-app/files/src/app/nav.tsx +32 -0
- package/templates/react-app/files/src/app/providers.tsx +39 -0
- package/templates/react-app/files/src/app/router.tsx +142 -0
- package/templates/react-app/files/src/app/store.ts +20 -0
- package/templates/react-app/files/src/features/auth/components/auth-card.tsx +23 -0
- package/templates/react-app/files/src/features/auth/components/checkbox-field.tsx +32 -0
- package/templates/react-app/files/src/features/auth/components/google-button.tsx +41 -0
- package/templates/react-app/files/src/features/auth/components/or-divider.tsx +9 -0
- package/templates/react-app/files/src/features/auth/components/password-field.tsx +70 -0
- package/templates/react-app/files/src/features/auth/components/submit-button.tsx +16 -0
- package/templates/react-app/files/src/features/auth/lib/auth-actions.ts +68 -0
- package/templates/react-app/files/src/features/auth/lib/password-strength.ts +20 -0
- package/templates/react-app/files/src/features/auth/lib/redirect.ts +6 -0
- package/templates/react-app/files/src/features/auth/lib/schemas.ts +37 -0
- package/templates/react-app/files/src/features/seo/routes.ts +65 -0
- package/templates/react-app/files/src/lib/env.ts +28 -0
- package/templates/react-app/files/src/lib/paths.ts +58 -0
- package/templates/react-app/files/src/main.tsx +19 -0
- package/templates/react-app/files/src/routes/admin/home.tsx +24 -0
- package/templates/react-app/files/src/routes/auth/forgot-password.tsx +93 -0
- package/templates/react-app/files/src/routes/auth/login.tsx +87 -0
- package/templates/react-app/files/src/routes/auth/reset-password.tsx +83 -0
- package/templates/react-app/files/src/routes/auth/signup.tsx +117 -0
- package/templates/react-app/files/src/routes/auth/verified.tsx +31 -0
- package/templates/react-app/files/src/routes/auth/verify-email.tsx +97 -0
- package/templates/react-app/files/src/routes/home.tsx +17 -0
- package/templates/react-app/files/src/routes/legal/privacy.tsx +26 -0
- package/templates/react-app/files/src/routes/legal/terms.tsx +28 -0
- package/templates/react-app/files/src/routes/marketing/home.tsx +18 -0
- package/templates/react-app/files/src/routes/settings.tsx +10 -0
- package/templates/react-app/files/src/shared/api/base-api.ts +24 -0
- package/templates/react-app/files/src/shared/api/base-query.ts +29 -0
- package/templates/react-app/files/src/shared/api/config.ts +21 -0
- package/templates/react-app/files/src/shared/api/index.ts +15 -0
- package/templates/react-app/files/src/shared/api/realtime-events.ts +34 -0
- package/templates/react-app/files/src/shared/api/socket.ts +41 -0
- package/templates/react-app/files/src/shared/api/store.ts +39 -0
- package/templates/react-app/files/src/shared/api/upload-query.ts +115 -0
- package/templates/react-app/files/src/shared/auth/client.ts +27 -0
- package/templates/react-app/files/src/shared/auth/config.ts +28 -0
- package/templates/react-app/files/src/shared/auth/guards.tsx +75 -0
- package/templates/react-app/files/src/shared/auth/hooks.ts +13 -0
- package/templates/react-app/files/src/shared/auth/index.ts +20 -0
- package/templates/react-app/files/src/shared/auth/logout.ts +23 -0
- package/templates/react-app/files/src/shared/auth/provider.tsx +52 -0
- package/templates/react-app/files/src/shared/auth/redirect.ts +65 -0
- package/templates/react-app/files/src/shared/auth/slice.ts +54 -0
- package/templates/react-app/files/src/shared/hooks/index.ts +6 -0
- package/templates/react-app/files/src/shared/layouts/app-shell/app-shell.tsx +225 -0
- package/templates/react-app/files/src/shared/layouts/app-shell/user-menu.tsx +74 -0
- package/templates/react-app/files/src/shared/layouts/auth-layout.tsx +137 -0
- package/templates/react-app/files/src/shared/layouts/error-boundary.tsx +38 -0
- package/templates/react-app/files/src/shared/layouts/index.ts +23 -0
- package/templates/react-app/files/src/shared/layouts/marketing/marketing-layout.tsx +29 -0
- package/templates/react-app/files/src/shared/layouts/marketing/section-heading.tsx +38 -0
- package/templates/react-app/files/src/shared/layouts/marketing/section.tsx +30 -0
- package/templates/react-app/files/src/shared/layouts/states/app-shell-skeleton.tsx +47 -0
- package/templates/react-app/files/src/shared/layouts/states/empty.tsx +22 -0
- package/templates/react-app/files/src/shared/layouts/states/forbidden.tsx +14 -0
- package/templates/react-app/files/src/shared/layouts/states/loading.tsx +11 -0
- package/templates/react-app/files/src/shared/layouts/states/not-found.tsx +21 -0
- package/templates/react-app/files/src/shared/layouts/states/page-skeleton.tsx +39 -0
- package/templates/react-app/files/src/shared/layouts/states/route-fallback.tsx +26 -0
- package/templates/react-app/files/src/shared/layouts/states/state-screen.tsx +22 -0
- package/templates/react-app/files/src/shared/types/README.md +28 -0
- package/templates/react-app/files/src/shared/types/api/index.ts +9 -0
- package/templates/react-app/files/src/shared/types/api/zod.gen.ts +5 -0
- package/templates/react-app/files/src/shared/types/index.ts +4 -0
- package/templates/react-app/files/src/shared/ui/components/brand/logo.tsx +51 -0
- package/templates/react-app/files/src/shared/ui/components/brand/social-icons.tsx +37 -0
- package/templates/react-app/files/src/shared/ui/components/form/form.tsx +120 -0
- package/templates/react-app/files/src/shared/ui/components/ui/alert.tsx +25 -0
- package/templates/react-app/files/src/shared/ui/components/ui/button.tsx +43 -0
- package/templates/react-app/files/src/shared/ui/components/ui/calendar.tsx +78 -0
- package/templates/react-app/files/src/shared/ui/components/ui/card.tsx +44 -0
- package/templates/react-app/files/src/shared/ui/components/ui/checkbox.tsx +16 -0
- package/templates/react-app/files/src/shared/ui/components/ui/date-picker.tsx +76 -0
- package/templates/react-app/files/src/shared/ui/components/ui/input.tsx +22 -0
- package/templates/react-app/files/src/shared/ui/components/ui/label.tsx +19 -0
- package/templates/react-app/files/src/shared/ui/components/ui/modal.tsx +94 -0
- package/templates/react-app/files/src/shared/ui/components/ui/popover.tsx +31 -0
- package/templates/react-app/files/src/shared/ui/components/ui/progress.tsx +33 -0
- package/templates/react-app/files/src/shared/ui/components/ui/search-input.tsx +69 -0
- package/templates/react-app/files/src/shared/ui/components/ui/select.tsx +80 -0
- package/templates/react-app/files/src/shared/ui/components/ui/skeleton.tsx +12 -0
- package/templates/react-app/files/src/shared/ui/components/ui/sonner.tsx +23 -0
- package/templates/react-app/files/src/shared/ui/components/ui/spinner.tsx +8 -0
- package/templates/react-app/files/src/shared/ui/components/ui/textarea.tsx +22 -0
- package/templates/react-app/files/src/shared/ui/index.ts +45 -0
- package/templates/react-app/files/src/shared/ui/lib/utils.ts +7 -0
- package/templates/react-app/files/src/shared/ui/lib/zod-setup.ts +28 -0
- package/templates/react-app/files/src/shared/ui/meta/brand.ts +18 -0
- package/templates/react-app/files/src/shared/ui/meta/meta-context.ts +30 -0
- package/templates/react-app/files/src/shared/ui/meta/meta-provider.tsx +24 -0
- package/templates/react-app/files/src/shared/ui/meta/page-meta.tsx +69 -0
- package/templates/react-app/files/src/shared/ui/meta/use-meta-config.ts +11 -0
- package/templates/react-app/files/src/shared/ui/styles/globals.css +285 -0
- package/templates/react-app/files/src/shared/ui/styles/theme.css +48 -0
- package/templates/react-app/files/src/shared/ui/theme/init-script.ts +23 -0
- package/templates/react-app/files/src/shared/ui/theme/theme-colors.ts +14 -0
- package/templates/react-app/files/src/shared/ui/theme/theme-context.ts +60 -0
- package/templates/react-app/files/src/shared/ui/theme/theme-provider.tsx +101 -0
- package/templates/react-app/files/src/shared/ui/theme/theme-toggle.tsx +40 -0
- package/templates/react-app/files/src/shared/ui/theme/use-theme.ts +11 -0
- package/templates/react-app/files/src/vite-env.d.ts +13 -0
- package/templates/react-app/files/tsconfig.json +26 -0
- package/templates/react-app/files/tsconfig.node.json +25 -0
- package/templates/react-app/files/vite.config.ts +344 -0
- package/templates/react-app/overrides/src/routes/auth/forgot-password.tsx +93 -0
- package/templates/react-app/overrides/src/routes/auth/login.tsx +87 -0
- package/templates/react-app/overrides/src/routes/auth/signup.tsx +117 -0
- package/templates/react-app/overrides/src/routes/auth/verified.tsx +31 -0
- package/templates/react-app/overrides/src/routes/auth/verify-email.tsx +97 -0
- package/templates/react-app/overrides/src/shared/auth/config.ts +28 -0
- package/templates/react-app/overrides/src/shared/auth/guards.tsx +75 -0
- package/templates/react-app/overrides/src/shared/auth/index.ts +20 -0
- package/templates/react-app/overrides/src/shared/auth/redirect.ts +65 -0
- package/templates/react-app/overrides/src/shared/hooks/index.ts +6 -0
- package/templates/react-app/overrides/src/shared/types/README.md +28 -0
- package/templates/react-app/template.json +222 -0
- package/templates/react-monorepo/files/.dockerignore +19 -0
- package/templates/react-monorepo/files/.editorconfig +12 -0
- package/templates/react-monorepo/files/.forgejo/workflows/build-and-push.yml +98 -0
- package/templates/react-monorepo/files/.forgejo/workflows/ci.yml +35 -0
- package/templates/react-monorepo/files/.husky/pre-commit +1 -0
- package/templates/react-monorepo/files/.nvmrc +1 -0
- package/templates/react-monorepo/files/.prettierignore +6 -0
- package/templates/react-monorepo/files/.prettierrc +9 -0
- package/templates/react-monorepo/files/.yarnrc.yml +2 -0
- package/templates/react-monorepo/files/Dockerfile +90 -0
- package/templates/react-monorepo/files/_gitignore +35 -0
- package/templates/react-monorepo/files/apps/admin/.bin/secrets.js +57 -0
- package/templates/react-monorepo/files/apps/admin/.env.example +16 -0
- package/templates/react-monorepo/files/apps/admin/eslint.config.mjs +3 -0
- package/templates/react-monorepo/files/apps/admin/index.html +57 -0
- package/templates/react-monorepo/files/apps/admin/package.json +33 -0
- package/templates/react-monorepo/files/apps/admin/src/App.tsx +3 -0
- package/templates/react-monorepo/files/apps/admin/src/app/nav.tsx +14 -0
- package/templates/react-monorepo/files/apps/admin/src/app/providers.tsx +42 -0
- package/templates/react-monorepo/files/apps/admin/src/app/router.tsx +50 -0
- package/templates/react-monorepo/files/apps/admin/src/app/store.ts +15 -0
- package/templates/react-monorepo/files/apps/admin/src/lib/env.ts +12 -0
- package/templates/react-monorepo/files/apps/admin/src/main.tsx +14 -0
- package/templates/react-monorepo/files/apps/admin/src/routes/home.tsx +17 -0
- package/templates/react-monorepo/files/apps/admin/src/routes/settings.tsx +10 -0
- package/templates/react-monorepo/files/apps/admin/src/vite-env.d.ts +14 -0
- package/templates/react-monorepo/files/apps/admin/tsconfig.json +10 -0
- package/templates/react-monorepo/files/apps/admin/vite.config.ts +13 -0
- package/templates/react-monorepo/files/apps/auth/.bin/secrets.js +57 -0
- package/templates/react-monorepo/files/apps/auth/.env.example +16 -0
- package/templates/react-monorepo/files/apps/auth/eslint.config.mjs +3 -0
- package/templates/react-monorepo/files/apps/auth/index.html +57 -0
- package/templates/react-monorepo/files/apps/auth/package.json +32 -0
- package/templates/react-monorepo/files/apps/auth/src/App.tsx +3 -0
- package/templates/react-monorepo/files/apps/auth/src/app/providers.tsx +42 -0
- package/templates/react-monorepo/files/apps/auth/src/app/router.tsx +19 -0
- package/templates/react-monorepo/files/apps/auth/src/app/store.ts +15 -0
- package/templates/react-monorepo/files/apps/auth/src/components/auth-card.tsx +23 -0
- package/templates/react-monorepo/files/apps/auth/src/components/checkbox-field.tsx +32 -0
- package/templates/react-monorepo/files/apps/auth/src/components/google-button.tsx +41 -0
- package/templates/react-monorepo/files/apps/auth/src/components/or-divider.tsx +9 -0
- package/templates/react-monorepo/files/apps/auth/src/components/password-field.tsx +70 -0
- package/templates/react-monorepo/files/apps/auth/src/components/submit-button.tsx +16 -0
- package/templates/react-monorepo/files/apps/auth/src/lib/auth-actions.ts +68 -0
- package/templates/react-monorepo/files/apps/auth/src/lib/env.ts +8 -0
- package/templates/react-monorepo/files/apps/auth/src/lib/password-strength.ts +20 -0
- package/templates/react-monorepo/files/apps/auth/src/lib/redirect.ts +6 -0
- package/templates/react-monorepo/files/apps/auth/src/lib/schemas.ts +37 -0
- package/templates/react-monorepo/files/apps/auth/src/main.tsx +14 -0
- package/templates/react-monorepo/files/apps/auth/src/routes/forgot-password.tsx +91 -0
- package/templates/react-monorepo/files/apps/auth/src/routes/login.tsx +82 -0
- package/templates/react-monorepo/files/apps/auth/src/routes/reset-password.tsx +83 -0
- package/templates/react-monorepo/files/apps/auth/src/routes/signup.tsx +117 -0
- package/templates/react-monorepo/files/apps/auth/src/routes/verified.tsx +30 -0
- package/templates/react-monorepo/files/apps/auth/src/routes/verify-email.tsx +96 -0
- package/templates/react-monorepo/files/apps/auth/src/vite-env.d.ts +14 -0
- package/templates/react-monorepo/files/apps/auth/tsconfig.json +10 -0
- package/templates/react-monorepo/files/apps/auth/vite.config.ts +13 -0
- package/templates/react-monorepo/files/apps/client/.bin/secrets.js +57 -0
- package/templates/react-monorepo/files/apps/client/.env.example +16 -0
- package/templates/react-monorepo/files/apps/client/eslint.config.mjs +3 -0
- package/templates/react-monorepo/files/apps/client/index.html +57 -0
- package/templates/react-monorepo/files/apps/client/package.json +35 -0
- package/templates/react-monorepo/files/apps/client/src/App.tsx +3 -0
- package/templates/react-monorepo/files/apps/client/src/app/nav.tsx +14 -0
- package/templates/react-monorepo/files/apps/client/src/app/providers.tsx +42 -0
- package/templates/react-monorepo/files/apps/client/src/app/router.tsx +50 -0
- package/templates/react-monorepo/files/apps/client/src/app/store.ts +15 -0
- package/templates/react-monorepo/files/apps/client/src/lib/env.ts +12 -0
- package/templates/react-monorepo/files/apps/client/src/main.tsx +19 -0
- package/templates/react-monorepo/files/apps/client/src/routes/home.tsx +17 -0
- package/templates/react-monorepo/files/apps/client/src/routes/settings.tsx +10 -0
- package/templates/react-monorepo/files/apps/client/src/vite-env.d.ts +15 -0
- package/templates/react-monorepo/files/apps/client/tsconfig.json +10 -0
- package/templates/react-monorepo/files/apps/client/vite.config.ts +120 -0
- package/templates/react-monorepo/files/apps/landing/.bin/secrets.js +57 -0
- package/templates/react-monorepo/files/apps/landing/.env.example +20 -0
- package/templates/react-monorepo/files/apps/landing/eslint.config.mjs +3 -0
- package/templates/react-monorepo/files/apps/landing/index.html +51 -0
- package/templates/react-monorepo/files/apps/landing/package.json +33 -0
- package/templates/react-monorepo/files/apps/landing/src/App.tsx +3 -0
- package/templates/react-monorepo/files/apps/landing/src/app/providers.tsx +42 -0
- package/templates/react-monorepo/files/apps/landing/src/app/router.tsx +21 -0
- package/templates/react-monorepo/files/apps/landing/src/app/store.ts +18 -0
- package/templates/react-monorepo/files/apps/landing/src/features/seo/routes.ts +65 -0
- package/templates/react-monorepo/files/apps/landing/src/lib/env.ts +10 -0
- package/templates/react-monorepo/files/apps/landing/src/main.tsx +14 -0
- package/templates/react-monorepo/files/apps/landing/src/routes/home.tsx +18 -0
- package/templates/react-monorepo/files/apps/landing/src/vite-env.d.ts +15 -0
- package/templates/react-monorepo/files/apps/landing/tsconfig.json +10 -0
- package/templates/react-monorepo/files/apps/landing/vite.config.ts +169 -0
- package/templates/react-monorepo/files/build.sh +61 -0
- package/templates/react-monorepo/files/docker/nginx.conf +136 -0
- package/templates/react-monorepo/files/package.json +37 -0
- package/templates/react-monorepo/files/packages/.gitkeep +0 -0
- package/templates/react-monorepo/files/packages/api/eslint.config.mjs +3 -0
- package/templates/react-monorepo/files/packages/api/package.json +30 -0
- package/templates/react-monorepo/files/packages/api/src/base-api.ts +24 -0
- package/templates/react-monorepo/files/packages/api/src/base-query.ts +29 -0
- package/templates/react-monorepo/files/packages/api/src/config.ts +21 -0
- package/templates/react-monorepo/files/packages/api/src/index.ts +15 -0
- package/templates/react-monorepo/files/packages/api/src/realtime-events.ts +34 -0
- package/templates/react-monorepo/files/packages/api/src/socket.ts +41 -0
- package/templates/react-monorepo/files/packages/api/src/store.ts +39 -0
- package/templates/react-monorepo/files/packages/api/src/upload-query.ts +115 -0
- package/templates/react-monorepo/files/packages/api/tsconfig.json +4 -0
- package/templates/react-monorepo/files/packages/auth/eslint.config.mjs +3 -0
- package/templates/react-monorepo/files/packages/auth/package.json +33 -0
- package/templates/react-monorepo/files/packages/auth/src/client.ts +27 -0
- package/templates/react-monorepo/files/packages/auth/src/config.ts +22 -0
- package/templates/react-monorepo/files/packages/auth/src/guards.tsx +61 -0
- package/templates/react-monorepo/files/packages/auth/src/hooks.ts +13 -0
- package/templates/react-monorepo/files/packages/auth/src/index.ts +20 -0
- package/templates/react-monorepo/files/packages/auth/src/logout.ts +23 -0
- package/templates/react-monorepo/files/packages/auth/src/provider.tsx +52 -0
- package/templates/react-monorepo/files/packages/auth/src/redirect.ts +37 -0
- package/templates/react-monorepo/files/packages/auth/src/slice.ts +54 -0
- package/templates/react-monorepo/files/packages/auth/tsconfig.json +4 -0
- package/templates/react-monorepo/files/packages/config/README.md +47 -0
- package/templates/react-monorepo/files/packages/config/eslint/base.mjs +83 -0
- package/templates/react-monorepo/files/packages/config/package.json +37 -0
- package/templates/react-monorepo/files/packages/config/tailwind/theme.css +48 -0
- package/templates/react-monorepo/files/packages/config/tsconfig/base.json +22 -0
- package/templates/react-monorepo/files/packages/config/tsconfig/node.json +9 -0
- package/templates/react-monorepo/files/packages/config/tsconfig/react.json +9 -0
- package/templates/react-monorepo/files/packages/config/vite/index.d.ts +18 -0
- package/templates/react-monorepo/files/packages/config/vite/index.mjs +129 -0
- package/templates/react-monorepo/files/packages/hooks/eslint.config.mjs +3 -0
- package/templates/react-monorepo/files/packages/hooks/package.json +16 -0
- package/templates/react-monorepo/files/packages/hooks/src/index.ts +3 -0
- package/templates/react-monorepo/files/packages/hooks/tsconfig.json +4 -0
- package/templates/react-monorepo/files/packages/layouts/eslint.config.mjs +3 -0
- package/templates/react-monorepo/files/packages/layouts/package.json +30 -0
- package/templates/react-monorepo/files/packages/layouts/src/app-shell/app-shell.tsx +225 -0
- package/templates/react-monorepo/files/packages/layouts/src/app-shell/user-menu.tsx +74 -0
- package/templates/react-monorepo/files/packages/layouts/src/auth-layout.tsx +137 -0
- package/templates/react-monorepo/files/packages/layouts/src/error-boundary.tsx +38 -0
- package/templates/react-monorepo/files/packages/layouts/src/index.ts +23 -0
- package/templates/react-monorepo/files/packages/layouts/src/marketing/marketing-layout.tsx +29 -0
- package/templates/react-monorepo/files/packages/layouts/src/marketing/section-heading.tsx +38 -0
- package/templates/react-monorepo/files/packages/layouts/src/marketing/section.tsx +30 -0
- package/templates/react-monorepo/files/packages/layouts/src/states/app-shell-skeleton.tsx +47 -0
- package/templates/react-monorepo/files/packages/layouts/src/states/empty.tsx +22 -0
- package/templates/react-monorepo/files/packages/layouts/src/states/forbidden.tsx +14 -0
- package/templates/react-monorepo/files/packages/layouts/src/states/loading.tsx +11 -0
- package/templates/react-monorepo/files/packages/layouts/src/states/not-found.tsx +21 -0
- package/templates/react-monorepo/files/packages/layouts/src/states/page-skeleton.tsx +39 -0
- package/templates/react-monorepo/files/packages/layouts/src/states/route-fallback.tsx +26 -0
- package/templates/react-monorepo/files/packages/layouts/src/states/state-screen.tsx +22 -0
- package/templates/react-monorepo/files/packages/layouts/tsconfig.json +4 -0
- package/templates/react-monorepo/files/packages/types/.bin/openapi.js +32 -0
- package/templates/react-monorepo/files/packages/types/.bin/secrets.js +57 -0
- package/templates/react-monorepo/files/packages/types/README.md +28 -0
- package/templates/react-monorepo/files/packages/types/eslint.config.mjs +3 -0
- package/templates/react-monorepo/files/packages/types/openapi-ts.config.ts +22 -0
- package/templates/react-monorepo/files/packages/types/package.json +29 -0
- package/templates/react-monorepo/files/packages/types/src/index.ts +4 -0
- package/templates/react-monorepo/files/packages/types/src/types/api/index.ts +9 -0
- package/templates/react-monorepo/files/packages/types/src/types/api/zod.gen.ts +5 -0
- package/templates/react-monorepo/files/packages/types/tsconfig.json +7 -0
- package/templates/react-monorepo/files/packages/ui/components.json +21 -0
- package/templates/react-monorepo/files/packages/ui/eslint.config.mjs +13 -0
- package/templates/react-monorepo/files/packages/ui/package.json +44 -0
- package/templates/react-monorepo/files/packages/ui/src/components/brand/logo.tsx +51 -0
- package/templates/react-monorepo/files/packages/ui/src/components/brand/social-icons.tsx +37 -0
- package/templates/react-monorepo/files/packages/ui/src/components/form/form.tsx +120 -0
- package/templates/react-monorepo/files/packages/ui/src/components/ui/alert.tsx +25 -0
- package/templates/react-monorepo/files/packages/ui/src/components/ui/button.tsx +43 -0
- package/templates/react-monorepo/files/packages/ui/src/components/ui/calendar.tsx +78 -0
- package/templates/react-monorepo/files/packages/ui/src/components/ui/card.tsx +44 -0
- package/templates/react-monorepo/files/packages/ui/src/components/ui/checkbox.tsx +16 -0
- package/templates/react-monorepo/files/packages/ui/src/components/ui/date-picker.tsx +76 -0
- package/templates/react-monorepo/files/packages/ui/src/components/ui/input.tsx +22 -0
- package/templates/react-monorepo/files/packages/ui/src/components/ui/label.tsx +19 -0
- package/templates/react-monorepo/files/packages/ui/src/components/ui/modal.tsx +94 -0
- package/templates/react-monorepo/files/packages/ui/src/components/ui/popover.tsx +31 -0
- package/templates/react-monorepo/files/packages/ui/src/components/ui/progress.tsx +33 -0
- package/templates/react-monorepo/files/packages/ui/src/components/ui/search-input.tsx +69 -0
- package/templates/react-monorepo/files/packages/ui/src/components/ui/select.tsx +80 -0
- package/templates/react-monorepo/files/packages/ui/src/components/ui/skeleton.tsx +12 -0
- package/templates/react-monorepo/files/packages/ui/src/components/ui/sonner.tsx +23 -0
- package/templates/react-monorepo/files/packages/ui/src/components/ui/spinner.tsx +8 -0
- package/templates/react-monorepo/files/packages/ui/src/components/ui/textarea.tsx +22 -0
- package/templates/react-monorepo/files/packages/ui/src/index.ts +45 -0
- package/templates/react-monorepo/files/packages/ui/src/lib/utils.ts +7 -0
- package/templates/react-monorepo/files/packages/ui/src/lib/zod-setup.ts +28 -0
- package/templates/react-monorepo/files/packages/ui/src/meta/brand.ts +18 -0
- package/templates/react-monorepo/files/packages/ui/src/meta/meta-context.ts +30 -0
- package/templates/react-monorepo/files/packages/ui/src/meta/meta-provider.tsx +24 -0
- package/templates/react-monorepo/files/packages/ui/src/meta/page-meta.tsx +69 -0
- package/templates/react-monorepo/files/packages/ui/src/meta/use-meta-config.ts +11 -0
- package/templates/react-monorepo/files/packages/ui/src/styles/globals.css +285 -0
- package/templates/react-monorepo/files/packages/ui/src/theme/init-script.ts +23 -0
- package/templates/react-monorepo/files/packages/ui/src/theme/theme-colors.ts +14 -0
- package/templates/react-monorepo/files/packages/ui/src/theme/theme-context.ts +60 -0
- package/templates/react-monorepo/files/packages/ui/src/theme/theme-provider.tsx +101 -0
- package/templates/react-monorepo/files/packages/ui/src/theme/theme-toggle.tsx +40 -0
- package/templates/react-monorepo/files/packages/ui/src/theme/use-theme.ts +11 -0
- package/templates/react-monorepo/files/packages/ui/tsconfig.json +9 -0
- package/templates/react-monorepo/files/turbo.json +24 -0
- package/templates/react-monorepo/template.json +230 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @acme/ui global stylesheet — the single CSS entry every app imports:
|
|
3
|
+
* import '@acme/ui/globals.css';
|
|
4
|
+
*
|
|
5
|
+
* Layers: self-hosted font -> Tailwind v4 -> brand tokens (@acme/config) ->
|
|
6
|
+
* shadcn semantic tokens (light/dark) mapped for utilities. Design language =
|
|
7
|
+
* Wise: flat surfaces, no shadows, 1px ink hairlines, 24px radii, green CTA only.
|
|
8
|
+
*/
|
|
9
|
+
@import 'tailwindcss';
|
|
10
|
+
@import '@acme/config/tailwind.css';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Inter, self-hosted. This was a `@import url(fonts.googleapis.com/...)` on the
|
|
14
|
+
* first line of this file, which is the slowest possible way to load a font: a
|
|
15
|
+
* CSS @import is only discovered after this stylesheet has been parsed, so it
|
|
16
|
+
* cost three SERIALIZED round trips across two extra origins (googleapis ->
|
|
17
|
+
* gstatic -> woff2) before text could paint. Worse for the installed PWA — the
|
|
18
|
+
* service worker cannot precache a cross-origin URL, so fonts hit the network on
|
|
19
|
+
* every cold start and failed outright offline, despite the shell being cached.
|
|
20
|
+
*
|
|
21
|
+
* One variable file replaces the six static weights that were being requested.
|
|
22
|
+
* 47 KB covers the whole 100-900 axis, and all six weights are in use
|
|
23
|
+
* (400/500/600/700/800/900). Latin only, and upright only — there is no italic
|
|
24
|
+
* anywhere in the codebase. Add `wght-italic` here if that ever changes.
|
|
25
|
+
*
|
|
26
|
+
* Declared by hand rather than `@import '@fontsource-variable/inter/wght.css'`
|
|
27
|
+
* because that pulls in all seven subsets (cyrillic, greek, vietnamese, ...).
|
|
28
|
+
* Vite would emit every one into dist, and `woff2` is in the PWA precache glob.
|
|
29
|
+
*
|
|
30
|
+
* Named `Inter` so the `--font-sans` / `--font-display` stacks need no change; an
|
|
31
|
+
* @font-face shadows a same-named system font, so this file always wins.
|
|
32
|
+
*/
|
|
33
|
+
@font-face {
|
|
34
|
+
font-family: 'Inter';
|
|
35
|
+
font-style: normal;
|
|
36
|
+
font-weight: 100 900;
|
|
37
|
+
font-display: swap;
|
|
38
|
+
src: url('@fontsource-variable/inter/files/inter-latin-wght-normal.woff2') format('woff2');
|
|
39
|
+
unicode-range:
|
|
40
|
+
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC,
|
|
41
|
+
U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* Scan workspace component packages for utility classes (they live under
|
|
45
|
+
node_modules in consumers, which Tailwind skips by default). The consuming
|
|
46
|
+
app's own src is auto-detected by Tailwind. Add a line here for any new
|
|
47
|
+
component package. */
|
|
48
|
+
@source '../**/*.{ts,tsx}';
|
|
49
|
+
@source '../../../layouts/src/**/*.{ts,tsx}';
|
|
50
|
+
|
|
51
|
+
@custom-variant dark (&:is(.dark *));
|
|
52
|
+
|
|
53
|
+
:root {
|
|
54
|
+
--radius: 0.75rem; /* 12px base (inputs); cards/buttons use explicit 24px */
|
|
55
|
+
|
|
56
|
+
--background: #eceee9;
|
|
57
|
+
--foreground: #0e0f0c;
|
|
58
|
+
--card: #ffffff;
|
|
59
|
+
--card-foreground: #0e0f0c;
|
|
60
|
+
--popover: #ffffff;
|
|
61
|
+
--popover-foreground: #0e0f0c;
|
|
62
|
+
--primary: #9fe870;
|
|
63
|
+
--primary-foreground: #0e0f0c;
|
|
64
|
+
--secondary: #f2f4ef;
|
|
65
|
+
--secondary-foreground: #0e0f0c;
|
|
66
|
+
--muted: #f2f4ef;
|
|
67
|
+
--muted-foreground: #7d7f7a;
|
|
68
|
+
--accent: #e2f6d5;
|
|
69
|
+
--accent-foreground: #163300;
|
|
70
|
+
--destructive: #c0242b;
|
|
71
|
+
--destructive-foreground: #ffffff;
|
|
72
|
+
--border: rgba(14, 15, 12, 0.09);
|
|
73
|
+
--input: rgba(14, 15, 12, 0.28);
|
|
74
|
+
--ring: rgba(159, 232, 112, 0.55);
|
|
75
|
+
--brand-panel: #e8ebe6;
|
|
76
|
+
|
|
77
|
+
/*
|
|
78
|
+
* Secondary text + link colours. These start life as fixed brand tokens in
|
|
79
|
+
* @acme/config, which means they never changed with the theme — #454745
|
|
80
|
+
* body text sits at ~2:1 on a dark card, and the #163300 link is effectively
|
|
81
|
+
* invisible. Redeclared here (light values identical to the brand ones, so
|
|
82
|
+
* light mode is unchanged) and overridden in .dark below.
|
|
83
|
+
* Dark values are the design's own palette from `Client App.dc.html`.
|
|
84
|
+
*/
|
|
85
|
+
--body: #454745;
|
|
86
|
+
--mute: #868685;
|
|
87
|
+
--link: #163300;
|
|
88
|
+
|
|
89
|
+
/*
|
|
90
|
+
* Status colours, same problem: the brand hexes are tuned for a white card and
|
|
91
|
+
* are muddy on a dark one. Light values match the brand tokens exactly; the
|
|
92
|
+
* dark ones are the design's own `--gain`/`--loss` (plus a lifted warning).
|
|
93
|
+
* These feed both the `text-*` and the translucent `bg-*` tint utilities.
|
|
94
|
+
*/
|
|
95
|
+
--positive: #2ead4b;
|
|
96
|
+
--negative: #d03238;
|
|
97
|
+
--warning: #ffd11a;
|
|
98
|
+
|
|
99
|
+
/*
|
|
100
|
+
* Foreground for the `bg-warning/20` pill. The fixed brand `--color-warning-deep`
|
|
101
|
+
* (#b86700) was being used here, which is the classic pairing bug: the tint
|
|
102
|
+
* follows the theme, the text does not. On a dark card it measured 2.43:1 —
|
|
103
|
+
* unreadable. These are the design's own warning-pill foregrounds
|
|
104
|
+
* (`--pill-warn-fg`), and they measure 8.8–10:1 light / 6.5–8.6:1 dark.
|
|
105
|
+
*/
|
|
106
|
+
--warning-content: #4a3b1c;
|
|
107
|
+
|
|
108
|
+
/*
|
|
109
|
+
* Active-tab underline (admin console tab bars). The design uses a literal
|
|
110
|
+
* #579b2e in both themes, which measures 2.93:1 on the light page background —
|
|
111
|
+
* under the 3:1 WCAG 1.4.11 needs for a non-text indicator. Darkened along the
|
|
112
|
+
* same hue (97.4°) to 3.55:1; the dark value is the same hue lifted for an ink
|
|
113
|
+
* canvas. Deliberately NOT `--primary`: Wise Green is the CTA accent, and an
|
|
114
|
+
* active tab is a state, not a call to action.
|
|
115
|
+
*/
|
|
116
|
+
--tab-active: #4e8b29;
|
|
117
|
+
|
|
118
|
+
/*
|
|
119
|
+
* Categorical chart palette — for encoding *identity* (which holding is which),
|
|
120
|
+
* never magnitude. Six slots because a donut stops being readable past ~6
|
|
121
|
+
* slices, and because the hue budget is genuinely that small: excluding the
|
|
122
|
+
* gain (~144°) and loss (~30°) hue neighbourhoods removes ~100° of the wheel,
|
|
123
|
+
* and what's left can't hold 8 mutually distinguishable hues that also stay
|
|
124
|
+
* clear of those two colours in both themes. Verified min pairwise ΔE 40 (light)
|
|
125
|
+
* / 36 (dark) against Tableau10's 30 as the benchmark, every slot ≥32 ΔE from
|
|
126
|
+
* `--positive`/`--negative`/`--primary` so no slice reads as a gain, a loss or
|
|
127
|
+
* a CTA, and hue drift ≤3° between themes so a slice keeps its identity on
|
|
128
|
+
* toggle. Lightness is deliberately held in a narrow band per theme so no slice
|
|
129
|
+
* outweighs another.
|
|
130
|
+
*
|
|
131
|
+
* `--chart-other` is the "and the rest" bucket: neutral on purpose, so it
|
|
132
|
+
* recedes instead of reading as another holding. Adding a 7th hue here will
|
|
133
|
+
* break the guarantees above — collapse into Other instead.
|
|
134
|
+
*/
|
|
135
|
+
/*
|
|
136
|
+
* Single magnitude hue for bar charts. Bar rows are ONE series — identity comes
|
|
137
|
+
* from the row label, so every bar is the same colour; colouring them by value
|
|
138
|
+
* would double-encode the length. Separate from `--chart-1` despite sharing the
|
|
139
|
+
* light value: that slot is tuned for small donut slices, and its dark value
|
|
140
|
+
* (#55bca4, OKLCH L 0.727) sits outside the 0.48–0.67 lightness band a dark-mode
|
|
141
|
+
* bar fill needs. Both values pass the dataviz validator on all five checks.
|
|
142
|
+
*/
|
|
143
|
+
--chart-bar: #009e84;
|
|
144
|
+
|
|
145
|
+
--chart-1: #009e84;
|
|
146
|
+
--chart-2: #0096c7;
|
|
147
|
+
--chart-3: #6767c7;
|
|
148
|
+
--chart-4: #ac7e9a;
|
|
149
|
+
--chart-5: #c27c2d;
|
|
150
|
+
--chart-6: #737548;
|
|
151
|
+
--chart-other: #8f958e;
|
|
152
|
+
|
|
153
|
+
/*
|
|
154
|
+
* Modal / drawer backdrop. Needs to be darker on a dark page to still read as
|
|
155
|
+
* a separation layer — the design specifies exactly this pair.
|
|
156
|
+
*/
|
|
157
|
+
--scrim: rgba(14, 15, 12, 0.45);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.dark {
|
|
161
|
+
--background: #0e0f0c;
|
|
162
|
+
--foreground: #f4f6f1;
|
|
163
|
+
--card: #191b16;
|
|
164
|
+
--card-foreground: #f4f6f1;
|
|
165
|
+
--popover: #191b16;
|
|
166
|
+
--popover-foreground: #f4f6f1;
|
|
167
|
+
--primary: #9fe870;
|
|
168
|
+
--primary-foreground: #0e0f0c;
|
|
169
|
+
--secondary: #23261f;
|
|
170
|
+
--secondary-foreground: #f4f6f1;
|
|
171
|
+
--muted: #23261f;
|
|
172
|
+
--muted-foreground: #8b8d88;
|
|
173
|
+
--accent: rgba(159, 232, 112, 0.15);
|
|
174
|
+
--accent-foreground: #9fe870;
|
|
175
|
+
--destructive: #ff8085;
|
|
176
|
+
--destructive-foreground: #0e0f0c;
|
|
177
|
+
--border: rgba(232, 235, 230, 0.11);
|
|
178
|
+
--input: rgba(232, 235, 230, 0.3);
|
|
179
|
+
--ring: rgba(159, 232, 112, 0.4);
|
|
180
|
+
--brand-panel: #14160f;
|
|
181
|
+
|
|
182
|
+
--body: #c4c7c0;
|
|
183
|
+
--mute: #8b8d88;
|
|
184
|
+
/* Links become the brand green on dark — ink-deep is unreadable there. */
|
|
185
|
+
--link: #9fe870;
|
|
186
|
+
|
|
187
|
+
--positive: #6fdd8b;
|
|
188
|
+
--negative: #ff8085;
|
|
189
|
+
--warning: #ffd95e;
|
|
190
|
+
/* On a dark tint the deep olive inverts to the warning colour itself. */
|
|
191
|
+
--warning-content: #ffd95e;
|
|
192
|
+
|
|
193
|
+
/* Same 97.4° hue as light, lifted for the ink canvas (9.1:1 on --background). */
|
|
194
|
+
--tab-active: #74c742;
|
|
195
|
+
|
|
196
|
+
/* Same hue as light, stepped into the dark band (see the light block for why). */
|
|
197
|
+
--chart-bar: #35a68d;
|
|
198
|
+
|
|
199
|
+
/* Same hues as light, lifted for the dark card (see the light block for why). */
|
|
200
|
+
--chart-1: #55bca4;
|
|
201
|
+
--chart-2: #43c8ff;
|
|
202
|
+
--chart-3: #a7a1fc;
|
|
203
|
+
--chart-4: #eab8d6;
|
|
204
|
+
--chart-5: #e29d56;
|
|
205
|
+
--chart-6: #c9c998;
|
|
206
|
+
--chart-other: #7f857e;
|
|
207
|
+
|
|
208
|
+
--scrim: rgba(0, 0, 0, 0.6);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
@theme inline {
|
|
212
|
+
--color-background: var(--background);
|
|
213
|
+
--color-foreground: var(--foreground);
|
|
214
|
+
--color-card: var(--card);
|
|
215
|
+
--color-card-foreground: var(--card-foreground);
|
|
216
|
+
--color-popover: var(--popover);
|
|
217
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
218
|
+
--color-primary: var(--primary);
|
|
219
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
220
|
+
--color-secondary: var(--secondary);
|
|
221
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
222
|
+
--color-muted: var(--muted);
|
|
223
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
224
|
+
--color-accent: var(--accent);
|
|
225
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
226
|
+
--color-destructive: var(--destructive);
|
|
227
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
228
|
+
--color-border: var(--border);
|
|
229
|
+
--color-input: var(--input);
|
|
230
|
+
--color-ring: var(--ring);
|
|
231
|
+
--color-brand-panel: var(--brand-panel);
|
|
232
|
+
|
|
233
|
+
/*
|
|
234
|
+
* Re-point the secondary-text utilities (`text-body`, `text-mute`,
|
|
235
|
+
* `text-ink-deep`) at the theme-aware vars above. @acme/config defines these
|
|
236
|
+
* as fixed brand hexes, and this layer wins — so `text-body` finally responds
|
|
237
|
+
* to the theme instead of staying #454745 on a dark card.
|
|
238
|
+
*/
|
|
239
|
+
--color-body: var(--body);
|
|
240
|
+
--color-mute: var(--mute);
|
|
241
|
+
--color-ink-deep: var(--link);
|
|
242
|
+
--color-positive: var(--positive);
|
|
243
|
+
--color-negative: var(--negative);
|
|
244
|
+
--color-warning: var(--warning);
|
|
245
|
+
/* Pair `bg-warning/20` with `text-warning-content`, never `text-warning-deep`. */
|
|
246
|
+
--color-warning-content: var(--warning-content);
|
|
247
|
+
/*
|
|
248
|
+
* `bg-chart-bar` for CSS bar fills. The `--chart-1..6` donut slots are
|
|
249
|
+
* deliberately NOT mapped to utilities — the donut reads them through
|
|
250
|
+
* `readVar()` in JS — so this is the only chart colour with a class.
|
|
251
|
+
*/
|
|
252
|
+
--color-chart-bar: var(--chart-bar);
|
|
253
|
+
/* Enables `border-tab-active` on the selected tab's 2px underline. */
|
|
254
|
+
--color-tab-active: var(--tab-active);
|
|
255
|
+
/* Enables `bg-scrim` — already carries its own alpha, so no /45 modifier. */
|
|
256
|
+
--color-scrim: var(--scrim);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
@layer base {
|
|
260
|
+
* {
|
|
261
|
+
border-color: var(--color-border);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/*
|
|
265
|
+
* Tells the browser which palette its own UI should use — native scrollbars,
|
|
266
|
+
* form controls, autofill backgrounds and the text caret. Without it these
|
|
267
|
+
* stay light-styled over a dark page. The theme provider also sets this
|
|
268
|
+
* inline on <html> so it flips instantly with the class.
|
|
269
|
+
*/
|
|
270
|
+
:root {
|
|
271
|
+
color-scheme: light;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.dark {
|
|
275
|
+
color-scheme: dark;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
body {
|
|
279
|
+
background-color: var(--color-background);
|
|
280
|
+
color: var(--color-foreground);
|
|
281
|
+
font-family: var(--font-sans);
|
|
282
|
+
-webkit-font-smoothing: antialiased;
|
|
283
|
+
text-rendering: optimizeLegibility;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { THEME_COLORS } from './theme-colors';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Blocking snippet for each app's `index.html`, placed in `<head>` **before**
|
|
5
|
+
* the stylesheet link.
|
|
6
|
+
*
|
|
7
|
+
* Without it the page paints light, then React mounts and flips it to dark —
|
|
8
|
+
* a visible flash on every load, worst on a slow mobile connection where the
|
|
9
|
+
* bundle takes seconds to arrive. It duplicates a few lines of `theme-context`
|
|
10
|
+
* on purpose: it must run before any module loads, so it cannot import them.
|
|
11
|
+
*
|
|
12
|
+
* The colours are interpolated from `THEME_COLORS` so this isn't another copy to
|
|
13
|
+
* keep in sync. `THEME_STORAGE_KEY` still is one — it has to be a literal here.
|
|
14
|
+
*/
|
|
15
|
+
export const themeInitScript = `(function(){try{
|
|
16
|
+
var m=localStorage.getItem('acme-theme');
|
|
17
|
+
var d=m==='dark'||((m===null||m==='system')&&matchMedia('(prefers-color-scheme: dark)').matches);
|
|
18
|
+
var r=document.documentElement;
|
|
19
|
+
r.classList.toggle('dark',d);
|
|
20
|
+
r.style.colorScheme=d?'dark':'light';
|
|
21
|
+
var t=document.querySelector('meta[name="theme-color"]');
|
|
22
|
+
if(t)t.setAttribute('content',d?'${THEME_COLORS.dark}':'${THEME_COLORS.light}');
|
|
23
|
+
}catch(e){}})();`;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The page background per theme, as a raw value.
|
|
3
|
+
*
|
|
4
|
+
* This module deliberately has **no imports**. Build-time config needs these
|
|
5
|
+
* values too — `apps/client/vite.config.ts` bakes them into the PWA manifest —
|
|
6
|
+
* and importing `theme-context` there would pull React into the Vite config.
|
|
7
|
+
* Keep it dependency-free so both the runtime and the build can read it.
|
|
8
|
+
*
|
|
9
|
+
* Must match `--background` in `globals.css` for each theme.
|
|
10
|
+
*/
|
|
11
|
+
export const THEME_COLORS = {
|
|
12
|
+
light: '#eceee9',
|
|
13
|
+
dark: '#0e0f0c'
|
|
14
|
+
} as const;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { createContext } from 'react';
|
|
2
|
+
import { THEME_COLORS } from './theme-colors';
|
|
3
|
+
|
|
4
|
+
/** What the user chose. `system` follows the OS and keeps following it. */
|
|
5
|
+
export type ThemeMode = 'system' | 'light' | 'dark';
|
|
6
|
+
/** What is actually on screen once `system` is resolved. */
|
|
7
|
+
export type ResolvedTheme = 'light' | 'dark';
|
|
8
|
+
|
|
9
|
+
export type ThemeContextValue = {
|
|
10
|
+
mode: ThemeMode;
|
|
11
|
+
resolved: ResolvedTheme;
|
|
12
|
+
setMode: (mode: ThemeMode) => void;
|
|
13
|
+
/** Cycles light → dark → system. */
|
|
14
|
+
toggle: () => void;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Shared across every app. Kept in its own module (no components) so the
|
|
19
|
+
* provider and the hook can live in separate files without tripping
|
|
20
|
+
* `react-refresh/only-export-components`.
|
|
21
|
+
*/
|
|
22
|
+
export const ThemeContext = createContext<ThemeContextValue | null>(null);
|
|
23
|
+
|
|
24
|
+
/** localStorage key. Per-origin, so each app remembers its own choice. */
|
|
25
|
+
export const THEME_STORAGE_KEY = 'acme-theme';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Re-exported so consumers keep importing theme colours from one place. The
|
|
29
|
+
* values live in `theme-colors.ts`, which has no imports so the Vite config can
|
|
30
|
+
* read them at build time (see the PWA manifest in `apps/client/vite.config.ts`).
|
|
31
|
+
*/
|
|
32
|
+
export { THEME_COLORS };
|
|
33
|
+
|
|
34
|
+
export function isThemeMode(value: unknown): value is ThemeMode {
|
|
35
|
+
return value === 'system' || value === 'light' || value === 'dark';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Read the stored preference. Safe on the server and in private-mode browsers. */
|
|
39
|
+
export function readStoredMode(): ThemeMode {
|
|
40
|
+
if (typeof window === 'undefined') return 'system';
|
|
41
|
+
try {
|
|
42
|
+
const stored = window.localStorage.getItem(THEME_STORAGE_KEY);
|
|
43
|
+
return isThemeMode(stored) ? stored : 'system';
|
|
44
|
+
} catch {
|
|
45
|
+
return 'system';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Apply a resolved theme to the document: the `dark` class that Tailwind's
|
|
51
|
+
* `@custom-variant dark` keys off, `color-scheme` so native scrollbars, form
|
|
52
|
+
* controls and autofill follow, and the PWA status-bar colour.
|
|
53
|
+
*/
|
|
54
|
+
export function applyTheme(resolved: ResolvedTheme): void {
|
|
55
|
+
if (typeof document === 'undefined') return;
|
|
56
|
+
const root = document.documentElement;
|
|
57
|
+
root.classList.toggle('dark', resolved === 'dark');
|
|
58
|
+
root.style.colorScheme = resolved;
|
|
59
|
+
document.querySelector('meta[name="theme-color"]')?.setAttribute('content', THEME_COLORS[resolved]);
|
|
60
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState, type ReactNode } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
ThemeContext,
|
|
4
|
+
THEME_STORAGE_KEY,
|
|
5
|
+
applyTheme,
|
|
6
|
+
isThemeMode,
|
|
7
|
+
readStoredMode,
|
|
8
|
+
type ResolvedTheme,
|
|
9
|
+
type ThemeMode
|
|
10
|
+
} from './theme-context';
|
|
11
|
+
|
|
12
|
+
const QUERY = '(prefers-color-scheme: dark)';
|
|
13
|
+
|
|
14
|
+
function systemTheme(): ResolvedTheme {
|
|
15
|
+
if (typeof window === 'undefined') return 'light';
|
|
16
|
+
return window.matchMedia(QUERY).matches ? 'dark' : 'light';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function resolve(mode: ThemeMode): ResolvedTheme {
|
|
20
|
+
return mode === 'system' ? systemTheme() : mode;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Theme provider for every app. Wrap the app root once, above the router.
|
|
25
|
+
*
|
|
26
|
+
* The initial class is set by the inline snippet in `index.html` (see
|
|
27
|
+
* `themeInitScript`) so there is no flash of the wrong theme before React
|
|
28
|
+
* mounts; this provider then takes over and keeps the DOM in sync.
|
|
29
|
+
*/
|
|
30
|
+
export function ThemeProvider({ children }: { children: ReactNode }) {
|
|
31
|
+
const [mode, setModeState] = useState<ThemeMode>(readStoredMode);
|
|
32
|
+
const [resolved, setResolved] = useState<ResolvedTheme>(() => resolve(readStoredMode()));
|
|
33
|
+
|
|
34
|
+
const setMode = useCallback((next: ThemeMode) => {
|
|
35
|
+
setModeState(next);
|
|
36
|
+
const nextResolved = resolve(next);
|
|
37
|
+
setResolved(nextResolved);
|
|
38
|
+
applyTheme(nextResolved);
|
|
39
|
+
try {
|
|
40
|
+
window.localStorage.setItem(THEME_STORAGE_KEY, next);
|
|
41
|
+
} catch {
|
|
42
|
+
// Private mode / storage disabled — the theme still applies for this session.
|
|
43
|
+
}
|
|
44
|
+
}, []);
|
|
45
|
+
|
|
46
|
+
// Follow the OS while in `system` mode.
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (mode !== 'system') return;
|
|
49
|
+
const media = window.matchMedia(QUERY);
|
|
50
|
+
const sync = () => {
|
|
51
|
+
const next: ResolvedTheme = media.matches ? 'dark' : 'light';
|
|
52
|
+
setResolved(next);
|
|
53
|
+
applyTheme(next);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
media.addEventListener('change', sync);
|
|
57
|
+
|
|
58
|
+
// The `change` event alone is not enough for an installed PWA. It can sit
|
|
59
|
+
// frozen for days, and the OS flipping theme while it is backgrounded — an
|
|
60
|
+
// automatic sunset switch, typically — delivers no event, so the app resumes
|
|
61
|
+
// on the stale theme until a full reload. A browser tab hides this because it
|
|
62
|
+
// gets navigated and discarded far more often. Re-read on resume as well;
|
|
63
|
+
// `pageshow` additionally covers a back/forward-cache restore.
|
|
64
|
+
const onVisible = () => {
|
|
65
|
+
if (document.visibilityState === 'visible') sync();
|
|
66
|
+
};
|
|
67
|
+
document.addEventListener('visibilitychange', onVisible);
|
|
68
|
+
window.addEventListener('pageshow', sync);
|
|
69
|
+
|
|
70
|
+
return () => {
|
|
71
|
+
media.removeEventListener('change', sync);
|
|
72
|
+
document.removeEventListener('visibilitychange', onVisible);
|
|
73
|
+
window.removeEventListener('pageshow', sync);
|
|
74
|
+
};
|
|
75
|
+
}, [mode]);
|
|
76
|
+
|
|
77
|
+
// Keep other tabs of the same app in step.
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
const onStorage = (event: StorageEvent) => {
|
|
80
|
+
if (event.key !== THEME_STORAGE_KEY || !isThemeMode(event.newValue)) return;
|
|
81
|
+
setModeState(event.newValue);
|
|
82
|
+
const next = resolve(event.newValue);
|
|
83
|
+
setResolved(next);
|
|
84
|
+
applyTheme(next);
|
|
85
|
+
};
|
|
86
|
+
window.addEventListener('storage', onStorage);
|
|
87
|
+
return () => window.removeEventListener('storage', onStorage);
|
|
88
|
+
}, []);
|
|
89
|
+
|
|
90
|
+
const value = useMemo(
|
|
91
|
+
() => ({
|
|
92
|
+
mode,
|
|
93
|
+
resolved,
|
|
94
|
+
setMode,
|
|
95
|
+
toggle: () => setMode(mode === 'light' ? 'dark' : mode === 'dark' ? 'system' : 'light')
|
|
96
|
+
}),
|
|
97
|
+
[mode, resolved, setMode]
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
return <ThemeContext value={value}>{children}</ThemeContext>;
|
|
101
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Monitor, Moon, Sun } from 'lucide-react';
|
|
2
|
+
import { cn } from '../lib/utils';
|
|
3
|
+
import { useTheme } from './use-theme';
|
|
4
|
+
import type { ThemeMode } from './theme-context';
|
|
5
|
+
|
|
6
|
+
const NEXT_LABEL: Record<ThemeMode, string> = {
|
|
7
|
+
light: 'Switch to dark theme',
|
|
8
|
+
dark: 'Switch to system theme',
|
|
9
|
+
system: 'Switch to light theme'
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const ICONS: Record<ThemeMode, React.ReactNode> = {
|
|
13
|
+
light: <Sun className="size-4" aria-hidden="true" />,
|
|
14
|
+
dark: <Moon className="size-4" aria-hidden="true" />,
|
|
15
|
+
system: <Monitor className="size-4" aria-hidden="true" />
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Single-button theme control that cycles light → dark → system. The icon shows
|
|
20
|
+
* the *current* mode, while the accessible name says what pressing it will do —
|
|
21
|
+
* an icon-only control has to announce its action, not just its state.
|
|
22
|
+
*/
|
|
23
|
+
export function ThemeToggle({ className }: { className?: string }) {
|
|
24
|
+
const { mode, toggle } = useTheme();
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<button
|
|
28
|
+
type="button"
|
|
29
|
+
onClick={toggle}
|
|
30
|
+
title={NEXT_LABEL[mode]}
|
|
31
|
+
aria-label={NEXT_LABEL[mode]}
|
|
32
|
+
className={cn(
|
|
33
|
+
'flex size-9 items-center justify-center rounded-full bg-secondary text-foreground transition-colors hover:bg-accent',
|
|
34
|
+
className
|
|
35
|
+
)}
|
|
36
|
+
>
|
|
37
|
+
{ICONS[mode]}
|
|
38
|
+
</button>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { useContext } from 'react';
|
|
2
|
+
import { ThemeContext, type ThemeContextValue } from './theme-context';
|
|
3
|
+
|
|
4
|
+
/** Current theme + setters. Throws if used outside <ThemeProvider>. */
|
|
5
|
+
export function useTheme(): ThemeContextValue {
|
|
6
|
+
const value = useContext(ThemeContext);
|
|
7
|
+
if (!value) {
|
|
8
|
+
throw new Error('useTheme must be used inside a <ThemeProvider>.');
|
|
9
|
+
}
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://turbo.build/schema.json",
|
|
3
|
+
"ui": "tui",
|
|
4
|
+
"globalDependencies": [".env", "tsconfig.base.json"],
|
|
5
|
+
"tasks": {
|
|
6
|
+
"build": {
|
|
7
|
+
"dependsOn": ["^build"],
|
|
8
|
+
"outputs": ["dist/**"]
|
|
9
|
+
},
|
|
10
|
+
"lint": {
|
|
11
|
+
"dependsOn": ["^build"]
|
|
12
|
+
},
|
|
13
|
+
"typecheck": {
|
|
14
|
+
"dependsOn": ["^build"]
|
|
15
|
+
},
|
|
16
|
+
"dev": {
|
|
17
|
+
"cache": false,
|
|
18
|
+
"persistent": true
|
|
19
|
+
},
|
|
20
|
+
"secrets": {
|
|
21
|
+
"cache": false
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|