@storyshelf/app 0.6.2 → 0.8.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/README.md +42 -4
- package/dist/app-types.d.mts +2 -6
- package/dist/asset-manifest.mjs +14 -7
- package/dist/asset-manifest.mjs.map +1 -1
- package/dist/assets/{htmx.min.js → htmx.mjs} +9 -1
- package/dist/assets/htmx.mjs.map +1 -0
- package/dist/assets/mark.mjs +9 -0
- package/dist/assets/mark.mjs.map +1 -0
- package/dist/boot-tasks.mjs +79 -0
- package/dist/boot-tasks.mjs.map +1 -0
- package/dist/build-state-flags.mjs +17 -0
- package/dist/build-state-flags.mjs.map +1 -0
- package/dist/build-state-setup.mjs +74 -0
- package/dist/build-state-setup.mjs.map +1 -0
- package/dist/capture-alerts.mjs +45 -0
- package/dist/capture-alerts.mjs.map +1 -0
- package/dist/capture-process.mjs +58 -0
- package/dist/capture-process.mjs.map +1 -0
- package/dist/capture-setup.mjs +96 -119
- package/dist/capture-setup.mjs.map +1 -1
- package/dist/connections.mjs +33 -0
- package/dist/connections.mjs.map +1 -0
- package/dist/consumer-setup.mjs +57 -0
- package/dist/consumer-setup.mjs.map +1 -0
- package/dist/credential-keys.mjs +1 -2
- package/dist/credentials.mjs +1 -2
- package/dist/git-status.mjs +45 -0
- package/dist/git-status.mjs.map +1 -0
- package/dist/hidden-data.mjs +23 -0
- package/dist/hidden-data.mjs.map +1 -0
- package/dist/index.d.mts +3 -5
- package/dist/index.mjs +204 -127
- package/dist/index.mjs.map +1 -1
- package/dist/insights/budget.mjs +48 -0
- package/dist/insights/budget.mjs.map +1 -0
- package/dist/insights/deps.mjs +20 -0
- package/dist/insights/deps.mjs.map +1 -0
- package/dist/insights/errors.mjs +21 -0
- package/dist/insights/errors.mjs.map +1 -0
- package/dist/insights/health-panel.mjs +39 -0
- package/dist/insights/health-panel.mjs.map +1 -0
- package/dist/insights/job.mjs +102 -0
- package/dist/insights/job.mjs.map +1 -0
- package/dist/insights/load-health.mjs +58 -0
- package/dist/insights/load-health.mjs.map +1 -0
- package/dist/insights/load-triage.mjs +97 -0
- package/dist/insights/load-triage.mjs.map +1 -0
- package/dist/insights/panel.mjs +27 -0
- package/dist/insights/panel.mjs.map +1 -0
- package/dist/insights/request.mjs +112 -0
- package/dist/insights/request.mjs.map +1 -0
- package/dist/insights/stale.mjs +20 -0
- package/dist/insights/stale.mjs.map +1 -0
- package/dist/insights/usage-report.mjs +59 -0
- package/dist/insights/usage-report.mjs.map +1 -0
- package/dist/insights/view.mjs +47 -0
- package/dist/insights/view.mjs.map +1 -0
- package/dist/job-canceller.mjs +13 -0
- package/dist/job-canceller.mjs.map +1 -0
- package/dist/job-queue.mjs +50 -0
- package/dist/job-queue.mjs.map +1 -0
- package/dist/jobs-setup.mjs +131 -0
- package/dist/jobs-setup.mjs.map +1 -0
- package/dist/lifecycle.mjs +93 -22
- package/dist/lifecycle.mjs.map +1 -1
- package/dist/middleware/auth-gate.mjs +5 -4
- package/dist/middleware/auth-gate.mjs.map +1 -1
- package/dist/middleware/csrf.mjs +1 -2
- package/dist/middleware/rate-limit.mjs +42 -35
- package/dist/middleware/rate-limit.mjs.map +1 -1
- package/dist/middleware/request-log.mjs +4 -4
- package/dist/middleware/request-log.mjs.map +1 -1
- package/dist/middleware/require-review.mjs +50 -0
- package/dist/middleware/require-review.mjs.map +1 -0
- package/dist/middleware/require-testing.mjs +40 -0
- package/dist/middleware/require-testing.mjs.map +1 -0
- package/dist/middleware/server-timing.mjs +1 -2
- package/dist/middleware/setup-gate.mjs +1 -2
- package/dist/middleware/setup-gate.mjs.map +1 -1
- package/dist/middleware/store-scope.mjs +13 -4
- package/dist/middleware/store-scope.mjs.map +1 -1
- package/dist/notifications-pause.mjs +32 -0
- package/dist/notifications-pause.mjs.map +1 -0
- package/dist/notify.d.mts +3 -4
- package/dist/notify.mjs +79 -41
- package/dist/notify.mjs.map +1 -1
- package/dist/pages/a11y-violations.mjs +115 -0
- package/dist/pages/a11y-violations.mjs.map +1 -0
- package/dist/pages/admin-ai.mjs +77 -0
- package/dist/pages/admin-ai.mjs.map +1 -0
- package/dist/pages/admin-system-origin.mjs +132 -0
- package/dist/pages/admin-system-origin.mjs.map +1 -0
- package/dist/pages/admin-system-targets.mjs +52 -0
- package/dist/pages/admin-system-targets.mjs.map +1 -0
- package/dist/pages/admin-system.mjs +64 -15
- package/dist/pages/admin-system.mjs.map +1 -1
- package/dist/pages/build-detail.mjs +105 -78
- package/dist/pages/build-detail.mjs.map +1 -1
- package/dist/pages/build-diff-actions.mjs +88 -35
- package/dist/pages/build-diff-actions.mjs.map +1 -1
- package/dist/pages/build-diff-comments.mjs +17 -16
- package/dist/pages/build-diff-comments.mjs.map +1 -1
- package/dist/pages/build-diff-default.mjs +53 -0
- package/dist/pages/build-diff-default.mjs.map +1 -0
- package/dist/pages/build-diff-environment.mjs +81 -0
- package/dist/pages/build-diff-environment.mjs.map +1 -0
- package/dist/pages/build-diff-header.mjs +23 -6
- package/dist/pages/build-diff-header.mjs.map +1 -1
- package/dist/pages/build-diff-insight.mjs +112 -0
- package/dist/pages/build-diff-insight.mjs.map +1 -0
- package/dist/pages/build-diff-model.mjs +74 -12
- package/dist/pages/build-diff-model.mjs.map +1 -1
- package/dist/pages/build-diff-nav.mjs +29 -14
- package/dist/pages/build-diff-nav.mjs.map +1 -1
- package/dist/pages/build-diff-reference.mjs +84 -0
- package/dist/pages/build-diff-reference.mjs.map +1 -0
- package/dist/pages/build-diff-shortcuts.mjs +6 -3
- package/dist/pages/build-diff-shortcuts.mjs.map +1 -1
- package/dist/pages/build-diff-stale.mjs +1 -2
- package/dist/pages/build-diff-usage.mjs +62 -0
- package/dist/pages/build-diff-usage.mjs.map +1 -0
- package/dist/pages/build-diff-viewer.mjs +97 -14
- package/dist/pages/build-diff-viewer.mjs.map +1 -1
- package/dist/pages/build-diff.mjs +37 -22
- package/dist/pages/build-diff.mjs.map +1 -1
- package/dist/pages/build-tests.mjs +73 -0
- package/dist/pages/build-tests.mjs.map +1 -0
- package/dist/pages/component-detail.mjs +248 -0
- package/dist/pages/component-detail.mjs.map +1 -0
- package/dist/pages/component-stories.mjs +167 -0
- package/dist/pages/component-stories.mjs.map +1 -0
- package/dist/pages/compute-jobs.mjs +14 -28
- package/dist/pages/compute-jobs.mjs.map +1 -1
- package/dist/pages/design-links.mjs +181 -0
- package/dist/pages/design-links.mjs.map +1 -0
- package/dist/pages/environment-change.mjs +52 -0
- package/dist/pages/environment-change.mjs.map +1 -0
- package/dist/pages/health-panel.mjs +125 -0
- package/dist/pages/health-panel.mjs.map +1 -0
- package/dist/pages/invite.mjs +1 -2
- package/dist/pages/label-detail.mjs +1 -2
- package/dist/pages/library-grid.mjs +104 -0
- package/dist/pages/library-grid.mjs.map +1 -0
- package/dist/pages/library-model.mjs +46 -0
- package/dist/pages/library-model.mjs.map +1 -0
- package/dist/pages/library-snapshots.mjs +129 -0
- package/dist/pages/library-snapshots.mjs.map +1 -0
- package/dist/pages/library.mjs +113 -220
- package/dist/pages/library.mjs.map +1 -1
- package/dist/pages/login.mjs +1 -2
- package/dist/pages/notifications-paused.mjs +21 -0
- package/dist/pages/notifications-paused.mjs.map +1 -0
- package/dist/pages/passkey-ceremony.mjs +1 -2
- package/dist/pages/profile.mjs +54 -50
- package/dist/pages/profile.mjs.map +1 -1
- package/dist/pages/project-builds-table.mjs +3 -4
- package/dist/pages/project-builds-table.mjs.map +1 -1
- package/dist/pages/project-builds.mjs +52 -23
- package/dist/pages/project-builds.mjs.map +1 -1
- package/dist/pages/project-create.mjs +1 -2
- package/dist/pages/project-settings.mjs +52 -14
- package/dist/pages/project-settings.mjs.map +1 -1
- package/dist/pages/projects-card.mjs +15 -3
- package/dist/pages/projects-card.mjs.map +1 -1
- package/dist/pages/projects.mjs +7 -4
- package/dist/pages/projects.mjs.map +1 -1
- package/dist/pages/review-changeset.mjs +115 -0
- package/dist/pages/review-changeset.mjs.map +1 -0
- package/dist/pages/review-comments.mjs +102 -0
- package/dist/pages/review-comments.mjs.map +1 -0
- package/dist/pages/review-verdict.mjs +101 -0
- package/dist/pages/review-verdict.mjs.map +1 -0
- package/dist/pages/root.mjs +1 -2
- package/dist/pages/settings-ai.mjs +68 -0
- package/dist/pages/settings-ai.mjs.map +1 -0
- package/dist/pages/settings-capture.mjs +123 -0
- package/dist/pages/settings-capture.mjs.map +1 -0
- package/dist/pages/settings-design-credential.mjs +98 -0
- package/dist/pages/settings-design-credential.mjs.map +1 -0
- package/dist/pages/settings-design-links.mjs +178 -0
- package/dist/pages/settings-design-links.mjs.map +1 -0
- package/dist/pages/settings-design.mjs +47 -0
- package/dist/pages/settings-design.mjs.map +1 -0
- package/dist/pages/settings-general.mjs +47 -23
- package/dist/pages/settings-general.mjs.map +1 -1
- package/dist/pages/settings-labels.mjs +44 -4
- package/dist/pages/settings-labels.mjs.map +1 -1
- package/dist/pages/settings-links.mjs +111 -0
- package/dist/pages/settings-links.mjs.map +1 -0
- package/dist/pages/settings-members.mjs +1 -2
- package/dist/pages/settings-notifications-git.mjs +127 -0
- package/dist/pages/settings-notifications-git.mjs.map +1 -0
- package/dist/pages/settings-notifications.mjs +80 -44
- package/dist/pages/settings-notifications.mjs.map +1 -1
- package/dist/pages/settings-tests.mjs +34 -4
- package/dist/pages/settings-tests.mjs.map +1 -1
- package/dist/pages/settings-tokens.mjs +1 -2
- package/dist/pages/settings-webhooks.mjs +1 -2
- package/dist/pages/storybook.mjs +1 -2
- package/dist/pages/target-choices.mjs +38 -0
- package/dist/pages/target-choices.mjs.map +1 -0
- package/dist/publish-setup.mjs +88 -0
- package/dist/publish-setup.mjs.map +1 -0
- package/dist/retention-timer.mjs +12 -12
- package/dist/retention-timer.mjs.map +1 -1
- package/dist/review-events.mjs +47 -0
- package/dist/review-events.mjs.map +1 -0
- package/dist/routers/admin-pages.mjs +46 -5
- package/dist/routers/admin-pages.mjs.map +1 -1
- package/dist/routers/admin-public-origin.mjs +92 -0
- package/dist/routers/admin-public-origin.mjs.map +1 -0
- package/dist/routers/admin.mjs +8 -4
- package/dist/routers/admin.mjs.map +1 -1
- package/dist/routers/assets.mjs +10 -4
- package/dist/routers/assets.mjs.map +1 -1
- package/dist/routers/attempts.mjs +1 -2
- package/dist/routers/auth-sync.mjs +1 -2
- package/dist/routers/auth.mjs +2 -3
- package/dist/routers/baseline-guard.mjs +4 -5
- package/dist/routers/baseline-guard.mjs.map +1 -1
- package/dist/routers/baseline-reference.mjs +72 -0
- package/dist/routers/baseline-reference.mjs.map +1 -0
- package/dist/routers/baselines.handlers.mjs +63 -0
- package/dist/routers/baselines.handlers.mjs.map +1 -0
- package/dist/routers/baselines.mjs +124 -0
- package/dist/routers/baselines.mjs.map +1 -0
- package/dist/routers/build-cancel.mjs +67 -0
- package/dist/routers/build-cancel.mjs.map +1 -0
- package/dist/routers/build-changeset.mjs +69 -0
- package/dist/routers/build-changeset.mjs.map +1 -0
- package/dist/routers/build-reviews.handlers.mjs +85 -0
- package/dist/routers/build-reviews.handlers.mjs.map +1 -0
- package/dist/routers/build-reviews.mjs +73 -0
- package/dist/routers/build-reviews.mjs.map +1 -0
- package/dist/routers/builds.handlers.mjs +65 -60
- package/dist/routers/builds.handlers.mjs.map +1 -1
- package/dist/routers/builds.mjs +77 -19
- package/dist/routers/builds.mjs.map +1 -1
- package/dist/routers/capture-cache.mjs +29 -0
- package/dist/routers/capture-cache.mjs.map +1 -0
- package/dist/routers/capture-config.mjs +34 -0
- package/dist/routers/capture-config.mjs.map +1 -0
- package/dist/routers/comments.mjs +42 -17
- package/dist/routers/comments.mjs.map +1 -1
- package/dist/routers/comparison-image.mjs +29 -0
- package/dist/routers/comparison-image.mjs.map +1 -0
- package/dist/routers/component-catalog.handlers.mjs +70 -0
- package/dist/routers/component-catalog.handlers.mjs.map +1 -0
- package/dist/routers/component.handlers.mjs +120 -0
- package/dist/routers/component.handlers.mjs.map +1 -0
- package/dist/routers/connections.mjs +117 -0
- package/dist/routers/connections.mjs.map +1 -0
- package/dist/routers/design-connect.handlers.mjs +43 -0
- package/dist/routers/design-connect.handlers.mjs.map +1 -0
- package/dist/routers/design.handlers.mjs +113 -0
- package/dist/routers/design.handlers.mjs.map +1 -0
- package/dist/routers/flash.mjs +1 -2
- package/dist/routers/health-report.mjs +3 -5
- package/dist/routers/health-report.mjs.map +1 -1
- package/dist/routers/health.mjs +5 -5
- package/dist/routers/health.mjs.map +1 -1
- package/dist/routers/helpers.mjs +20 -5
- package/dist/routers/helpers.mjs.map +1 -1
- package/dist/routers/htmx.mjs +1 -2
- package/dist/routers/insights-ui.mjs +63 -0
- package/dist/routers/insights-ui.mjs.map +1 -0
- package/dist/routers/insights.handlers.mjs +44 -0
- package/dist/routers/insights.handlers.mjs.map +1 -0
- package/dist/routers/insights.mjs +188 -0
- package/dist/routers/insights.mjs.map +1 -0
- package/dist/routers/labels.mjs +1 -2
- package/dist/routers/links.handlers.mjs +79 -0
- package/dist/routers/links.handlers.mjs.map +1 -0
- package/dist/routers/links.mjs +138 -0
- package/dist/routers/links.mjs.map +1 -0
- package/dist/routers/media.mjs +30 -7
- package/dist/routers/media.mjs.map +1 -1
- package/dist/routers/members.mjs +3 -3
- package/dist/routers/members.mjs.map +1 -1
- package/dist/routers/notification-channels.mjs +18 -25
- package/dist/routers/notification-channels.mjs.map +1 -1
- package/dist/routers/notification-subscriptions.mjs +1 -2
- package/dist/routers/oauth-state.mjs +44 -0
- package/dist/routers/oauth-state.mjs.map +1 -0
- package/dist/routers/preview-base.mjs +37 -0
- package/dist/routers/preview-base.mjs.map +1 -0
- package/dist/routers/preview-token.mjs +50 -0
- package/dist/routers/preview-token.mjs.map +1 -0
- package/dist/routers/profile.mjs +4 -3
- package/dist/routers/profile.mjs.map +1 -1
- package/dist/routers/projects.mjs +18 -3
- package/dist/routers/projects.mjs.map +1 -1
- package/dist/routers/publish-upload.mjs +85 -0
- package/dist/routers/publish-upload.mjs.map +1 -0
- package/dist/routers/published-access.mjs +51 -0
- package/dist/routers/published-access.mjs.map +1 -0
- package/dist/routers/published-compression.mjs +109 -0
- package/dist/routers/published-compression.mjs.map +1 -0
- package/dist/routers/published-files.mjs +69 -0
- package/dist/routers/published-files.mjs.map +1 -0
- package/dist/routers/review-forms.mjs +71 -0
- package/dist/routers/review-forms.mjs.map +1 -0
- package/dist/routers/review-next.mjs +1 -2
- package/dist/routers/review-section.handlers.mjs +84 -0
- package/dist/routers/review-section.handlers.mjs.map +1 -0
- package/dist/routers/review-switch-off.mjs +28 -0
- package/dist/routers/review-switch-off.mjs.map +1 -0
- package/dist/routers/schemas.mjs +141 -24
- package/dist/routers/schemas.mjs.map +1 -1
- package/dist/routers/scoped-models.mjs +21 -0
- package/dist/routers/scoped-models.mjs.map +1 -0
- package/dist/routers/settings-ai.mjs +34 -0
- package/dist/routers/settings-ai.mjs.map +1 -0
- package/dist/routers/settings-capture.mjs +76 -0
- package/dist/routers/settings-capture.mjs.map +1 -0
- package/dist/routers/settings-design.mjs +102 -0
- package/dist/routers/settings-design.mjs.map +1 -0
- package/dist/routers/settings-general.mjs +56 -12
- package/dist/routers/settings-general.mjs.map +1 -1
- package/dist/routers/settings-git-channel.mjs +76 -0
- package/dist/routers/settings-git-channel.mjs.map +1 -0
- package/dist/routers/settings-labels.mjs +28 -3
- package/dist/routers/settings-labels.mjs.map +1 -1
- package/dist/routers/settings-links.mjs +62 -0
- package/dist/routers/settings-links.mjs.map +1 -0
- package/dist/routers/settings-members.mjs +5 -9
- package/dist/routers/settings-members.mjs.map +1 -1
- package/dist/routers/settings-notifications.mjs +16 -12
- package/dist/routers/settings-notifications.mjs.map +1 -1
- package/dist/routers/settings-tokens.mjs +3 -7
- package/dist/routers/settings-tokens.mjs.map +1 -1
- package/dist/routers/settings-webhooks.mjs +8 -26
- package/dist/routers/settings-webhooks.mjs.map +1 -1
- package/dist/routers/settings.handlers.mjs +108 -28
- package/dist/routers/settings.handlers.mjs.map +1 -1
- package/dist/routers/settings.mjs +17 -6
- package/dist/routers/settings.mjs.map +1 -1
- package/dist/routers/signed-storybook.mjs +55 -0
- package/dist/routers/signed-storybook.mjs.map +1 -0
- package/dist/routers/snapshot-groups.mjs +62 -0
- package/dist/routers/snapshot-groups.mjs.map +1 -0
- package/dist/routers/snapshots.mjs +104 -7
- package/dist/routers/snapshots.mjs.map +1 -1
- package/dist/routers/storybook.mjs +57 -104
- package/dist/routers/storybook.mjs.map +1 -1
- package/dist/routers/tokens.mjs +3 -7
- package/dist/routers/tokens.mjs.map +1 -1
- package/dist/routers/ui.mjs +38 -7
- package/dist/routers/ui.mjs.map +1 -1
- package/dist/routers/webhooks.mjs +6 -13
- package/dist/routers/webhooks.mjs.map +1 -1
- package/dist/routers/well-known.mjs +12 -6
- package/dist/routers/well-known.mjs.map +1 -1
- package/dist/routes/api-routes.mjs +92 -0
- package/dist/routes/api-routes.mjs.map +1 -0
- package/dist/routes/classification.mjs +37 -0
- package/dist/routes/classification.mjs.map +1 -0
- package/dist/routes/openapi-classes.mjs +21 -0
- package/dist/routes/openapi-classes.mjs.map +1 -0
- package/dist/routes/page-routes.mjs +103 -0
- package/dist/routes/page-routes.mjs.map +1 -0
- package/dist/runtime.mjs +19 -15
- package/dist/runtime.mjs.map +1 -1
- package/dist/serverless.mjs +4 -0
- package/dist/store.mjs +20 -3
- package/dist/store.mjs.map +1 -1
- package/dist/targets.mjs +45 -0
- package/dist/targets.mjs.map +1 -0
- package/dist/ui/avatar.mjs +1 -2
- package/dist/ui/buttons.mjs +17 -5
- package/dist/ui/buttons.mjs.map +1 -1
- package/dist/ui/code-block.mjs +1 -2
- package/dist/ui/compare-stage.mjs +86 -54
- package/dist/ui/compare-stage.mjs.map +1 -1
- package/dist/ui/csrf-field.mjs +1 -2
- package/dist/ui/css.mjs +1 -2
- package/dist/ui/design-reference.mjs +96 -0
- package/dist/ui/design-reference.mjs.map +1 -0
- package/dist/ui/document.mjs +2 -4
- package/dist/ui/document.mjs.map +1 -1
- package/dist/ui/dropdown.mjs +1 -2
- package/dist/ui/feedback.mjs +1 -2
- package/dist/ui/filter-input.mjs +1 -2
- package/dist/ui/forms.mjs +7 -7
- package/dist/ui/forms.mjs.map +1 -1
- package/dist/ui/icons/icon.mjs +1 -2
- package/dist/ui/icons/paths.mjs +2 -2
- package/dist/ui/icons/paths.mjs.map +1 -1
- package/dist/ui/icons/sprite.mjs +1 -2
- package/dist/ui/kbd.mjs +1 -2
- package/dist/ui/layout.mjs +1 -2
- package/dist/ui/preview-embed.mjs +81 -0
- package/dist/ui/preview-embed.mjs.map +1 -0
- package/dist/ui/progress.mjs +1 -2
- package/dist/ui/segmented.mjs +1 -2
- package/dist/ui/shell/account.mjs +1 -2
- package/dist/ui/shell/bare.mjs +1 -2
- package/dist/ui/shell/boot-script.mjs +1 -2
- package/dist/ui/shell/client-script.mjs +86 -2
- package/dist/ui/shell/client-script.mjs.map +1 -1
- package/dist/ui/shell/nav.mjs +13 -10
- package/dist/ui/shell/nav.mjs.map +1 -1
- package/dist/ui/shell/review-script.mjs +25 -3
- package/dist/ui/shell/review-script.mjs.map +1 -1
- package/dist/ui/shell/sidebar.mjs +3 -4
- package/dist/ui/shell/sidebar.mjs.map +1 -1
- package/dist/ui/shell/topbar.mjs +1 -2
- package/dist/ui/stacks.mjs +1 -2
- package/dist/ui/styles/base.mjs +1 -2
- package/dist/ui/styles/behavior.mjs +1 -2
- package/dist/ui/styles/review-layout.mjs +1 -2
- package/dist/ui/styles/review.mjs +1 -2
- package/dist/ui/styles/tokens.mjs +1 -2
- package/dist/ui/styles.mjs +1 -2
- package/dist/ui/subnav.mjs +1 -2
- package/dist/ui/table.mjs +1 -2
- package/dist/ui/theme.mjs +1 -2
- package/dist/ui/thumbnail.mjs +1 -2
- package/dist/ui/time.mjs +1 -2
- package/package.json +14 -12
- package/dist/asset-paths.mjs +0 -23
- package/dist/asset-paths.mjs.map +0 -1
- package/dist/assets/mark.svg +0 -1
- package/dist/pages/settings-status.mjs +0 -137
- package/dist/pages/settings-status.mjs.map +0 -1
- package/dist/routers/settings-status.mjs +0 -77
- package/dist/routers/settings-status.mjs.map +0 -1
- package/dist/routers/status-configs.mjs +0 -108
- package/dist/routers/status-configs.mjs.map +0 -1
package/dist/store.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.mjs","names":[],"sources":["../src/store.ts"],"sourcesContent":["import type { CaptureQueue } from \"@storyshelf/core/adapter/capture-queue\";\nimport type { DatabaseAdapter } from \"@storyshelf/core/adapter/database\";\nimport type {
|
|
1
|
+
{"version":3,"file":"store.mjs","names":[],"sources":["../src/store.ts"],"sourcesContent":["import type { CaptureQueue } from \"@storyshelf/core/adapter/capture-queue\";\nimport type { CaptureRunner } from \"@storyshelf/core/adapter/capture-runner\";\nimport type { DatabaseAdapter } from \"@storyshelf/core/adapter/database\";\nimport type { DesignProvider } from \"@storyshelf/core/adapter/design\";\nimport type { NotifierProvider } from \"@storyshelf/core/adapter/notifier\";\nimport type { StorageAdapter } from \"@storyshelf/core/adapter/storage\";\nimport type { Ai } from \"@storyshelf/core/ai\";\nimport {\n createCatalogLoader,\n type BuildStatePublisher,\n type CatalogLoader,\n} from \"@storyshelf/core/capture\";\nimport type { NotificationsConfig, ShelfConfig, UIConfig } from \"@storyshelf/core/config\";\nimport type { Logger } from \"@storyshelf/core/logger\";\nimport type { PublicOriginResolver } from \"@storyshelf/core/models\";\nimport type { AuthUser } from \"@storyshelf/core/types\";\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport type { PublishService } from \"./publish-setup.ts\";\n\n/** Request-scoped dependencies and session state. */\nexport interface Store {\n db: DatabaseAdapter;\n storage: StorageAdapter;\n config: ShelfConfig;\n ui: UIConfig;\n logger: Logger;\n user: AuthUser | null;\n authEnabled: boolean;\n /** Raw session cookie value (\"default\" when anonymous); binds CSRF tokens. */\n sessionId: string;\n /** `options.testing` is set: capture, compare and baselines run here (ADR 0006). */\n testing: boolean;\n enqueueCapture?: (buildId: string, reqId?: string) => Promise<void>;\n captureQueue?: CaptureQueue | null;\n /** This instance runs capture jobs itself, so retry and the compute-jobs controls apply. */\n capturesHere?: boolean;\n /** Publishing: completion and the `publish` job queue (absent in hand-built stores). */\n publish?: PublishService;\n /** Cancels the unfinished jobs of a build, of every kind (absent in hand-built stores). */\n cancelBuildJobs?: (buildId: string) => Promise<boolean>;\n /** Shows a build's state on its git channels (absent in hand-built stores: delivered inline). */\n buildState?: BuildStatePublisher;\n /** Explicit `testing.concurrency`; unset means sized from the hardware. */\n captureConcurrency?: number;\n /** The capture runner (`options.testing.runner`); its targets decide which browsers a project may pick. */\n runner?: Pick<CaptureRunner, \"metadata\"> | undefined;\n /** The public origin: configured, detected from the platform, or reported by an admin (ADR 0007). */\n publicOrigin?: PublicOriginResolver | undefined;\n /** Design providers (Figma, …); empty when none is wired. */\n design: DesignProvider[];\n notifiers: NotifierProvider[];\n /** Brand/sender settings for outgoing notifications (`notifications.config`). */\n notificationsConfig?: NotificationsConfig;\n /** AI insights surface; absent ⇒ AI off site-wide. */\n ai?: Ai;\n}\n\nconst storage = new AsyncLocalStorage<Store>();\n\n/** Run a function with the given request store in scope. */\nexport function runWithStore<T>(store: Store, fn: () => T): T {\n return storage.run(store, fn);\n}\n\n/** Return the current request store, throwing outside a request scope. */\nexport function getStore(): Store {\n const store = storage.getStore();\n if (!store) {\n throw new Error(\"Store not available. Use runWithStore().\");\n }\n return store;\n}\n\n/**\n * Whether this instance has `testing` (`options.testing` is set): it captures and compares builds,\n * so pages layer snapshot data (screenshots, statuses, design links) onto what the catalog gives\n * them. Without it the same pages render from the published Storybook alone, and the routes\n * classified `testing` answer 404 (`requireTesting`).\n */\nexport function testingEnabled(store: Store = getStore()): boolean {\n return store.testing;\n}\n\n/** The catalog loader pages read; hand-built stores without one get an uncached loader. */\nexport function catalogOf(store: Store = getStore()): CatalogLoader {\n return (\n store.publish?.catalog ??\n createCatalogLoader({ db: store.db, storage: store.storage, logger: store.logger })\n );\n}\n"],"mappings":";;;;;;AAyDA,MAAM,UAAU,IAAI,kBAAyB;;AAG7C,SAAgB,aAAgB,OAAc,IAAgB;CAC5D,OAAO,QAAQ,IAAI,OAAO,EAAE;AAC9B;;AAGA,SAAgB,WAAkB;CAChC,MAAM,QAAQ,QAAQ,SAAS;CAC/B,IAAI,CAAC,OACH,MAAM,IAAI,MAAM,0CAA0C;CAE5D,OAAO;AACT;;;;;;;AAQA,SAAgB,eAAe,QAAe,SAAS,GAAY;CACjE,OAAO,MAAM;AACf;;AAGA,SAAgB,UAAU,QAAe,SAAS,GAAkB;CAClE,OACE,MAAM,SAAS,WACf,oBAAoB;EAAE,IAAI,MAAM;EAAI,SAAS,MAAM;EAAS,QAAQ,MAAM;CAAO,CAAC;AAEtF"}
|
package/dist/targets.mjs
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { getStore } from "./store.mjs";
|
|
5
|
+
import { MAX_BROWSERS, assertSupportedBrowsers, offeredTargets } from "@storyshelf/core/capture";
|
|
6
|
+
import { HTTPException } from "hono/http-exception";
|
|
7
|
+
import { isCaptureTarget, parseTarget } from "@storyshelf/core/adapter/capture-runner";
|
|
8
|
+
// src/targets.ts
|
|
9
|
+
/** Which capture targets this instance offers right now, and the checks a settings save runs (ADR 0007). */
|
|
10
|
+
/**
|
|
11
|
+
* The targets the runner offers, with the ones that need a URL marked `unavailable` while there is
|
|
12
|
+
* no `secret` or public origin. `undefined` when there is no runner or it publishes no list: any
|
|
13
|
+
* target is accepted, as before.
|
|
14
|
+
*/
|
|
15
|
+
async function runnerTargets(store = getStore()) {
|
|
16
|
+
if (store.runner === void 0) return;
|
|
17
|
+
return offeredTargets(store.runner, {
|
|
18
|
+
publicOrigin: await store.publicOrigin?.url(),
|
|
19
|
+
hasSecret: store.config.secret !== void 0
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/** Engine label for a target (`Chromium`), plus the remote's name (`Chromium on grid`). */
|
|
23
|
+
function targetLabel(target) {
|
|
24
|
+
const { engine, name } = parseTarget(target);
|
|
25
|
+
const label = engine === "webkit" ? "WebKit" : `${engine[0]?.toUpperCase()}${engine.slice(1)}`;
|
|
26
|
+
return name === void 0 ? label : `${label} on ${name}`;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Refuse, with 400 and the reason, a target list the runner cannot render (not offered, or
|
|
30
|
+
* unavailable), or one that is malformed or too long. Runs when a setting is saved, so a build
|
|
31
|
+
* never fails one by one with an unexplained error.
|
|
32
|
+
*/
|
|
33
|
+
async function assertTargetsSelectable(targets, current = []) {
|
|
34
|
+
const bad = targets.find((target) => !isCaptureTarget(target));
|
|
35
|
+
if (bad !== void 0) throw new HTTPException(400, { message: `Invalid capture target "${bad}"` });
|
|
36
|
+
if (targets.length > MAX_BROWSERS) throw new HTTPException(400, { message: `Choose at most ${MAX_BROWSERS} browsers` });
|
|
37
|
+
try {
|
|
38
|
+
assertSupportedBrowsers(targets.filter((target) => !current.includes(target)), await runnerTargets());
|
|
39
|
+
} catch (error) {
|
|
40
|
+
throw new HTTPException(400, { message: error instanceof Error ? error.message : "Refused" });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export { assertTargetsSelectable, runnerTargets, targetLabel };
|
|
44
|
+
|
|
45
|
+
//# sourceMappingURL=targets.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"targets.mjs","names":[],"sources":["../src/targets.ts"],"sourcesContent":["/** Which capture targets this instance offers right now, and the checks a settings save runs (ADR 0007). */\nimport {\n isCaptureTarget,\n parseTarget,\n type CaptureTarget,\n type RunnerTarget,\n} from \"@storyshelf/core/adapter/capture-runner\";\nimport { MAX_BROWSERS, assertSupportedBrowsers, offeredTargets } from \"@storyshelf/core/capture\";\nimport { HTTPException } from \"hono/http-exception\";\nimport { getStore, type Store } from \"./store.ts\";\n\n/**\n * The targets the runner offers, with the ones that need a URL marked `unavailable` while there is\n * no `secret` or public origin. `undefined` when there is no runner or it publishes no list: any\n * target is accepted, as before.\n */\nexport async function runnerTargets(\n store: Store = getStore(),\n): Promise<readonly RunnerTarget[] | undefined> {\n if (store.runner === undefined) {\n return undefined;\n }\n return offeredTargets(store.runner, {\n publicOrigin: await store.publicOrigin?.url(),\n hasSecret: store.config.secret !== undefined,\n });\n}\n\n/** Engine label for a target (`Chromium`), plus the remote's name (`Chromium on grid`). */\nexport function targetLabel(target: CaptureTarget): string {\n const { engine, name } = parseTarget(target);\n const label = engine === \"webkit\" ? \"WebKit\" : `${engine[0]?.toUpperCase()}${engine.slice(1)}`;\n return name === undefined ? label : `${label} on ${name}`;\n}\n\n/**\n * Refuse, with 400 and the reason, a target list the runner cannot render (not offered, or\n * unavailable), or one that is malformed or too long. Runs when a setting is saved, so a build\n * never fails one by one with an unexplained error.\n */\nexport async function assertTargetsSelectable(\n targets: readonly string[],\n /** Targets the project already has: saving them again is not a new choice, so they are not refused. */\n current: readonly string[] = [],\n): Promise<void> {\n const bad = targets.find((target) => !isCaptureTarget(target));\n if (bad !== undefined) {\n throw new HTTPException(400, { message: `Invalid capture target \"${bad}\"` });\n }\n if (targets.length > MAX_BROWSERS) {\n throw new HTTPException(400, { message: `Choose at most ${MAX_BROWSERS} browsers` });\n }\n try {\n assertSupportedBrowsers(\n (targets as CaptureTarget[]).filter((target) => !current.includes(target)),\n await runnerTargets(),\n );\n } catch (error) {\n throw new HTTPException(400, { message: error instanceof Error ? error.message : \"Refused\" });\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AAgBA,eAAsB,cACpB,QAAe,SAAS,GACsB;CAC9C,IAAI,MAAM,WAAW,KAAA,GACnB;CAEF,OAAO,eAAe,MAAM,QAAQ;EAClC,cAAc,MAAM,MAAM,cAAc,IAAI;EAC5C,WAAW,MAAM,OAAO,WAAW,KAAA;CACrC,CAAC;AACH;;AAGA,SAAgB,YAAY,QAA+B;CACzD,MAAM,EAAE,QAAQ,SAAS,YAAY,MAAM;CAC3C,MAAM,QAAQ,WAAW,WAAW,WAAW,GAAG,OAAO,EAAE,EAAE,YAAY,IAAI,OAAO,MAAM,CAAC;CAC3F,OAAO,SAAS,KAAA,IAAY,QAAQ,GAAG,MAAM,MAAM;AACrD;;;;;;AAOA,eAAsB,wBACpB,SAEA,UAA6B,CAAC,GACf;CACf,MAAM,MAAM,QAAQ,MAAM,WAAW,CAAC,gBAAgB,MAAM,CAAC;CAC7D,IAAI,QAAQ,KAAA,GACV,MAAM,IAAI,cAAc,KAAK,EAAE,SAAS,2BAA2B,IAAI,GAAG,CAAC;CAE7E,IAAI,QAAQ,SAAS,cACnB,MAAM,IAAI,cAAc,KAAK,EAAE,SAAS,kBAAkB,aAAa,WAAW,CAAC;CAErF,IAAI;EACF,wBACG,QAA4B,QAAQ,WAAW,CAAC,QAAQ,SAAS,MAAM,CAAC,GACzE,MAAM,cAAc,CACtB;CACF,SAAS,OAAO;EACd,MAAM,IAAI,cAAc,KAAK,EAAE,SAAS,iBAAiB,QAAQ,MAAM,UAAU,UAAU,CAAC;CAC9F;AACF"}
|
package/dist/ui/avatar.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import "node:url";
|
|
|
3
3
|
import.meta.url;
|
|
4
4
|
import { css } from "./css.mjs";
|
|
5
5
|
import { jsx } from "hono/jsx/jsx-runtime";
|
|
6
|
-
|
|
6
|
+
// src/ui/avatar.tsx
|
|
7
7
|
const avatarBase = css`
|
|
8
8
|
/* avatar */
|
|
9
9
|
flex: none;
|
|
@@ -70,7 +70,6 @@ const Avatar = ({ name, src, size = "md" }) => {
|
|
|
70
70
|
children: initials(name) || "?"
|
|
71
71
|
});
|
|
72
72
|
};
|
|
73
|
-
//#endregion
|
|
74
73
|
export { Avatar, initials };
|
|
75
74
|
|
|
76
75
|
//# sourceMappingURL=avatar.mjs.map
|
package/dist/ui/buttons.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import.meta.url;
|
|
|
4
4
|
import { css } from "./css.mjs";
|
|
5
5
|
import { Icon } from "./icons/icon.mjs";
|
|
6
6
|
import { Fragment, jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
7
|
-
|
|
7
|
+
// src/ui/buttons.tsx
|
|
8
8
|
const btnBase = css`
|
|
9
9
|
/* btn */
|
|
10
10
|
appearance: none;
|
|
@@ -149,7 +149,7 @@ const btnClasses = {
|
|
|
149
149
|
sm: btnDangerSm
|
|
150
150
|
}
|
|
151
151
|
};
|
|
152
|
-
css`
|
|
152
|
+
const tabsNav = css`
|
|
153
153
|
/* tabs */
|
|
154
154
|
display: flex;
|
|
155
155
|
gap: 0.25rem;
|
|
@@ -171,7 +171,7 @@ const tabsLink = css`
|
|
|
171
171
|
text-decoration: none;
|
|
172
172
|
}
|
|
173
173
|
`;
|
|
174
|
-
css`
|
|
174
|
+
const tabsLinkActive = css`
|
|
175
175
|
/* tabs-link-active */
|
|
176
176
|
${tabsLink}
|
|
177
177
|
color: var(--text-primary);
|
|
@@ -199,7 +199,19 @@ const Button = ({ variant = "primary", size = "md", type = "button", href, disab
|
|
|
199
199
|
children: content
|
|
200
200
|
});
|
|
201
201
|
};
|
|
202
|
-
|
|
203
|
-
|
|
202
|
+
/** Tab navigation bar for section switching. */
|
|
203
|
+
const Tabs = ({ tabs, label = "Sections" }) => {
|
|
204
|
+
return /* @__PURE__ */ jsx("nav", {
|
|
205
|
+
class: tabsNav,
|
|
206
|
+
"aria-label": label,
|
|
207
|
+
children: tabs.map((tab) => /* @__PURE__ */ jsx("a", {
|
|
208
|
+
class: tab.active ? tabsLinkActive : tabsLink,
|
|
209
|
+
href: tab.href,
|
|
210
|
+
"aria-current": tab.active ? "page" : void 0,
|
|
211
|
+
children: tab.label
|
|
212
|
+
}))
|
|
213
|
+
});
|
|
214
|
+
};
|
|
215
|
+
export { Button, Tabs };
|
|
204
216
|
|
|
205
217
|
//# sourceMappingURL=buttons.mjs.map
|
package/dist/ui/buttons.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buttons.mjs","names":[],"sources":["../../src/ui/buttons.tsx"],"sourcesContent":["import type { FC } from \"hono/jsx\";\nimport { css } from \"./css.ts\";\nimport { Icon } from \"./icons/icon.tsx\";\nimport type { IconName } from \"./icons/paths.ts\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\ntype ButtonVariant = \"primary\" | \"secondary\" | \"ghost\" | \"danger\" | \"outline\";\ntype ButtonSize = \"md\" | \"sm\";\n\nconst btnBase = css`\n /* btn */\n appearance: none;\n border: 1px solid transparent;\n border-radius: var(--radius);\n padding: 0.55rem 0.9rem;\n font-weight: 600;\n font-size: var(--text-base);\n cursor: pointer;\n text-decoration: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 0.4rem;\n line-height: 1.2;\n /* Top-aligned so a button in a <form> (a block with a line box) is exactly\n as tall as the button instead of picking up a baseline strut. */\n vertical-align: top;\n min-height: 40px;\n transition:\n background var(--dur-fast) var(--ease),\n border-color var(--dur-fast) var(--ease),\n filter var(--dur-fast) var(--ease),\n transform var(--dur-fast) var(--ease);\n &:active:not(:disabled) {\n transform: translateY(1px);\n }\n &:disabled {\n opacity: 0.55;\n cursor: not-allowed;\n }\n`;\n\nconst btnPrimary = css`\n /* btn-primary */\n ${btnBase}\n background: var(--accent);\n color: var(--accent-contrast);\n border-color: var(--accent);\n box-shadow: var(--shadow);\n &:hover {\n filter: brightness(1.06);\n text-decoration: none;\n }\n`;\n\nconst btnPrimarySm = css`\n /* btn-primary-sm */\n ${btnPrimary}\n padding: 0.35rem 0.6rem;\n font-size: 0.8rem;\n min-height: 32px;\n`;\n\nconst btnSecondary = css`\n /* btn-secondary */\n ${btnBase}\n background: var(--surface-card);\n color: var(--text-primary);\n border-color: var(--border);\n box-shadow: var(--shadow);\n &:hover {\n background: var(--surface-subtle);\n text-decoration: none;\n }\n`;\n\nconst btnSecondarySm = css`\n /* btn-secondary-sm */\n ${btnSecondary}\n padding: 0.35rem 0.6rem;\n font-size: 0.8rem;\n min-height: 32px;\n`;\n\nconst btnOutline = css`\n /* btn-outline */\n ${btnSecondary}\n &:hover {\n background: var(--surface-subtle);\n text-decoration: none;\n }\n`;\n\nconst btnOutlineSm = css`\n /* btn-outline-sm */\n ${btnOutline}\n padding: 0.35rem 0.6rem;\n font-size: 0.8rem;\n min-height: 32px;\n`;\n\nconst btnGhost = css`\n /* btn-ghost */\n ${btnBase}\n background: transparent;\n color: var(--text-secondary);\n border-color: transparent;\n &:hover {\n background: var(--surface-muted);\n color: var(--text-primary);\n text-decoration: none;\n }\n`;\n\nconst btnGhostSm = css`\n /* btn-ghost-sm */\n ${btnGhost}\n padding: 0.35rem 0.6rem;\n font-size: 0.8rem;\n min-height: 32px;\n`;\n\nconst btnDanger = css`\n /* btn-danger */\n ${btnBase}\n background: color-mix(in srgb, var(--status-rejected) 82%, #000);\n color: #fff;\n border-color: color-mix(in srgb, var(--status-rejected) 82%, #000);\n &:hover {\n filter: brightness(1.06);\n text-decoration: none;\n }\n`;\n\nconst btnDangerSm = css`\n /* btn-danger-sm */\n ${btnDanger}\n padding: 0.35rem 0.6rem;\n font-size: 0.8rem;\n min-height: 32px;\n`;\n\nconst btnClasses: Record<ButtonVariant, Record<ButtonSize, Promise<string>>> = {\n primary: { md: btnPrimary, sm: btnPrimarySm },\n secondary: { md: btnSecondary, sm: btnSecondarySm },\n outline: { md: btnOutline, sm: btnOutlineSm },\n ghost: { md: btnGhost, sm: btnGhostSm },\n danger: { md: btnDanger, sm: btnDangerSm },\n};\n\nconst tabsNav = css`\n /* tabs */\n display: flex;\n gap: 0.25rem;\n border-bottom: 1px solid var(--border);\n margin-bottom: 1rem;\n overflow-x: auto;\n`;\n\nconst tabsLink = css`\n /* tabs-link */\n padding: 0.55rem 0.75rem;\n border-bottom: 2px solid transparent;\n color: var(--text-secondary);\n font-size: 0.875rem;\n text-decoration: none;\n white-space: nowrap;\n font-weight: 600;\n &:hover {\n color: var(--text-primary);\n text-decoration: none;\n }\n`;\n\nconst tabsLinkActive = css`\n /* tabs-link-active */\n ${tabsLink}\n color: var(--text-primary);\n border-bottom-color: var(--accent);\n`;\n\n/** Button or link styled as a button (renders an anchor when `href` is set). */\nexport const Button: FC<{\n variant?: ButtonVariant;\n size?: ButtonSize;\n type?: \"button\" | \"submit\" | \"reset\";\n href?: string;\n disabled?: boolean;\n /** Leading icon from the sprite. */\n icon?: IconName;\n children?: unknown;\n [key: string]: unknown;\n}> = ({\n variant = \"primary\",\n size = \"md\",\n type = \"button\",\n href,\n disabled,\n icon,\n children,\n ...rest\n}) => {\n const cls = btnClasses[variant][size];\n const content = (\n <>\n {icon ? <Icon name={icon} size={size === \"sm\" ? \"sm\" : \"md\"} /> : null}\n {children}\n </>\n );\n if (href) {\n return (\n <a class={cls} href={href} aria-disabled={disabled ? \"true\" : undefined} {...rest}>\n {content}\n </a>\n );\n }\n return (\n <button class={cls} type={type} disabled={disabled} {...rest}>\n {content}\n </button>\n );\n};\n\n/** Tab navigation bar for section switching. */\n// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const Tabs: FC<{\n tabs: { label: string; href: string; active?: boolean }[];\n label?: string;\n}> = ({ tabs, label = \"Sections\" }) => {\n return (\n <nav class={tabsNav} aria-label={label}>\n {tabs.map((tab) => (\n <a\n class={tab.active ? tabsLinkActive : tabsLink}\n href={tab.href}\n aria-current={tab.active ? \"page\" : undefined}\n >\n {tab.label}\n </a>\n ))}\n </nav>\n );\n};\n"],"mappings":";;;;;;;AAUA,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCnB,MAAM,aAAa,GAAG;;IAElB,QAAQ;;;;;;;;;;AAWZ,MAAM,eAAe,GAAG;;IAEpB,WAAW;;;;;AAMf,MAAM,eAAe,GAAG;;IAEpB,QAAQ;;;;;;;;;;AAWZ,MAAM,iBAAiB,GAAG;;IAEtB,aAAa;;;;;AAMjB,MAAM,aAAa,GAAG;;IAElB,aAAa;;;;;;AAOjB,MAAM,eAAe,GAAG;;IAEpB,WAAW;;;;;AAMf,MAAM,WAAW,GAAG;;IAEhB,QAAQ;;;;;;;;;;AAWZ,MAAM,aAAa,GAAG;;IAElB,SAAS;;;;;AAMb,MAAM,YAAY,GAAG;;IAEjB,QAAQ;;;;;;;;;AAUZ,MAAM,cAAc,GAAG;;IAEnB,UAAU;;;;;AAMd,MAAM,aAAyE;CAC7E,SAAS;EAAE,IAAI;EAAY,IAAI;CAAa;CAC5C,WAAW;EAAE,IAAI;EAAc,IAAI;CAAe;CAClD,SAAS;EAAE,IAAI;EAAY,IAAI;CAAa;CAC5C,OAAO;EAAE,IAAI;EAAU,IAAI;CAAW;CACtC,QAAQ;EAAE,IAAI;EAAW,IAAI;CAAY;AAC3C;
|
|
1
|
+
{"version":3,"file":"buttons.mjs","names":[],"sources":["../../src/ui/buttons.tsx"],"sourcesContent":["import type { FC } from \"hono/jsx\";\nimport { css } from \"./css.ts\";\nimport { Icon } from \"./icons/icon.tsx\";\nimport type { IconName } from \"./icons/paths.ts\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\ntype ButtonVariant = \"primary\" | \"secondary\" | \"ghost\" | \"danger\" | \"outline\";\ntype ButtonSize = \"md\" | \"sm\";\n\nconst btnBase = css`\n /* btn */\n appearance: none;\n border: 1px solid transparent;\n border-radius: var(--radius);\n padding: 0.55rem 0.9rem;\n font-weight: 600;\n font-size: var(--text-base);\n cursor: pointer;\n text-decoration: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 0.4rem;\n line-height: 1.2;\n /* Top-aligned so a button in a <form> (a block with a line box) is exactly\n as tall as the button instead of picking up a baseline strut. */\n vertical-align: top;\n min-height: 40px;\n transition:\n background var(--dur-fast) var(--ease),\n border-color var(--dur-fast) var(--ease),\n filter var(--dur-fast) var(--ease),\n transform var(--dur-fast) var(--ease);\n &:active:not(:disabled) {\n transform: translateY(1px);\n }\n &:disabled {\n opacity: 0.55;\n cursor: not-allowed;\n }\n`;\n\nconst btnPrimary = css`\n /* btn-primary */\n ${btnBase}\n background: var(--accent);\n color: var(--accent-contrast);\n border-color: var(--accent);\n box-shadow: var(--shadow);\n &:hover {\n filter: brightness(1.06);\n text-decoration: none;\n }\n`;\n\nconst btnPrimarySm = css`\n /* btn-primary-sm */\n ${btnPrimary}\n padding: 0.35rem 0.6rem;\n font-size: 0.8rem;\n min-height: 32px;\n`;\n\nconst btnSecondary = css`\n /* btn-secondary */\n ${btnBase}\n background: var(--surface-card);\n color: var(--text-primary);\n border-color: var(--border);\n box-shadow: var(--shadow);\n &:hover {\n background: var(--surface-subtle);\n text-decoration: none;\n }\n`;\n\nconst btnSecondarySm = css`\n /* btn-secondary-sm */\n ${btnSecondary}\n padding: 0.35rem 0.6rem;\n font-size: 0.8rem;\n min-height: 32px;\n`;\n\nconst btnOutline = css`\n /* btn-outline */\n ${btnSecondary}\n &:hover {\n background: var(--surface-subtle);\n text-decoration: none;\n }\n`;\n\nconst btnOutlineSm = css`\n /* btn-outline-sm */\n ${btnOutline}\n padding: 0.35rem 0.6rem;\n font-size: 0.8rem;\n min-height: 32px;\n`;\n\nconst btnGhost = css`\n /* btn-ghost */\n ${btnBase}\n background: transparent;\n color: var(--text-secondary);\n border-color: transparent;\n &:hover {\n background: var(--surface-muted);\n color: var(--text-primary);\n text-decoration: none;\n }\n`;\n\nconst btnGhostSm = css`\n /* btn-ghost-sm */\n ${btnGhost}\n padding: 0.35rem 0.6rem;\n font-size: 0.8rem;\n min-height: 32px;\n`;\n\nconst btnDanger = css`\n /* btn-danger */\n ${btnBase}\n background: color-mix(in srgb, var(--status-rejected) 82%, #000);\n color: #fff;\n border-color: color-mix(in srgb, var(--status-rejected) 82%, #000);\n &:hover {\n filter: brightness(1.06);\n text-decoration: none;\n }\n`;\n\nconst btnDangerSm = css`\n /* btn-danger-sm */\n ${btnDanger}\n padding: 0.35rem 0.6rem;\n font-size: 0.8rem;\n min-height: 32px;\n`;\n\nconst btnClasses: Record<ButtonVariant, Record<ButtonSize, Promise<string>>> = {\n primary: { md: btnPrimary, sm: btnPrimarySm },\n secondary: { md: btnSecondary, sm: btnSecondarySm },\n outline: { md: btnOutline, sm: btnOutlineSm },\n ghost: { md: btnGhost, sm: btnGhostSm },\n danger: { md: btnDanger, sm: btnDangerSm },\n};\n\nconst tabsNav = css`\n /* tabs */\n display: flex;\n gap: 0.25rem;\n border-bottom: 1px solid var(--border);\n margin-bottom: 1rem;\n overflow-x: auto;\n`;\n\nconst tabsLink = css`\n /* tabs-link */\n padding: 0.55rem 0.75rem;\n border-bottom: 2px solid transparent;\n color: var(--text-secondary);\n font-size: 0.875rem;\n text-decoration: none;\n white-space: nowrap;\n font-weight: 600;\n &:hover {\n color: var(--text-primary);\n text-decoration: none;\n }\n`;\n\nconst tabsLinkActive = css`\n /* tabs-link-active */\n ${tabsLink}\n color: var(--text-primary);\n border-bottom-color: var(--accent);\n`;\n\n/** Button or link styled as a button (renders an anchor when `href` is set). */\nexport const Button: FC<{\n variant?: ButtonVariant;\n size?: ButtonSize;\n type?: \"button\" | \"submit\" | \"reset\";\n href?: string;\n disabled?: boolean;\n /** Leading icon from the sprite. */\n icon?: IconName;\n children?: unknown;\n [key: string]: unknown;\n}> = ({\n variant = \"primary\",\n size = \"md\",\n type = \"button\",\n href,\n disabled,\n icon,\n children,\n ...rest\n}) => {\n const cls = btnClasses[variant][size];\n const content = (\n <>\n {icon ? <Icon name={icon} size={size === \"sm\" ? \"sm\" : \"md\"} /> : null}\n {children}\n </>\n );\n if (href) {\n return (\n <a class={cls} href={href} aria-disabled={disabled ? \"true\" : undefined} {...rest}>\n {content}\n </a>\n );\n }\n return (\n <button class={cls} type={type} disabled={disabled} {...rest}>\n {content}\n </button>\n );\n};\n\n/** Tab navigation bar for section switching. */\n// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const Tabs: FC<{\n tabs: { label: string; href: string; active?: boolean }[];\n label?: string;\n}> = ({ tabs, label = \"Sections\" }) => {\n return (\n <nav class={tabsNav} aria-label={label}>\n {tabs.map((tab) => (\n <a\n class={tab.active ? tabsLinkActive : tabsLink}\n href={tab.href}\n aria-current={tab.active ? \"page\" : undefined}\n >\n {tab.label}\n </a>\n ))}\n </nav>\n );\n};\n"],"mappings":";;;;;;;AAUA,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCnB,MAAM,aAAa,GAAG;;IAElB,QAAQ;;;;;;;;;;AAWZ,MAAM,eAAe,GAAG;;IAEpB,WAAW;;;;;AAMf,MAAM,eAAe,GAAG;;IAEpB,QAAQ;;;;;;;;;;AAWZ,MAAM,iBAAiB,GAAG;;IAEtB,aAAa;;;;;AAMjB,MAAM,aAAa,GAAG;;IAElB,aAAa;;;;;;AAOjB,MAAM,eAAe,GAAG;;IAEpB,WAAW;;;;;AAMf,MAAM,WAAW,GAAG;;IAEhB,QAAQ;;;;;;;;;;AAWZ,MAAM,aAAa,GAAG;;IAElB,SAAS;;;;;AAMb,MAAM,YAAY,GAAG;;IAEjB,QAAQ;;;;;;;;;AAUZ,MAAM,cAAc,GAAG;;IAEnB,UAAU;;;;;AAMd,MAAM,aAAyE;CAC7E,SAAS;EAAE,IAAI;EAAY,IAAI;CAAa;CAC5C,WAAW;EAAE,IAAI;EAAc,IAAI;CAAe;CAClD,SAAS;EAAE,IAAI;EAAY,IAAI;CAAa;CAC5C,OAAO;EAAE,IAAI;EAAU,IAAI;CAAW;CACtC,QAAQ;EAAE,IAAI;EAAW,IAAI;CAAY;AAC3C;AAEA,MAAM,UAAU,GAAG;;;;;;;;AASnB,MAAM,WAAW,GAAG;;;;;;;;;;;;;;AAepB,MAAM,iBAAiB,GAAG;;IAEtB,SAAS;;;;;AAMb,MAAa,UAUP,EACJ,UAAU,WACV,OAAO,MACP,OAAO,UACP,MACA,UACA,MACA,UACA,GAAG,WACC;CACJ,MAAM,MAAM,WAAW,QAAQ,CAAC;CAChC,MAAM,UACJ,qBAAA,UAAA,EAAA,UAAA,CACG,OAAO,oBAAC,MAAD;EAAM,MAAM;EAAM,MAAM,SAAS,OAAO,OAAO;CAAO,CAAA,IAAI,MACjE,QACD,EAAA,CAAA;CAEJ,IAAI,MACF,OACE,oBAAC,KAAD;EAAG,OAAO;EAAW;EAAM,iBAAe,WAAW,SAAS,KAAA;EAAW,GAAI;EAC1E,UAAA;CACA,CAAA;CAGP,OACE,oBAAC,UAAD;EAAQ,OAAO;EAAW;EAAgB;EAAU,GAAI;EACrD,UAAA;CACK,CAAA;AAEZ;;AAIA,MAAa,QAGP,EAAE,MAAM,QAAQ,iBAAiB;CACrC,OACE,oBAAC,OAAD;EAAK,OAAO;EAAS,cAAY;EAC9B,UAAA,KAAK,KAAK,QACT,oBAAC,KAAD;GACE,OAAO,IAAI,SAAS,iBAAiB;GACrC,MAAM,IAAI;GACV,gBAAc,IAAI,SAAS,SAAS,KAAA;GAEnC,UAAA,IAAI;EACJ,CAAA,CACJ;CACE,CAAA;AAET"}
|
package/dist/ui/code-block.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import.meta.url;
|
|
|
4
4
|
import { css } from "./css.mjs";
|
|
5
5
|
import { Button } from "./buttons.mjs";
|
|
6
6
|
import { jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
7
|
-
|
|
7
|
+
// src/ui/code-block.tsx
|
|
8
8
|
const codeWrap = css`
|
|
9
9
|
/* code-block */
|
|
10
10
|
display: flex;
|
|
@@ -41,7 +41,6 @@ const CodeBlock = ({ code, label = "Copy" }) => {
|
|
|
41
41
|
})]
|
|
42
42
|
});
|
|
43
43
|
};
|
|
44
|
-
//#endregion
|
|
45
44
|
export { CodeBlock };
|
|
46
45
|
|
|
47
46
|
//# sourceMappingURL=code-block.mjs.map
|
|
@@ -4,7 +4,7 @@ import.meta.url;
|
|
|
4
4
|
import { css } from "./css.mjs";
|
|
5
5
|
import { Segmented } from "./segmented.mjs";
|
|
6
6
|
import { Fragment, jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
7
|
-
|
|
7
|
+
// src/ui/compare-stage.tsx
|
|
8
8
|
/**
|
|
9
9
|
* Screenshot comparison surface. All modes share one set of images; the
|
|
10
10
|
* `data-view` / `data-zoom` / `data-flip` attributes on the root (driven by
|
|
@@ -70,6 +70,10 @@ const stageRoot = css`
|
|
|
70
70
|
font-size: var(--text-sm);
|
|
71
71
|
text-align: center;
|
|
72
72
|
}
|
|
73
|
+
.stage__note {
|
|
74
|
+
color: var(--text-secondary);
|
|
75
|
+
font-size: var(--text-sm);
|
|
76
|
+
}
|
|
73
77
|
.stage__divider,
|
|
74
78
|
.stage__controls {
|
|
75
79
|
display: none;
|
|
@@ -103,6 +107,10 @@ const stageRoot = css`
|
|
|
103
107
|
&[data-zoom="400"] .stage__img {
|
|
104
108
|
image-rendering: pixelated;
|
|
105
109
|
}
|
|
110
|
+
/* Design variant: screenshot against a render, two panes and no diff. */
|
|
111
|
+
&[data-variant="design"][data-view="split"] .stage__grid {
|
|
112
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
113
|
+
}
|
|
106
114
|
&[data-view="diff"] .stage__grid,
|
|
107
115
|
&[data-view="flip"] .stage__grid,
|
|
108
116
|
&[data-view="swipe"] .stage__grid,
|
|
@@ -218,6 +226,12 @@ const ZOOMS = [
|
|
|
218
226
|
value: "400"
|
|
219
227
|
}
|
|
220
228
|
];
|
|
229
|
+
/** Modes and zoom a design comparison offers: no pixel diff, and the render always fits the stage. */
|
|
230
|
+
const DESIGN_MODES = [
|
|
231
|
+
"split",
|
|
232
|
+
"swipe",
|
|
233
|
+
"onion"
|
|
234
|
+
];
|
|
221
235
|
function withActive(items, active) {
|
|
222
236
|
return items.map((item) => ({
|
|
223
237
|
...item,
|
|
@@ -244,12 +258,12 @@ const Pane = ({ kind, label, src, alt, empty, meta }) => {
|
|
|
244
258
|
});
|
|
245
259
|
};
|
|
246
260
|
/** Swipe and onion-skin sliders; each is shown only in its own mode (CSS). */
|
|
247
|
-
const RangeControls = () => {
|
|
261
|
+
const RangeControls = ({ baselineLabel }) => {
|
|
248
262
|
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("label", {
|
|
249
263
|
class: "stage__controls",
|
|
250
264
|
"data-for": "swipe",
|
|
251
265
|
children: [
|
|
252
|
-
/* @__PURE__ */ jsx("span", { children:
|
|
266
|
+
/* @__PURE__ */ jsx("span", { children: baselineLabel }),
|
|
253
267
|
/* @__PURE__ */ jsx("input", {
|
|
254
268
|
type: "range",
|
|
255
269
|
min: "0",
|
|
@@ -264,7 +278,7 @@ const RangeControls = () => {
|
|
|
264
278
|
class: "stage__controls",
|
|
265
279
|
"data-for": "onion",
|
|
266
280
|
children: [
|
|
267
|
-
/* @__PURE__ */ jsx("span", { children:
|
|
281
|
+
/* @__PURE__ */ jsx("span", { children: baselineLabel }),
|
|
268
282
|
/* @__PURE__ */ jsx("input", {
|
|
269
283
|
type: "range",
|
|
270
284
|
min: "0",
|
|
@@ -277,68 +291,86 @@ const RangeControls = () => {
|
|
|
277
291
|
]
|
|
278
292
|
})] });
|
|
279
293
|
};
|
|
294
|
+
/** Mode switch, plus the zoom switch (a design comparison always fits, so it has none). */
|
|
295
|
+
const StageBar = ({ design }) => {
|
|
296
|
+
const modes = design ? MODES.filter((mode) => DESIGN_MODES.includes(mode.value)) : MODES;
|
|
297
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
298
|
+
class: "stage__bar",
|
|
299
|
+
children: [/* @__PURE__ */ jsx(Segmented, {
|
|
300
|
+
label: "Comparison mode",
|
|
301
|
+
"data-compare-mode": true,
|
|
302
|
+
items: withActive(modes, "split")
|
|
303
|
+
}), design ? null : /* @__PURE__ */ jsx(Segmented, {
|
|
304
|
+
label: "Zoom",
|
|
305
|
+
"data-compare-zoom": true,
|
|
306
|
+
items: withActive(ZOOMS, "fit")
|
|
307
|
+
})]
|
|
308
|
+
});
|
|
309
|
+
};
|
|
310
|
+
/** The panes (baseline or design, current, diff) and the swipe divider. */
|
|
311
|
+
const StageGrid = ({ baselineSrc, currentSrc, diffSrc, subject, baselineEmpty = "New story — no baseline yet. Approve to set the baseline.", diffEmpty = "No diff", diffMeta, design, baselineLabel }) => /* @__PURE__ */ jsxs("div", {
|
|
312
|
+
class: "stage__grid",
|
|
313
|
+
children: [
|
|
314
|
+
/* @__PURE__ */ jsx(Pane, {
|
|
315
|
+
kind: "baseline",
|
|
316
|
+
label: baselineLabel,
|
|
317
|
+
src: baselineSrc,
|
|
318
|
+
alt: `${baselineLabel} for ${subject}`,
|
|
319
|
+
empty: baselineEmpty
|
|
320
|
+
}),
|
|
321
|
+
/* @__PURE__ */ jsx(Pane, {
|
|
322
|
+
kind: "current",
|
|
323
|
+
label: "Current",
|
|
324
|
+
src: currentSrc,
|
|
325
|
+
alt: `Current for ${subject}`,
|
|
326
|
+
empty: "No screenshot"
|
|
327
|
+
}),
|
|
328
|
+
design ? null : /* @__PURE__ */ jsx(Pane, {
|
|
329
|
+
kind: "diff",
|
|
330
|
+
label: "Diff",
|
|
331
|
+
src: diffSrc,
|
|
332
|
+
alt: `Diff for ${subject}`,
|
|
333
|
+
empty: diffEmpty,
|
|
334
|
+
meta: diffMeta
|
|
335
|
+
}),
|
|
336
|
+
/* @__PURE__ */ jsx("span", {
|
|
337
|
+
class: "stage__divider",
|
|
338
|
+
"aria-hidden": "true",
|
|
339
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
340
|
+
class: "stage__knob",
|
|
341
|
+
"data-compare-knob": true,
|
|
342
|
+
children: "↔"
|
|
343
|
+
})
|
|
344
|
+
})
|
|
345
|
+
]
|
|
346
|
+
});
|
|
280
347
|
/** Baseline / current / diff comparison with swipe, onion-skin, flip, and zoom. */
|
|
281
|
-
const CompareStage = ({
|
|
348
|
+
const CompareStage = ({ variant = "baseline", baselineLabel = "Baseline", note, ...images }) => {
|
|
349
|
+
const design = variant === "design";
|
|
282
350
|
return /* @__PURE__ */ jsxs("div", {
|
|
283
351
|
class: stageRoot,
|
|
284
352
|
"data-compare": true,
|
|
285
353
|
"data-view": "split",
|
|
286
354
|
"data-zoom": "fit",
|
|
287
355
|
"data-flip": "current",
|
|
356
|
+
"data-variant": variant,
|
|
357
|
+
"data-modes": design ? DESIGN_MODES.join(",") : void 0,
|
|
358
|
+
"data-zooms": design ? "fit" : void 0,
|
|
288
359
|
children: [
|
|
289
|
-
/* @__PURE__ */
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
items: withActive(ZOOMS, "fit")
|
|
299
|
-
})]
|
|
300
|
-
}),
|
|
301
|
-
/* @__PURE__ */ jsxs("div", {
|
|
302
|
-
class: "stage__grid",
|
|
303
|
-
children: [
|
|
304
|
-
/* @__PURE__ */ jsx(Pane, {
|
|
305
|
-
kind: "baseline",
|
|
306
|
-
label: "Baseline",
|
|
307
|
-
src: baselineSrc,
|
|
308
|
-
alt: `Baseline for ${subject}`,
|
|
309
|
-
empty: baselineEmpty
|
|
310
|
-
}),
|
|
311
|
-
/* @__PURE__ */ jsx(Pane, {
|
|
312
|
-
kind: "current",
|
|
313
|
-
label: "Current",
|
|
314
|
-
src: currentSrc,
|
|
315
|
-
alt: `Current for ${subject}`,
|
|
316
|
-
empty: "No screenshot"
|
|
317
|
-
}),
|
|
318
|
-
/* @__PURE__ */ jsx(Pane, {
|
|
319
|
-
kind: "diff",
|
|
320
|
-
label: "Diff",
|
|
321
|
-
src: diffSrc,
|
|
322
|
-
alt: `Diff for ${subject}`,
|
|
323
|
-
empty: diffEmpty,
|
|
324
|
-
meta: diffMeta
|
|
325
|
-
}),
|
|
326
|
-
/* @__PURE__ */ jsx("span", {
|
|
327
|
-
class: "stage__divider",
|
|
328
|
-
"aria-hidden": "true",
|
|
329
|
-
children: /* @__PURE__ */ jsx("span", {
|
|
330
|
-
class: "stage__knob",
|
|
331
|
-
"data-compare-knob": true,
|
|
332
|
-
children: "↔"
|
|
333
|
-
})
|
|
334
|
-
})
|
|
335
|
-
]
|
|
360
|
+
/* @__PURE__ */ jsx(StageBar, { design }),
|
|
361
|
+
note ? /* @__PURE__ */ jsx("p", {
|
|
362
|
+
class: "stage__note",
|
|
363
|
+
children: note
|
|
364
|
+
}) : null,
|
|
365
|
+
/* @__PURE__ */ jsx(StageGrid, {
|
|
366
|
+
...images,
|
|
367
|
+
design,
|
|
368
|
+
baselineLabel
|
|
336
369
|
}),
|
|
337
|
-
/* @__PURE__ */ jsx(RangeControls, {})
|
|
370
|
+
/* @__PURE__ */ jsx(RangeControls, { baselineLabel })
|
|
338
371
|
]
|
|
339
372
|
});
|
|
340
373
|
};
|
|
341
|
-
//#endregion
|
|
342
374
|
export { CompareStage };
|
|
343
375
|
|
|
344
376
|
//# sourceMappingURL=compare-stage.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compare-stage.mjs","names":[],"sources":["../../src/ui/compare-stage.tsx"],"sourcesContent":["import type { FC } from \"hono/jsx\";\nimport { css } from \"./css.ts\";\nimport { Segmented } from \"./segmented.tsx\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\n/**\n * Screenshot comparison surface. All modes share one set of images; the\n * `data-view` / `data-zoom` / `data-flip` attributes on the root (driven by\n * the review script) decide what is shown. Without JS it stays side-by-side.\n */\nconst stageRoot = css`\n /* compare-stage */\n display: grid;\n gap: var(--space-3);\n .stage__bar {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-3);\n align-items: center;\n justify-content: space-between;\n }\n .stage__grid {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: var(--space-3);\n align-items: start;\n }\n .stage__pane {\n position: relative;\n min-width: 0;\n max-height: 78vh;\n overflow: auto;\n background: var(--surface-card);\n border: 1px solid var(--border);\n border-radius: var(--radius);\n }\n .stage__label {\n position: sticky;\n top: 0;\n left: 0;\n z-index: 2;\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0.35rem 0.6rem;\n background: var(--surface-subtle);\n border-bottom: 1px solid var(--border);\n color: var(--text-secondary);\n font-size: var(--text-xs);\n font-weight: 600;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n }\n .stage__img {\n display: block;\n width: 100%;\n height: auto;\n background: repeating-conic-gradient(var(--surface-muted) 0% 25%, var(--surface-subtle) 0% 50%)\n 0 0 / 16px 16px;\n }\n .stage__empty {\n display: grid;\n place-items: center;\n aspect-ratio: 16 / 9;\n padding: var(--space-4);\n background: var(--surface-subtle);\n color: var(--text-secondary);\n font-size: var(--text-sm);\n text-align: center;\n }\n .stage__divider,\n .stage__controls {\n display: none;\n }\n .stage__controls {\n align-items: center;\n gap: var(--space-3);\n color: var(--text-secondary);\n font-size: var(--text-sm);\n }\n .stage__controls input {\n flex: 1;\n accent-color: var(--accent);\n }\n &[data-zoom=\"100\"] .stage__img {\n width: auto;\n max-width: none;\n }\n &[data-zoom=\"200\"] .stage__img {\n width: auto;\n max-width: none;\n zoom: 2;\n }\n &[data-zoom=\"400\"] .stage__img {\n width: auto;\n max-width: none;\n zoom: 4;\n }\n /* Zoomed in, show real pixels instead of a smoothed blur. */\n &[data-zoom=\"200\"] .stage__img,\n &[data-zoom=\"400\"] .stage__img {\n image-rendering: pixelated;\n }\n &[data-view=\"diff\"] .stage__grid,\n &[data-view=\"flip\"] .stage__grid,\n &[data-view=\"swipe\"] .stage__grid,\n &[data-view=\"onion\"] .stage__grid {\n grid-template-columns: minmax(0, 1fr);\n }\n &[data-view=\"diff\"] .stage__pane:not([data-pane=\"diff\"]) {\n display: none;\n }\n &[data-view=\"flip\"][data-flip=\"baseline\"] .stage__pane:not([data-pane=\"baseline\"]),\n &[data-view=\"flip\"]:not([data-flip=\"baseline\"]) .stage__pane:not([data-pane=\"current\"]) {\n display: none;\n }\n &[data-view=\"swipe\"] .stage__pane[data-pane=\"diff\"],\n &[data-view=\"onion\"] .stage__pane[data-pane=\"diff\"] {\n display: none;\n }\n &[data-view=\"swipe\"] .stage__pane,\n &[data-view=\"onion\"] .stage__pane,\n &[data-view=\"swipe\"] .stage__divider {\n grid-area: 1 / 1;\n }\n &[data-view=\"swipe\"] .stage__label,\n &[data-view=\"onion\"] .stage__label {\n display: none;\n }\n &[data-view=\"swipe\"] .stage__pane[data-pane=\"current\"] {\n clip-path: inset(0 0 0 var(--swipe, 50%));\n background: transparent;\n }\n &[data-view=\"onion\"] .stage__pane[data-pane=\"current\"] {\n opacity: var(--onion, 0.5);\n background: transparent;\n }\n &[data-view=\"swipe\"] .stage__divider {\n position: relative;\n display: block;\n z-index: 3;\n justify-self: start;\n width: 2px;\n height: 100%;\n margin-left: var(--swipe, 50%);\n background: var(--accent);\n pointer-events: none;\n }\n /* The only part of the divider that takes pointer input: drag it to wipe. */\n .stage__knob {\n position: absolute;\n top: 50%;\n left: 50%;\n display: grid;\n place-items: center;\n width: 32px;\n height: 32px;\n transform: translate(-50%, -50%);\n background: var(--accent);\n color: var(--accent-contrast);\n border: 2px solid var(--surface-card);\n border-radius: var(--radius-pill);\n box-shadow: var(--shadow-2);\n font-size: var(--text-lg);\n line-height: 1;\n cursor: ew-resize;\n pointer-events: auto;\n touch-action: none;\n user-select: none;\n }\n &[data-view=\"flip\"] .stage__grid {\n cursor: pointer;\n }\n &[data-view=\"swipe\"] .stage__controls[data-for=\"swipe\"],\n &[data-view=\"onion\"] .stage__controls[data-for=\"onion\"] {\n display: flex;\n }\n`;\n\nconst MODES = [\n { label: \"Side by side\", value: \"split\" },\n { label: \"Swipe\", value: \"swipe\" },\n { label: \"Onion skin\", value: \"onion\" },\n { label: \"Diff\", value: \"diff\" },\n { label: \"Flip\", value: \"flip\" },\n];\n\nconst ZOOMS = [\n { label: \"Fit\", value: \"fit\" },\n { label: \"100%\", value: \"100\" },\n { label: \"200%\", value: \"200\" },\n { label: \"400%\", value: \"400\" },\n];\n\nfunction withActive(\n items: { label: string; value: string }[],\n active: string,\n): { label: string; value: string; active: boolean }[] {\n return items.map((item) => ({ ...item, active: item.value === active }));\n}\n\ninterface PaneProps {\n kind: \"baseline\" | \"current\" | \"diff\";\n label: string;\n src: string | null;\n alt: string;\n empty: unknown;\n meta?: string;\n}\n\nconst Pane: FC<PaneProps> = ({ kind, label, src, alt, empty, meta }) => {\n return (\n <div class=\"stage__pane\" data-pane={kind}>\n <div class=\"stage__label\">\n <span>{label}</span>\n {meta ? <span>{meta}</span> : null}\n </div>\n {src ? (\n <img class=\"stage__img\" src={src} alt={alt} loading=\"lazy\" decoding=\"async\" />\n ) : (\n <div class=\"stage__empty\">{empty}</div>\n )}\n </div>\n );\n};\n\n/** Swipe and onion-skin sliders; each is shown only in its own mode (CSS). */\nconst RangeControls: FC = () => {\n return (\n <>\n <label class=\"stage__controls\" data-for=\"swipe\">\n <span>Baseline</span>\n <input type=\"range\" min=\"0\" max=\"100\" value=\"50\" data-compare-swipe aria-label=\"Swipe\" />\n <span>Current</span>\n </label>\n <label class=\"stage__controls\" data-for=\"onion\">\n <span>Baseline</span>\n <input type=\"range\" min=\"0\" max=\"100\" value=\"50\" data-compare-onion aria-label=\"Opacity\" />\n <span>Current</span>\n </label>\n </>\n );\n};\n\n/** Props for the screenshot comparison surface. */\nexport interface CompareStageProps {\n /** Baseline image URL, or null when the story has no baseline yet. */\n baselineSrc: string | null;\n currentSrc: string;\n /** Diff overlay URL, or null when none was produced. */\n diffSrc: string | null;\n /** Accessible description shared by the images, e.g. \"Button / Primary\". */\n subject: string;\n baselineEmpty?: unknown;\n diffEmpty?: unknown;\n diffMeta?: string;\n}\n\n/** Baseline / current / diff comparison with swipe, onion-skin, flip, and zoom. */\nexport const CompareStage: FC<CompareStageProps> = ({\n baselineSrc,\n currentSrc,\n diffSrc,\n subject,\n baselineEmpty = \"New story — no baseline yet. Approve to set the baseline.\",\n diffEmpty = \"No diff\",\n diffMeta,\n}) => {\n return (\n <div class={stageRoot} data-compare data-view=\"split\" data-zoom=\"fit\" data-flip=\"current\">\n <div class=\"stage__bar\">\n <Segmented label=\"Comparison mode\" data-compare-mode items={withActive(MODES, \"split\")} />\n <Segmented label=\"Zoom\" data-compare-zoom items={withActive(ZOOMS, \"fit\")} />\n </div>\n <div class=\"stage__grid\">\n <Pane\n kind=\"baseline\"\n label=\"Baseline\"\n src={baselineSrc}\n alt={`Baseline for ${subject}`}\n empty={baselineEmpty}\n />\n <Pane\n kind=\"current\"\n label=\"Current\"\n src={currentSrc}\n alt={`Current for ${subject}`}\n empty=\"No screenshot\"\n />\n <Pane\n kind=\"diff\"\n label=\"Diff\"\n src={diffSrc}\n alt={`Diff for ${subject}`}\n empty={diffEmpty}\n meta={diffMeta}\n />\n <span class=\"stage__divider\" aria-hidden=\"true\">\n <span class=\"stage__knob\" data-compare-knob>\n ↔\n </span>\n </span>\n </div>\n <RangeControls />\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;AAWA,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyKrB,MAAM,QAAQ;CACZ;EAAE,OAAO;EAAgB,OAAO;CAAQ;CACxC;EAAE,OAAO;EAAS,OAAO;CAAQ;CACjC;EAAE,OAAO;EAAc,OAAO;CAAQ;CACtC;EAAE,OAAO;EAAQ,OAAO;CAAO;CAC/B;EAAE,OAAO;EAAQ,OAAO;CAAO;AACjC;AAEA,MAAM,QAAQ;CACZ;EAAE,OAAO;EAAO,OAAO;CAAM;CAC7B;EAAE,OAAO;EAAQ,OAAO;CAAM;CAC9B;EAAE,OAAO;EAAQ,OAAO;CAAM;CAC9B;EAAE,OAAO;EAAQ,OAAO;CAAM;AAChC;AAEA,SAAS,WACP,OACA,QACqD;CACrD,OAAO,MAAM,KAAK,UAAU;EAAE,GAAG;EAAM,QAAQ,KAAK,UAAU;CAAO,EAAE;AACzE;AAWA,MAAM,QAAuB,EAAE,MAAM,OAAO,KAAK,KAAK,OAAO,WAAW;CACtE,OACE,qBAAC,OAAD;EAAK,OAAM;EAAc,aAAW;EAApC,UAAA,CACE,qBAAC,OAAD;GAAK,OAAM;GAAX,UAAA,CACE,oBAAC,QAAD,EAAA,UAAO,MAAY,CAAA,GAClB,OAAO,oBAAC,QAAD,EAAA,UAAO,KAAW,CAAA,IAAI,IAC3B;EACJ,CAAA,GAAA,MACC,oBAAC,OAAD;GAAK,OAAM;GAAkB;GAAU;GAAK,SAAQ;GAAO,UAAS;EAAS,CAAA,IAE7E,oBAAC,OAAD;GAAK,OAAM;GAAgB,UAAA;EAAW,CAAA,CAErC;;AAET;;AAGA,MAAM,sBAA0B;CAC9B,OACE,qBAAA,UAAA,EAAA,UAAA,CACE,qBAAC,SAAD;EAAO,OAAM;EAAkB,YAAS;EAAxC,UAAA;GACE,oBAAC,QAAD,EAAA,UAAM,WAAc,CAAA;GACpB,oBAAC,SAAD;IAAO,MAAK;IAAQ,KAAI;IAAI,KAAI;IAAM,OAAM;IAAK,sBAAA;IAAmB,cAAW;GAAS,CAAA;GACxF,oBAAC,QAAD,EAAA,UAAM,UAAa,CAAA;EACd;CACP,CAAA,GAAA,qBAAC,SAAD;EAAO,OAAM;EAAkB,YAAS;EAAxC,UAAA;GACE,oBAAC,QAAD,EAAA,UAAM,WAAc,CAAA;GACpB,oBAAC,SAAD;IAAO,MAAK;IAAQ,KAAI;IAAI,KAAI;IAAM,OAAM;IAAK,sBAAA;IAAmB,cAAW;GAAW,CAAA;GAC1F,oBAAC,QAAD,EAAA,UAAM,UAAa,CAAA;EACd;CACP,CAAA,CAAA,EAAA,CAAA;AAEN;;AAiBA,MAAa,gBAAuC,EAClD,aACA,YACA,SACA,SACA,gBAAgB,6DAChB,YAAY,WACZ,eACI;CACJ,OACE,qBAAC,OAAD;EAAK,OAAO;EAAW,gBAAA;EAAa,aAAU;EAAQ,aAAU;EAAM,aAAU;EAAhF,UAAA;GACE,qBAAC,OAAD;IAAK,OAAM;IAAX,UAAA,CACE,oBAAC,WAAD;KAAW,OAAM;KAAkB,qBAAA;KAAkB,OAAO,WAAW,OAAO,OAAO;IAAI,CAAA,GACzF,oBAAC,WAAD;KAAW,OAAM;KAAO,qBAAA;KAAkB,OAAO,WAAW,OAAO,KAAK;IAAI,CAAA,CACzE;;GACL,qBAAC,OAAD;IAAK,OAAM;IAAX,UAAA;KACE,oBAAC,MAAD;MACE,MAAK;MACL,OAAM;MACN,KAAK;MACL,KAAK,gBAAgB;MACrB,OAAO;KACR,CAAA;KACD,oBAAC,MAAD;MACE,MAAK;MACL,OAAM;MACN,KAAK;MACL,KAAK,eAAe;MACpB,OAAM;KACP,CAAA;KACD,oBAAC,MAAD;MACE,MAAK;MACL,OAAM;MACN,KAAK;MACL,KAAK,YAAY;MACjB,OAAO;MACP,MAAM;KACP,CAAA;KACD,oBAAC,QAAD;MAAM,OAAM;MAAiB,eAAY;MACvC,UAAA,oBAAC,QAAD;OAAM,OAAM;OAAc,qBAAA;OAAkB,UAAA;MAEtC,CAAA;KACF,CAAA;IACH;;GACL,oBAAC,eAAD,CAAgB,CAAA;EACb;;AAET"}
|
|
1
|
+
{"version":3,"file":"compare-stage.mjs","names":[],"sources":["../../src/ui/compare-stage.tsx"],"sourcesContent":["import type { FC } from \"hono/jsx\";\nimport { css } from \"./css.ts\";\nimport { Segmented } from \"./segmented.tsx\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\n/**\n * Screenshot comparison surface. All modes share one set of images; the\n * `data-view` / `data-zoom` / `data-flip` attributes on the root (driven by\n * the review script) decide what is shown. Without JS it stays side-by-side.\n */\nconst stageRoot = css`\n /* compare-stage */\n display: grid;\n gap: var(--space-3);\n .stage__bar {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-3);\n align-items: center;\n justify-content: space-between;\n }\n .stage__grid {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: var(--space-3);\n align-items: start;\n }\n .stage__pane {\n position: relative;\n min-width: 0;\n max-height: 78vh;\n overflow: auto;\n background: var(--surface-card);\n border: 1px solid var(--border);\n border-radius: var(--radius);\n }\n .stage__label {\n position: sticky;\n top: 0;\n left: 0;\n z-index: 2;\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0.35rem 0.6rem;\n background: var(--surface-subtle);\n border-bottom: 1px solid var(--border);\n color: var(--text-secondary);\n font-size: var(--text-xs);\n font-weight: 600;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n }\n .stage__img {\n display: block;\n width: 100%;\n height: auto;\n background: repeating-conic-gradient(var(--surface-muted) 0% 25%, var(--surface-subtle) 0% 50%)\n 0 0 / 16px 16px;\n }\n .stage__empty {\n display: grid;\n place-items: center;\n aspect-ratio: 16 / 9;\n padding: var(--space-4);\n background: var(--surface-subtle);\n color: var(--text-secondary);\n font-size: var(--text-sm);\n text-align: center;\n }\n .stage__note {\n color: var(--text-secondary);\n font-size: var(--text-sm);\n }\n .stage__divider,\n .stage__controls {\n display: none;\n }\n .stage__controls {\n align-items: center;\n gap: var(--space-3);\n color: var(--text-secondary);\n font-size: var(--text-sm);\n }\n .stage__controls input {\n flex: 1;\n accent-color: var(--accent);\n }\n &[data-zoom=\"100\"] .stage__img {\n width: auto;\n max-width: none;\n }\n &[data-zoom=\"200\"] .stage__img {\n width: auto;\n max-width: none;\n zoom: 2;\n }\n &[data-zoom=\"400\"] .stage__img {\n width: auto;\n max-width: none;\n zoom: 4;\n }\n /* Zoomed in, show real pixels instead of a smoothed blur. */\n &[data-zoom=\"200\"] .stage__img,\n &[data-zoom=\"400\"] .stage__img {\n image-rendering: pixelated;\n }\n /* Design variant: screenshot against a render, two panes and no diff. */\n &[data-variant=\"design\"][data-view=\"split\"] .stage__grid {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n }\n &[data-view=\"diff\"] .stage__grid,\n &[data-view=\"flip\"] .stage__grid,\n &[data-view=\"swipe\"] .stage__grid,\n &[data-view=\"onion\"] .stage__grid {\n grid-template-columns: minmax(0, 1fr);\n }\n &[data-view=\"diff\"] .stage__pane:not([data-pane=\"diff\"]) {\n display: none;\n }\n &[data-view=\"flip\"][data-flip=\"baseline\"] .stage__pane:not([data-pane=\"baseline\"]),\n &[data-view=\"flip\"]:not([data-flip=\"baseline\"]) .stage__pane:not([data-pane=\"current\"]) {\n display: none;\n }\n &[data-view=\"swipe\"] .stage__pane[data-pane=\"diff\"],\n &[data-view=\"onion\"] .stage__pane[data-pane=\"diff\"] {\n display: none;\n }\n &[data-view=\"swipe\"] .stage__pane,\n &[data-view=\"onion\"] .stage__pane,\n &[data-view=\"swipe\"] .stage__divider {\n grid-area: 1 / 1;\n }\n &[data-view=\"swipe\"] .stage__label,\n &[data-view=\"onion\"] .stage__label {\n display: none;\n }\n &[data-view=\"swipe\"] .stage__pane[data-pane=\"current\"] {\n clip-path: inset(0 0 0 var(--swipe, 50%));\n background: transparent;\n }\n &[data-view=\"onion\"] .stage__pane[data-pane=\"current\"] {\n opacity: var(--onion, 0.5);\n background: transparent;\n }\n &[data-view=\"swipe\"] .stage__divider {\n position: relative;\n display: block;\n z-index: 3;\n justify-self: start;\n width: 2px;\n height: 100%;\n margin-left: var(--swipe, 50%);\n background: var(--accent);\n pointer-events: none;\n }\n /* The only part of the divider that takes pointer input: drag it to wipe. */\n .stage__knob {\n position: absolute;\n top: 50%;\n left: 50%;\n display: grid;\n place-items: center;\n width: 32px;\n height: 32px;\n transform: translate(-50%, -50%);\n background: var(--accent);\n color: var(--accent-contrast);\n border: 2px solid var(--surface-card);\n border-radius: var(--radius-pill);\n box-shadow: var(--shadow-2);\n font-size: var(--text-lg);\n line-height: 1;\n cursor: ew-resize;\n pointer-events: auto;\n touch-action: none;\n user-select: none;\n }\n &[data-view=\"flip\"] .stage__grid {\n cursor: pointer;\n }\n &[data-view=\"swipe\"] .stage__controls[data-for=\"swipe\"],\n &[data-view=\"onion\"] .stage__controls[data-for=\"onion\"] {\n display: flex;\n }\n`;\n\nconst MODES = [\n { label: \"Side by side\", value: \"split\" },\n { label: \"Swipe\", value: \"swipe\" },\n { label: \"Onion skin\", value: \"onion\" },\n { label: \"Diff\", value: \"diff\" },\n { label: \"Flip\", value: \"flip\" },\n];\n\nconst ZOOMS = [\n { label: \"Fit\", value: \"fit\" },\n { label: \"100%\", value: \"100\" },\n { label: \"200%\", value: \"200\" },\n { label: \"400%\", value: \"400\" },\n];\n\n/** Modes and zoom a design comparison offers: no pixel diff, and the render always fits the stage. */\nconst DESIGN_MODES = [\"split\", \"swipe\", \"onion\"];\n\nfunction withActive(\n items: { label: string; value: string }[],\n active: string,\n): { label: string; value: string; active: boolean }[] {\n return items.map((item) => ({ ...item, active: item.value === active }));\n}\n\ninterface PaneProps {\n kind: \"baseline\" | \"current\" | \"diff\";\n label: string;\n src: string | null;\n alt: string;\n empty: unknown;\n meta?: string;\n}\n\nconst Pane: FC<PaneProps> = ({ kind, label, src, alt, empty, meta }) => {\n return (\n <div class=\"stage__pane\" data-pane={kind}>\n <div class=\"stage__label\">\n <span>{label}</span>\n {meta ? <span>{meta}</span> : null}\n </div>\n {src ? (\n <img class=\"stage__img\" src={src} alt={alt} loading=\"lazy\" decoding=\"async\" />\n ) : (\n <div class=\"stage__empty\">{empty}</div>\n )}\n </div>\n );\n};\n\n/** Swipe and onion-skin sliders; each is shown only in its own mode (CSS). */\nconst RangeControls: FC<{ baselineLabel: string }> = ({ baselineLabel }) => {\n return (\n <>\n <label class=\"stage__controls\" data-for=\"swipe\">\n <span>{baselineLabel}</span>\n <input type=\"range\" min=\"0\" max=\"100\" value=\"50\" data-compare-swipe aria-label=\"Swipe\" />\n <span>Current</span>\n </label>\n <label class=\"stage__controls\" data-for=\"onion\">\n <span>{baselineLabel}</span>\n <input type=\"range\" min=\"0\" max=\"100\" value=\"50\" data-compare-onion aria-label=\"Opacity\" />\n <span>Current</span>\n </label>\n </>\n );\n};\n\n/** Props for the screenshot comparison surface. */\nexport interface CompareStageProps {\n /** Baseline image URL, or null when the story has no baseline yet. */\n baselineSrc: string | null;\n currentSrc: string;\n /** Diff overlay URL, or null when none was produced. */\n diffSrc: string | null;\n /** Accessible description shared by the images, e.g. \"Button / Primary\". */\n subject: string;\n baselineEmpty?: unknown;\n diffEmpty?: unknown;\n diffMeta?: string;\n /**\n * `design` compares the screenshot with a design render instead of a baseline: side by side,\n * swipe and onion only (no diff, no flip), fit zoom only.\n */\n variant?: \"baseline\" | \"design\";\n /** Name of the left-hand image in labels and sliders (default \"Baseline\"). */\n baselineLabel?: string;\n /** A line under the controls, e.g. what the left-hand image is. */\n note?: string;\n}\n\n/** Mode switch, plus the zoom switch (a design comparison always fits, so it has none). */\nconst StageBar: FC<{ design: boolean }> = ({ design }) => {\n const modes = design ? MODES.filter((mode) => DESIGN_MODES.includes(mode.value)) : MODES;\n return (\n <div class=\"stage__bar\">\n <Segmented label=\"Comparison mode\" data-compare-mode items={withActive(modes, \"split\")} />\n {design ? null : (\n <Segmented label=\"Zoom\" data-compare-zoom items={withActive(ZOOMS, \"fit\")} />\n )}\n </div>\n );\n};\n\n/** The panes (baseline or design, current, diff) and the swipe divider. */\nconst StageGrid: FC<CompareStageProps & { design: boolean; baselineLabel: string }> = ({\n baselineSrc,\n currentSrc,\n diffSrc,\n subject,\n baselineEmpty = \"New story — no baseline yet. Approve to set the baseline.\",\n diffEmpty = \"No diff\",\n diffMeta,\n design,\n baselineLabel,\n}) => (\n <div class=\"stage__grid\">\n <Pane\n kind=\"baseline\"\n label={baselineLabel}\n src={baselineSrc}\n alt={`${baselineLabel} for ${subject}`}\n empty={baselineEmpty}\n />\n <Pane\n kind=\"current\"\n label=\"Current\"\n src={currentSrc}\n alt={`Current for ${subject}`}\n empty=\"No screenshot\"\n />\n {design ? null : (\n <Pane\n kind=\"diff\"\n label=\"Diff\"\n src={diffSrc}\n alt={`Diff for ${subject}`}\n empty={diffEmpty}\n meta={diffMeta}\n />\n )}\n <span class=\"stage__divider\" aria-hidden=\"true\">\n <span class=\"stage__knob\" data-compare-knob>\n ↔\n </span>\n </span>\n </div>\n);\n\n/** Baseline / current / diff comparison with swipe, onion-skin, flip, and zoom. */\nexport const CompareStage: FC<CompareStageProps> = ({\n variant = \"baseline\",\n baselineLabel = \"Baseline\",\n note,\n ...images\n}) => {\n const design = variant === \"design\";\n return (\n <div\n class={stageRoot}\n data-compare\n data-view=\"split\"\n data-zoom=\"fit\"\n data-flip=\"current\"\n data-variant={variant}\n data-modes={design ? DESIGN_MODES.join(\",\") : undefined}\n data-zooms={design ? \"fit\" : undefined}\n >\n <StageBar design={design} />\n {note ? <p class=\"stage__note\">{note}</p> : null}\n <StageGrid {...images} design={design} baselineLabel={baselineLabel} />\n <RangeControls baselineLabel={baselineLabel} />\n </div>\n );\n};\n"],"mappings":"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"}
|
package/dist/ui/csrf-field.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import.meta.url;
|
|
|
4
4
|
import { getStore } from "../store.mjs";
|
|
5
5
|
import { getCsrfToken } from "../middleware/csrf.mjs";
|
|
6
6
|
import { jsx } from "hono/jsx/jsx-runtime";
|
|
7
|
-
|
|
7
|
+
// src/ui/csrf-field.tsx
|
|
8
8
|
/** Hidden CSRF token so native (non-HTMX) form posts pass the csrf middleware. */
|
|
9
9
|
function csrfField() {
|
|
10
10
|
const { config, sessionId } = getStore();
|
|
@@ -14,7 +14,6 @@ function csrfField() {
|
|
|
14
14
|
value: getCsrfToken(config.secret, sessionId)
|
|
15
15
|
});
|
|
16
16
|
}
|
|
17
|
-
//#endregion
|
|
18
17
|
export { csrfField };
|
|
19
18
|
|
|
20
19
|
//# sourceMappingURL=csrf-field.mjs.map
|
package/dist/ui/css.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import "node:path";
|
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
4
|
import { createCssContext } from "hono/css";
|
|
5
|
-
|
|
5
|
+
// src/ui/css.ts
|
|
6
6
|
/**
|
|
7
7
|
* hono/css building blocks for the server-rendered UI.
|
|
8
8
|
*
|
|
@@ -30,7 +30,6 @@ const context = createCssContext({
|
|
|
30
30
|
const css = (strings, ...values) => context.css(strings, ...values);
|
|
31
31
|
/** Collects every used class into `<style id="storyshelf-css">`. */
|
|
32
32
|
const Style = (args) => context.Style(args);
|
|
33
|
-
//#endregion
|
|
34
33
|
export { Style, css };
|
|
35
34
|
|
|
36
35
|
//# sourceMappingURL=css.mjs.map
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { css } from "./css.mjs";
|
|
5
|
+
import { Icon } from "./icons/icon.mjs";
|
|
6
|
+
import { Fragment, jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
7
|
+
// src/ui/design-reference.tsx
|
|
8
|
+
const referenceRoot = css`
|
|
9
|
+
/* design-reference */
|
|
10
|
+
display: grid;
|
|
11
|
+
background: var(--surface-card);
|
|
12
|
+
border: 1px solid var(--border);
|
|
13
|
+
border-radius: var(--radius);
|
|
14
|
+
overflow: hidden;
|
|
15
|
+
.reference__label {
|
|
16
|
+
display: flex;
|
|
17
|
+
align-items: center;
|
|
18
|
+
justify-content: space-between;
|
|
19
|
+
gap: var(--space-3);
|
|
20
|
+
padding: 0.35rem 0.6rem;
|
|
21
|
+
background: var(--surface-subtle);
|
|
22
|
+
border-bottom: 1px solid var(--border);
|
|
23
|
+
color: var(--text-secondary);
|
|
24
|
+
font-size: var(--text-xs);
|
|
25
|
+
font-weight: 600;
|
|
26
|
+
letter-spacing: 0.06em;
|
|
27
|
+
text-transform: uppercase;
|
|
28
|
+
}
|
|
29
|
+
.reference__img {
|
|
30
|
+
display: block;
|
|
31
|
+
max-width: 100%;
|
|
32
|
+
max-height: 60vh;
|
|
33
|
+
margin: 0 auto;
|
|
34
|
+
object-fit: contain;
|
|
35
|
+
}
|
|
36
|
+
`;
|
|
37
|
+
const previewImg = css`
|
|
38
|
+
/* design-preview */
|
|
39
|
+
display: block;
|
|
40
|
+
width: 16rem;
|
|
41
|
+
max-width: 100%;
|
|
42
|
+
max-height: 14rem;
|
|
43
|
+
margin: 0.25rem auto;
|
|
44
|
+
object-fit: contain;
|
|
45
|
+
border-radius: var(--radius-sm);
|
|
46
|
+
background: repeating-conic-gradient(var(--surface-muted) 0% 25%, var(--surface-subtle) 0% 50%) 0
|
|
47
|
+
0 / 16px 16px;
|
|
48
|
+
`;
|
|
49
|
+
const previewCaption = css`
|
|
50
|
+
/* design-preview-caption */
|
|
51
|
+
padding: 0 0.6rem 0.25rem;
|
|
52
|
+
color: var(--text-secondary);
|
|
53
|
+
font-size: var(--text-xs);
|
|
54
|
+
`;
|
|
55
|
+
/** Cached design render shown as a reference pane under the diff stage. */
|
|
56
|
+
const DesignReference = ({ src, name, href, providerName }) => /* @__PURE__ */ jsxs("figure", {
|
|
57
|
+
class: referenceRoot,
|
|
58
|
+
children: [/* @__PURE__ */ jsxs("figcaption", {
|
|
59
|
+
class: "reference__label",
|
|
60
|
+
children: [/* @__PURE__ */ jsxs("span", { children: [
|
|
61
|
+
providerName,
|
|
62
|
+
" reference",
|
|
63
|
+
name ? ` · ${name}` : ""
|
|
64
|
+
] }), /* @__PURE__ */ jsxs("a", {
|
|
65
|
+
href,
|
|
66
|
+
target: "_blank",
|
|
67
|
+
rel: "noopener noreferrer",
|
|
68
|
+
children: [
|
|
69
|
+
/* @__PURE__ */ jsx(Icon, {
|
|
70
|
+
name: "external-link",
|
|
71
|
+
size: "sm"
|
|
72
|
+
}),
|
|
73
|
+
" Open in ",
|
|
74
|
+
providerName
|
|
75
|
+
]
|
|
76
|
+
})]
|
|
77
|
+
}), /* @__PURE__ */ jsx("img", {
|
|
78
|
+
class: "reference__img",
|
|
79
|
+
src,
|
|
80
|
+
alt: name ? `${providerName} design: ${name}` : `${providerName} design`,
|
|
81
|
+
loading: "lazy"
|
|
82
|
+
})]
|
|
83
|
+
});
|
|
84
|
+
/** Small cached render for popovers, with an optional caption (the design name). */
|
|
85
|
+
const DesignPreview = ({ src, name, providerName }) => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("img", {
|
|
86
|
+
class: previewImg,
|
|
87
|
+
src,
|
|
88
|
+
alt: name ? `${providerName} design: ${name}` : `${providerName} design`,
|
|
89
|
+
loading: "lazy"
|
|
90
|
+
}), name ? /* @__PURE__ */ jsx("div", {
|
|
91
|
+
class: previewCaption,
|
|
92
|
+
children: name
|
|
93
|
+
}) : null] });
|
|
94
|
+
export { DesignPreview, DesignReference };
|
|
95
|
+
|
|
96
|
+
//# sourceMappingURL=design-reference.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"design-reference.mjs","names":[],"sources":["../../src/ui/design-reference.tsx"],"sourcesContent":["import type { FC } from \"hono/jsx\";\nimport { css } from \"./css.ts\";\nimport { Icon } from \"./icons/icon.tsx\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\nconst referenceRoot = css`\n /* design-reference */\n display: grid;\n background: var(--surface-card);\n border: 1px solid var(--border);\n border-radius: var(--radius);\n overflow: hidden;\n .reference__label {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--space-3);\n padding: 0.35rem 0.6rem;\n background: var(--surface-subtle);\n border-bottom: 1px solid var(--border);\n color: var(--text-secondary);\n font-size: var(--text-xs);\n font-weight: 600;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n }\n .reference__img {\n display: block;\n max-width: 100%;\n max-height: 60vh;\n margin: 0 auto;\n object-fit: contain;\n }\n`;\n\nconst previewImg = css`\n /* design-preview */\n display: block;\n width: 16rem;\n max-width: 100%;\n max-height: 14rem;\n margin: 0.25rem auto;\n object-fit: contain;\n border-radius: var(--radius-sm);\n background: repeating-conic-gradient(var(--surface-muted) 0% 25%, var(--surface-subtle) 0% 50%) 0\n 0 / 16px 16px;\n`;\n\nconst previewCaption = css`\n /* design-preview-caption */\n padding: 0 0.6rem 0.25rem;\n color: var(--text-secondary);\n font-size: var(--text-xs);\n`;\n\n/** Cached design render shown as a reference pane under the diff stage. */\nexport const DesignReference: FC<{\n src: string;\n name?: string | null;\n href: string;\n providerName: string;\n}> = ({ src, name, href, providerName }) => (\n <figure class={referenceRoot}>\n <figcaption class=\"reference__label\">\n <span>\n {providerName} reference{name ? ` · ${name}` : \"\"}\n </span>\n <a href={href} target=\"_blank\" rel=\"noopener noreferrer\">\n <Icon name=\"external-link\" size=\"sm\" /> Open in {providerName}\n </a>\n </figcaption>\n <img\n class=\"reference__img\"\n src={src}\n alt={name ? `${providerName} design: ${name}` : `${providerName} design`}\n loading=\"lazy\"\n />\n </figure>\n);\n\n/** Small cached render for popovers, with an optional caption (the design name). */\nexport const DesignPreview: FC<{ src: string; name?: string | null; providerName: string }> = ({\n src,\n name,\n providerName,\n}) => (\n <>\n <img\n class={previewImg}\n src={src}\n alt={name ? `${providerName} design: ${name}` : `${providerName} design`}\n loading=\"lazy\"\n />\n {name ? <div class={previewCaption}>{name}</div> : null}\n </>\n);\n"],"mappings":";;;;;;;AAMA,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BzB,MAAM,aAAa,GAAG;;;;;;;;;;;;AAatB,MAAM,iBAAiB,GAAG;;;;;;;AAQ1B,MAAa,mBAKP,EAAE,KAAK,MAAM,MAAM,mBACvB,qBAAC,UAAD;CAAQ,OAAO;CAAf,UAAA,CACE,qBAAC,cAAD;EAAY,OAAM;EAAlB,UAAA,CACE,qBAAC,QAAD,EAAA,UAAA;GACG;GAAa;GAAW,OAAO,MAAM,SAAS;EAC3C,EAAA,CAAA,GACN,qBAAC,KAAD;GAAS;GAAM,QAAO;GAAS,KAAI;GAAnC,UAAA;IACE,oBAAC,MAAD;KAAM,MAAK;KAAgB,MAAK;IAAM,CAAA;IAAC;IAAU;GAChD;EACO,CAAA,CAAA;CACZ,CAAA,GAAA,oBAAC,OAAD;EACE,OAAM;EACD;EACL,KAAK,OAAO,GAAG,aAAa,WAAW,SAAS,GAAG,aAAa;EAChE,SAAQ;CACT,CAAA,CACK;;;AAIV,MAAa,iBAAkF,EAC7F,KACA,MACA,mBAEA,qBAAA,UAAA,EAAA,UAAA,CACE,oBAAC,OAAD;CACE,OAAO;CACF;CACL,KAAK,OAAO,GAAG,aAAa,WAAW,SAAS,GAAG,aAAa;CAChE,SAAQ;AACT,CAAA,GACA,OAAO,oBAAC,OAAD;CAAK,OAAO;CAAiB,UAAA;AAAU,CAAA,IAAI,IACnD,EAAA,CAAA"}
|
package/dist/ui/document.mjs
CHANGED
|
@@ -7,14 +7,13 @@ import { Style, css } from "./css.mjs";
|
|
|
7
7
|
import { assetManifest } from "../asset-manifest.mjs";
|
|
8
8
|
import { BareShell } from "./shell/bare.mjs";
|
|
9
9
|
import { bootScript } from "./shell/boot-script.mjs";
|
|
10
|
-
import { clientScript } from "./shell/client-script.mjs";
|
|
11
10
|
import { Sidebar } from "./shell/sidebar.mjs";
|
|
12
11
|
import { ToastRegion, TopBar } from "./shell/topbar.mjs";
|
|
13
12
|
import { baseStyle } from "./styles.mjs";
|
|
14
13
|
import { DARK_THEME, LIGHT_THEME } from "./theme.mjs";
|
|
15
14
|
import { Fragment, jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
16
15
|
import { raw } from "hono/html";
|
|
17
|
-
|
|
16
|
+
// src/ui/document.tsx
|
|
18
17
|
const shellRoot = css`
|
|
19
18
|
/* shell */
|
|
20
19
|
display: grid;
|
|
@@ -120,11 +119,10 @@ const DocumentLayout = ({ title, nav, layout = "default", children }) => {
|
|
|
120
119
|
})] }),
|
|
121
120
|
/* @__PURE__ */ jsx(ToastRegion, {}),
|
|
122
121
|
/* @__PURE__ */ jsx("script", { src: assetManifest.htmx.href }),
|
|
123
|
-
/* @__PURE__ */ jsx("script", {
|
|
122
|
+
/* @__PURE__ */ jsx("script", { src: assetManifest.client.href })
|
|
124
123
|
] })]
|
|
125
124
|
})] });
|
|
126
125
|
};
|
|
127
|
-
//#endregion
|
|
128
126
|
export { DocumentLayout };
|
|
129
127
|
|
|
130
128
|
//# sourceMappingURL=document.mjs.map
|