@storyshelf/app 0.6.2 → 0.7.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 +1 -3
- package/dist/asset-manifest.mjs +13 -5
- package/dist/asset-manifest.mjs.map +1 -1
- package/dist/assets/{htmx.min.js → htmx.mjs} +10 -1
- package/dist/assets/htmx.mjs.map +1 -0
- package/dist/assets/mark.mjs +10 -0
- package/dist/assets/mark.mjs.map +1 -0
- package/dist/boot-tasks.mjs +80 -0
- package/dist/boot-tasks.mjs.map +1 -0
- package/dist/build-state-flags.mjs +18 -0
- package/dist/build-state-flags.mjs.map +1 -0
- package/dist/build-state-setup.mjs +75 -0
- package/dist/build-state-setup.mjs.map +1 -0
- package/dist/capture-alerts.mjs +46 -0
- package/dist/capture-alerts.mjs.map +1 -0
- package/dist/capture-process.mjs +59 -0
- package/dist/capture-process.mjs.map +1 -0
- package/dist/capture-setup.mjs +95 -117
- package/dist/capture-setup.mjs.map +1 -1
- package/dist/connections.mjs +34 -0
- package/dist/connections.mjs.map +1 -0
- package/dist/consumer-setup.mjs +58 -0
- package/dist/consumer-setup.mjs.map +1 -0
- package/dist/git-status.mjs +46 -0
- package/dist/git-status.mjs.map +1 -0
- package/dist/hidden-data.mjs +24 -0
- package/dist/hidden-data.mjs.map +1 -0
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +203 -125
- package/dist/index.mjs.map +1 -1
- package/dist/insights/budget.mjs +49 -0
- package/dist/insights/budget.mjs.map +1 -0
- package/dist/insights/deps.mjs +21 -0
- package/dist/insights/deps.mjs.map +1 -0
- package/dist/insights/errors.mjs +22 -0
- package/dist/insights/errors.mjs.map +1 -0
- package/dist/insights/health-panel.mjs +40 -0
- package/dist/insights/health-panel.mjs.map +1 -0
- package/dist/insights/job.mjs +103 -0
- package/dist/insights/job.mjs.map +1 -0
- package/dist/insights/load-health.mjs +59 -0
- package/dist/insights/load-health.mjs.map +1 -0
- package/dist/insights/load-triage.mjs +98 -0
- package/dist/insights/load-triage.mjs.map +1 -0
- package/dist/insights/panel.mjs +28 -0
- package/dist/insights/panel.mjs.map +1 -0
- package/dist/insights/request.mjs +113 -0
- package/dist/insights/request.mjs.map +1 -0
- package/dist/insights/stale.mjs +21 -0
- package/dist/insights/stale.mjs.map +1 -0
- package/dist/insights/usage-report.mjs +60 -0
- package/dist/insights/usage-report.mjs.map +1 -0
- package/dist/insights/view.mjs +48 -0
- package/dist/insights/view.mjs.map +1 -0
- package/dist/job-canceller.mjs +14 -0
- package/dist/job-canceller.mjs.map +1 -0
- package/dist/job-queue.mjs +51 -0
- package/dist/job-queue.mjs.map +1 -0
- package/dist/jobs-setup.mjs +132 -0
- package/dist/jobs-setup.mjs.map +1 -0
- package/dist/lifecycle.mjs +91 -19
- package/dist/lifecycle.mjs.map +1 -1
- package/dist/middleware/auth-gate.mjs +4 -2
- package/dist/middleware/auth-gate.mjs.map +1 -1
- package/dist/middleware/rate-limit.mjs +41 -33
- package/dist/middleware/rate-limit.mjs.map +1 -1
- package/dist/middleware/request-log.mjs +3 -2
- package/dist/middleware/request-log.mjs.map +1 -1
- package/dist/middleware/require-review.mjs +51 -0
- package/dist/middleware/require-review.mjs.map +1 -0
- package/dist/middleware/require-testing.mjs +41 -0
- package/dist/middleware/require-testing.mjs.map +1 -0
- package/dist/middleware/setup-gate.mjs.map +1 -1
- package/dist/middleware/store-scope.mjs +12 -2
- package/dist/middleware/store-scope.mjs.map +1 -1
- package/dist/notifications-pause.mjs +33 -0
- package/dist/notifications-pause.mjs.map +1 -0
- package/dist/notify.d.mts +2 -1
- package/dist/notify.mjs +78 -39
- package/dist/notify.mjs.map +1 -1
- package/dist/pages/a11y-violations.mjs +116 -0
- package/dist/pages/a11y-violations.mjs.map +1 -0
- package/dist/pages/admin-ai.mjs +78 -0
- package/dist/pages/admin-ai.mjs.map +1 -0
- package/dist/pages/admin-system-origin.mjs +123 -0
- package/dist/pages/admin-system-origin.mjs.map +1 -0
- package/dist/pages/admin-system-targets.mjs +53 -0
- package/dist/pages/admin-system-targets.mjs.map +1 -0
- package/dist/pages/admin-system.mjs +63 -13
- package/dist/pages/admin-system.mjs.map +1 -1
- package/dist/pages/build-detail.mjs +104 -76
- package/dist/pages/build-detail.mjs.map +1 -1
- package/dist/pages/build-diff-actions.mjs +87 -33
- package/dist/pages/build-diff-actions.mjs.map +1 -1
- package/dist/pages/build-diff-comments.mjs +16 -14
- package/dist/pages/build-diff-comments.mjs.map +1 -1
- package/dist/pages/build-diff-environment.mjs +82 -0
- package/dist/pages/build-diff-environment.mjs.map +1 -0
- package/dist/pages/build-diff-header.mjs +22 -4
- package/dist/pages/build-diff-header.mjs.map +1 -1
- package/dist/pages/build-diff-insight.mjs +113 -0
- package/dist/pages/build-diff-insight.mjs.map +1 -0
- package/dist/pages/build-diff-model.mjs +55 -10
- package/dist/pages/build-diff-model.mjs.map +1 -1
- package/dist/pages/build-diff-nav.mjs +28 -12
- package/dist/pages/build-diff-nav.mjs.map +1 -1
- package/dist/pages/build-diff-shortcuts.mjs +5 -1
- package/dist/pages/build-diff-shortcuts.mjs.map +1 -1
- package/dist/pages/build-diff-usage.mjs +63 -0
- package/dist/pages/build-diff-usage.mjs.map +1 -0
- package/dist/pages/build-diff-viewer.mjs +62 -5
- package/dist/pages/build-diff-viewer.mjs.map +1 -1
- package/dist/pages/build-diff.mjs +34 -13
- package/dist/pages/build-diff.mjs.map +1 -1
- package/dist/pages/build-tests.mjs +74 -0
- package/dist/pages/build-tests.mjs.map +1 -0
- package/dist/pages/component-detail.mjs +249 -0
- package/dist/pages/component-detail.mjs.map +1 -0
- package/dist/pages/component-stories.mjs +168 -0
- package/dist/pages/component-stories.mjs.map +1 -0
- package/dist/pages/compute-jobs.mjs +13 -26
- package/dist/pages/compute-jobs.mjs.map +1 -1
- package/dist/pages/design-links.mjs +182 -0
- package/dist/pages/design-links.mjs.map +1 -0
- package/dist/pages/environment-change.mjs +53 -0
- package/dist/pages/environment-change.mjs.map +1 -0
- package/dist/pages/health-panel.mjs +126 -0
- package/dist/pages/health-panel.mjs.map +1 -0
- package/dist/pages/library-grid.mjs +105 -0
- package/dist/pages/library-grid.mjs.map +1 -0
- package/dist/pages/library-model.mjs +47 -0
- package/dist/pages/library-model.mjs.map +1 -0
- package/dist/pages/library-snapshots.mjs +130 -0
- package/dist/pages/library-snapshots.mjs.map +1 -0
- package/dist/pages/library.mjs +112 -218
- package/dist/pages/library.mjs.map +1 -1
- package/dist/pages/notifications-paused.mjs +22 -0
- package/dist/pages/notifications-paused.mjs.map +1 -0
- package/dist/pages/profile.mjs +53 -48
- package/dist/pages/profile.mjs.map +1 -1
- package/dist/pages/project-builds-table.mjs +2 -2
- package/dist/pages/project-builds-table.mjs.map +1 -1
- package/dist/pages/project-builds.mjs +51 -21
- package/dist/pages/project-builds.mjs.map +1 -1
- package/dist/pages/project-settings.mjs +51 -12
- package/dist/pages/project-settings.mjs.map +1 -1
- package/dist/pages/projects-card.mjs +14 -1
- package/dist/pages/projects-card.mjs.map +1 -1
- package/dist/pages/projects.mjs +6 -2
- package/dist/pages/projects.mjs.map +1 -1
- package/dist/pages/review-changeset.mjs +116 -0
- package/dist/pages/review-changeset.mjs.map +1 -0
- package/dist/pages/review-comments.mjs +103 -0
- package/dist/pages/review-comments.mjs.map +1 -0
- package/dist/pages/review-verdict.mjs +102 -0
- package/dist/pages/review-verdict.mjs.map +1 -0
- package/dist/pages/settings-ai.mjs +69 -0
- package/dist/pages/settings-ai.mjs.map +1 -0
- package/dist/pages/settings-capture.mjs +124 -0
- package/dist/pages/settings-capture.mjs.map +1 -0
- package/dist/pages/settings-design-credential.mjs +99 -0
- package/dist/pages/settings-design-credential.mjs.map +1 -0
- package/dist/pages/settings-design-links.mjs +179 -0
- package/dist/pages/settings-design-links.mjs.map +1 -0
- package/dist/pages/settings-design.mjs +48 -0
- package/dist/pages/settings-design.mjs.map +1 -0
- package/dist/pages/settings-general.mjs +46 -21
- package/dist/pages/settings-general.mjs.map +1 -1
- package/dist/pages/settings-labels.mjs +43 -2
- package/dist/pages/settings-labels.mjs.map +1 -1
- package/dist/pages/settings-links.mjs +112 -0
- package/dist/pages/settings-links.mjs.map +1 -0
- package/dist/pages/settings-notifications-git.mjs +128 -0
- package/dist/pages/settings-notifications-git.mjs.map +1 -0
- package/dist/pages/settings-notifications.mjs +79 -42
- package/dist/pages/settings-notifications.mjs.map +1 -1
- package/dist/pages/settings-tests.mjs +33 -2
- package/dist/pages/settings-tests.mjs.map +1 -1
- package/dist/pages/target-choices.mjs +39 -0
- package/dist/pages/target-choices.mjs.map +1 -0
- package/dist/publish-setup.mjs +89 -0
- package/dist/publish-setup.mjs.map +1 -0
- package/dist/retention-timer.mjs +2 -1
- package/dist/retention-timer.mjs.map +1 -1
- package/dist/review-events.mjs +48 -0
- package/dist/review-events.mjs.map +1 -0
- package/dist/routers/admin-pages.mjs +45 -3
- package/dist/routers/admin-pages.mjs.map +1 -1
- package/dist/routers/admin-public-origin.mjs +85 -0
- package/dist/routers/admin-public-origin.mjs.map +1 -0
- package/dist/routers/assets.mjs +9 -2
- package/dist/routers/assets.mjs.map +1 -1
- package/dist/routers/auth.mjs +1 -1
- package/dist/routers/baseline-guard.mjs +2 -2
- package/dist/routers/baseline-guard.mjs.map +1 -1
- package/dist/routers/baselines.handlers.mjs +64 -0
- package/dist/routers/baselines.handlers.mjs.map +1 -0
- package/dist/routers/baselines.mjs +125 -0
- package/dist/routers/baselines.mjs.map +1 -0
- package/dist/routers/build-cancel.mjs +68 -0
- package/dist/routers/build-cancel.mjs.map +1 -0
- package/dist/routers/build-changeset.mjs +70 -0
- package/dist/routers/build-changeset.mjs.map +1 -0
- package/dist/routers/build-reviews.handlers.mjs +86 -0
- package/dist/routers/build-reviews.handlers.mjs.map +1 -0
- package/dist/routers/build-reviews.mjs +74 -0
- package/dist/routers/build-reviews.mjs.map +1 -0
- package/dist/routers/builds.handlers.mjs +46 -55
- package/dist/routers/builds.handlers.mjs.map +1 -1
- package/dist/routers/builds.mjs +71 -17
- package/dist/routers/builds.mjs.map +1 -1
- package/dist/routers/capture-config.mjs +35 -0
- package/dist/routers/capture-config.mjs.map +1 -0
- package/dist/routers/comments.mjs +41 -15
- package/dist/routers/comments.mjs.map +1 -1
- package/dist/routers/component-catalog.handlers.mjs +71 -0
- package/dist/routers/component-catalog.handlers.mjs.map +1 -0
- package/dist/routers/component.handlers.mjs +121 -0
- package/dist/routers/component.handlers.mjs.map +1 -0
- package/dist/routers/connections.mjs +118 -0
- package/dist/routers/connections.mjs.map +1 -0
- package/dist/routers/design-connect.handlers.mjs +44 -0
- package/dist/routers/design-connect.handlers.mjs.map +1 -0
- package/dist/routers/design.handlers.mjs +114 -0
- package/dist/routers/design.handlers.mjs.map +1 -0
- package/dist/routers/health-report.mjs +2 -3
- package/dist/routers/health-report.mjs.map +1 -1
- package/dist/routers/health.mjs +4 -3
- package/dist/routers/health.mjs.map +1 -1
- package/dist/routers/helpers.mjs +19 -3
- package/dist/routers/helpers.mjs.map +1 -1
- package/dist/routers/insights-ui.mjs +64 -0
- package/dist/routers/insights-ui.mjs.map +1 -0
- package/dist/routers/insights.handlers.mjs +45 -0
- package/dist/routers/insights.handlers.mjs.map +1 -0
- package/dist/routers/insights.mjs +189 -0
- package/dist/routers/insights.mjs.map +1 -0
- package/dist/routers/links.handlers.mjs +80 -0
- package/dist/routers/links.handlers.mjs.map +1 -0
- package/dist/routers/links.mjs +139 -0
- package/dist/routers/links.mjs.map +1 -0
- package/dist/routers/media.mjs +18 -4
- package/dist/routers/media.mjs.map +1 -1
- package/dist/routers/members.mjs +2 -1
- package/dist/routers/members.mjs.map +1 -1
- package/dist/routers/notification-channels.mjs +17 -23
- package/dist/routers/notification-channels.mjs.map +1 -1
- package/dist/routers/oauth-state.mjs +45 -0
- package/dist/routers/oauth-state.mjs.map +1 -0
- package/dist/routers/preview-base.mjs +38 -0
- package/dist/routers/preview-base.mjs.map +1 -0
- package/dist/routers/preview-token.mjs +51 -0
- package/dist/routers/preview-token.mjs.map +1 -0
- package/dist/routers/profile.mjs +3 -1
- package/dist/routers/profile.mjs.map +1 -1
- package/dist/routers/projects.mjs +17 -1
- package/dist/routers/projects.mjs.map +1 -1
- package/dist/routers/publish-upload.mjs +78 -0
- package/dist/routers/publish-upload.mjs.map +1 -0
- package/dist/routers/published-access.mjs +52 -0
- package/dist/routers/published-access.mjs.map +1 -0
- package/dist/routers/published-files.mjs +52 -0
- package/dist/routers/published-files.mjs.map +1 -0
- package/dist/routers/review-forms.mjs +72 -0
- package/dist/routers/review-forms.mjs.map +1 -0
- package/dist/routers/review-section.handlers.mjs +85 -0
- package/dist/routers/review-section.handlers.mjs.map +1 -0
- package/dist/routers/review-switch-off.mjs +29 -0
- package/dist/routers/review-switch-off.mjs.map +1 -0
- package/dist/routers/schemas.mjs +140 -22
- package/dist/routers/schemas.mjs.map +1 -1
- package/dist/routers/scoped-models.mjs +22 -0
- package/dist/routers/scoped-models.mjs.map +1 -0
- package/dist/routers/settings-ai.mjs +35 -0
- package/dist/routers/settings-ai.mjs.map +1 -0
- package/dist/routers/settings-capture.mjs +77 -0
- package/dist/routers/settings-capture.mjs.map +1 -0
- package/dist/routers/settings-design.mjs +103 -0
- package/dist/routers/settings-design.mjs.map +1 -0
- package/dist/routers/settings-general.mjs +55 -10
- package/dist/routers/settings-general.mjs.map +1 -1
- package/dist/routers/settings-git-channel.mjs +77 -0
- package/dist/routers/settings-git-channel.mjs.map +1 -0
- package/dist/routers/settings-labels.mjs +27 -1
- package/dist/routers/settings-labels.mjs.map +1 -1
- package/dist/routers/settings-links.mjs +63 -0
- package/dist/routers/settings-links.mjs.map +1 -0
- package/dist/routers/settings-members.mjs +4 -7
- package/dist/routers/settings-members.mjs.map +1 -1
- package/dist/routers/settings-notifications.mjs +15 -10
- package/dist/routers/settings-notifications.mjs.map +1 -1
- package/dist/routers/settings-tokens.mjs +2 -5
- package/dist/routers/settings-tokens.mjs.map +1 -1
- package/dist/routers/settings-webhooks.mjs +7 -24
- package/dist/routers/settings-webhooks.mjs.map +1 -1
- package/dist/routers/settings.handlers.mjs +107 -26
- package/dist/routers/settings.handlers.mjs.map +1 -1
- package/dist/routers/settings.mjs +16 -4
- package/dist/routers/settings.mjs.map +1 -1
- package/dist/routers/signed-storybook.mjs +53 -0
- package/dist/routers/signed-storybook.mjs.map +1 -0
- package/dist/routers/snapshot-groups.mjs +63 -0
- package/dist/routers/snapshot-groups.mjs.map +1 -0
- package/dist/routers/snapshots.mjs +103 -5
- package/dist/routers/snapshots.mjs.map +1 -1
- package/dist/routers/storybook.mjs +37 -88
- package/dist/routers/storybook.mjs.map +1 -1
- package/dist/routers/tokens.mjs +2 -5
- package/dist/routers/tokens.mjs.map +1 -1
- package/dist/routers/ui.mjs +34 -5
- package/dist/routers/ui.mjs.map +1 -1
- package/dist/routers/webhooks.mjs +5 -11
- package/dist/routers/webhooks.mjs.map +1 -1
- package/dist/routers/well-known.mjs +11 -4
- package/dist/routers/well-known.mjs.map +1 -1
- package/dist/routes/api-routes.mjs +93 -0
- package/dist/routes/api-routes.mjs.map +1 -0
- package/dist/routes/classification.mjs +38 -0
- package/dist/routes/classification.mjs.map +1 -0
- package/dist/routes/openapi-classes.mjs +22 -0
- package/dist/routes/openapi-classes.mjs.map +1 -0
- package/dist/routes/page-routes.mjs +104 -0
- package/dist/routes/page-routes.mjs.map +1 -0
- package/dist/runtime.mjs +18 -13
- package/dist/runtime.mjs.map +1 -1
- package/dist/serverless.mjs +5 -0
- package/dist/store.mjs +19 -1
- package/dist/store.mjs.map +1 -1
- package/dist/targets.mjs +46 -0
- package/dist/targets.mjs.map +1 -0
- package/dist/ui/buttons.mjs +16 -3
- package/dist/ui/buttons.mjs.map +1 -1
- package/dist/ui/design-reference.mjs +97 -0
- package/dist/ui/design-reference.mjs.map +1 -0
- package/dist/ui/document.mjs +1 -2
- package/dist/ui/document.mjs.map +1 -1
- package/dist/ui/forms.mjs +6 -5
- package/dist/ui/forms.mjs.map +1 -1
- package/dist/ui/icons/paths.mjs +1 -0
- package/dist/ui/icons/paths.mjs.map +1 -1
- package/dist/ui/preview-embed.mjs +82 -0
- package/dist/ui/preview-embed.mjs.map +1 -0
- package/dist/ui/shell/client-script.mjs +85 -0
- package/dist/ui/shell/client-script.mjs.map +1 -1
- package/dist/ui/shell/nav.mjs +12 -8
- package/dist/ui/shell/nav.mjs.map +1 -1
- package/dist/ui/shell/review-script.mjs +8 -0
- package/dist/ui/shell/review-script.mjs.map +1 -1
- package/dist/ui/shell/sidebar.mjs +2 -2
- package/dist/ui/shell/sidebar.mjs.map +1 -1
- package/package.json +11 -9
- 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
|
@@ -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/buttons.mjs
CHANGED
|
@@ -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,20 @@ const Button = ({ variant = "primary", size = "md", type = "button", href, disab
|
|
|
199
199
|
children: content
|
|
200
200
|
});
|
|
201
201
|
};
|
|
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
|
+
};
|
|
202
215
|
//#endregion
|
|
203
|
-
export { Button };
|
|
216
|
+
export { Button, Tabs };
|
|
204
217
|
|
|
205
218
|
//# 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"}
|
|
@@ -0,0 +1,97 @@
|
|
|
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
|
+
//#region 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
|
+
//#endregion
|
|
95
|
+
export { DesignPreview, DesignReference };
|
|
96
|
+
|
|
97
|
+
//# 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,7 +7,6 @@ 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";
|
|
@@ -120,7 +119,7 @@ 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
|
};
|
package/dist/ui/document.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"document.mjs","names":[],"sources":["../../src/ui/document.tsx"],"sourcesContent":["import { raw } from \"hono/html\";\nimport type { FC } from \"hono/jsx\";\nimport { assetManifest } from \"../asset-manifest.ts\";\nimport { getCsrfToken } from \"../middleware/csrf.ts\";\nimport { getStore } from \"../store.ts\";\nimport { Style, css } from \"./css.ts\";\nimport { BareShell } from \"./shell/bare.tsx\";\nimport { bootScript } from \"./shell/boot-script.ts\";\nimport
|
|
1
|
+
{"version":3,"file":"document.mjs","names":[],"sources":["../../src/ui/document.tsx"],"sourcesContent":["import { raw } from \"hono/html\";\nimport type { FC } from \"hono/jsx\";\nimport { assetManifest } from \"../asset-manifest.ts\";\nimport { getCsrfToken } from \"../middleware/csrf.ts\";\nimport { getStore } from \"../store.ts\";\nimport { Style, css } from \"./css.ts\";\nimport { BareShell } from \"./shell/bare.tsx\";\nimport { bootScript } from \"./shell/boot-script.ts\";\nimport type { NavConfig } from \"./shell/nav.ts\";\nimport { Sidebar } from \"./shell/sidebar.tsx\";\nimport { ToastRegion, TopBar } from \"./shell/topbar.tsx\";\nimport { baseStyle } from \"./styles.ts\";\nimport { DARK_THEME, LIGHT_THEME } from \"./theme.ts\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\n/** Active section, project, and breadcrumb trail passed to the shell. */\nexport type { NavConfig };\n\n/** Rendered HTML content returned by page components. */\nexport type RenderedContent = string | Promise<string>;\n\n/**\n * Page frame: `default` is a readable column, `wide` uses the full viewport,\n * `bare` drops the app chrome (sign-in, invites).\n */\ntype PageLayout = \"default\" | \"wide\" | \"bare\";\n\nconst shellRoot = css`\n /* shell */\n display: grid;\n grid-template-columns: var(--sidebar-width) minmax(0, 1fr);\n min-height: 100vh;\n @media (max-width: 880px) {\n & {\n grid-template-columns: minmax(0, 1fr);\n }\n }\n`;\n\nconst shellContent = css`\n /* shell-content */\n width: 100%;\n max-width: 1200px;\n margin: 0 auto;\n padding: var(--space-6) var(--space-6) var(--space-8);\n min-width: 0;\n @media (max-width: 880px) {\n & {\n padding: var(--space-4);\n }\n }\n`;\n\nconst shellContentWide = css`\n /* shell-content-wide */\n ${shellContent}\n max-width: none;\n`;\n\nconst shellBackdrop = css`\n /* shell-backdrop */\n position: fixed;\n inset: 0;\n z-index: 55;\n background: rgb(0 0 0 / 0.4);\n &[hidden] {\n display: none;\n }\n`;\n\n// Hono's JSX.Element is typed as `HtmlEscapedString | Promise<...>`, so JSX-returning\n// Functions can legitimately return a promise; the rule is a false positive here.\n/** Full HTML document shell with sidebar, top bar, theme, toasts, and HTMX. */\nexport const DocumentLayout: FC<{\n title: string;\n nav?: NavConfig;\n layout?: PageLayout;\n children?: unknown;\n}> = ({ title, nav, layout = \"default\", children }) => {\n const { ui, config, sessionId } = getStore();\n const name = ui.name ?? \"StoryShelf\";\n const logo = ui.logo ?? assetManifest.mark.href;\n const light = ui.lightTheme ?? LIGHT_THEME;\n const dark = ui.darkTheme ?? DARK_THEME;\n\n return (\n <>\n {/* Without a doctype browsers use quirks mode (form margins, line-box struts, …). */}\n {raw(\"<!DOCTYPE html>\")}\n <html lang=\"en\" data-theme=\"system\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <meta name=\"color-scheme\" content=\"light dark\" />\n <meta name=\"csrf-token\" content={getCsrfToken(config.secret, sessionId)} />\n {/* Favicon is the product's own mark, not brandable: it identifies\n StoryShelf in the tab strip. `ui.logo` stays overridable. */}\n <link rel=\"icon\" type=\"image/svg+xml\" href={assetManifest.mark.href} />\n <title>\n {title} · {name}\n </title>\n <style dangerouslySetInnerHTML={{ __html: baseStyle(light, dark) }} />\n <Style />\n <script dangerouslySetInnerHTML={{ __html: bootScript() }} />\n </head>\n <body>\n <a class=\"skip-link\" href=\"#main-content\">\n Skip to content\n </a>\n {layout === \"bare\" ? (\n <BareShell name={name} logo={logo}>\n {children}\n </BareShell>\n ) : (\n <>\n <div class={shellRoot}>\n <Sidebar nav={nav} name={name} logo={logo} />\n <div>\n <TopBar nav={nav} />\n <main\n id=\"main-content\"\n class={layout === \"wide\" ? shellContentWide : shellContent}\n tabindex={-1}\n >\n {children}\n </main>\n </div>\n </div>\n <div class={shellBackdrop} data-sidebar-backdrop hidden />\n </>\n )}\n <ToastRegion />\n <script src={assetManifest.htmx.href} />\n <script src={assetManifest.client.href} />\n </body>\n </html>\n </>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;AA4BA,MAAM,YAAY,GAAG;;;;;;;;;;;AAYrB,MAAM,eAAe,GAAG;;;;;;;;;;;;;AAcxB,MAAM,mBAAmB,GAAG;;IAExB,aAAa;;;AAIjB,MAAM,gBAAgB,GAAG;;;;;;;;;;;AAczB,MAAa,kBAKP,EAAE,OAAO,KAAK,SAAS,WAAW,eAAe;CACrD,MAAM,EAAE,IAAI,QAAQ,cAAc,SAAS;CAC3C,MAAM,OAAO,GAAG,QAAQ;CACxB,MAAM,OAAO,GAAG,QAAQ,cAAc,KAAK;CAC3C,MAAM,QAAQ,GAAG,cAAc;CAC/B,MAAM,OAAO,GAAG,aAAa;CAE7B,OACE,qBAAA,UAAA,EAAA,UAAA,CAEG,IAAI,iBAAiB,GACtB,qBAAC,QAAD;EAAM,MAAK;EAAK,cAAW;EAA3B,UAAA,CACE,qBAAC,QAAD,EAAA,UAAA;GACE,oBAAC,QAAD,EAAM,SAAQ,QAAS,CAAA;GACvB,oBAAC,QAAD;IAAM,MAAK;IAAW,SAAQ;GAAuC,CAAA;GACrE,oBAAC,QAAD;IAAM,MAAK;IAAe,SAAQ;GAAc,CAAA;GAChD,oBAAC,QAAD;IAAM,MAAK;IAAa,SAAS,aAAa,OAAO,QAAQ,SAAS;GAAI,CAAA;GAG1E,oBAAC,QAAD;IAAM,KAAI;IAAO,MAAK;IAAgB,MAAM,cAAc,KAAK;GAAO,CAAA;GACtE,qBAAC,SAAD,EAAA,UAAA;IACG;IAAM;IAAI;GACN,EAAA,CAAA;GACP,oBAAC,SAAD,EAAO,yBAAyB,EAAE,QAAQ,UAAU,OAAO,IAAI,EAAE,EAAI,CAAA;GACrE,oBAAC,OAAD,CAAQ,CAAA;GACR,oBAAC,UAAD,EAAQ,yBAAyB,EAAE,QAAQ,WAAW,EAAE,EAAI,CAAA;EACxD,EAAA,CAAA,GACN,qBAAC,QAAD,EAAA,UAAA;GACE,oBAAC,KAAD;IAAG,OAAM;IAAY,MAAK;IAAgB,UAAA;GAEvC,CAAA;GACF,WAAW,SACV,oBAAC,WAAD;IAAiB;IAAY;IAC1B;GACQ,CAAA,IAEX,qBAAA,UAAA,EAAA,UAAA,CACE,qBAAC,OAAD;IAAK,OAAO;IAAZ,UAAA,CACE,oBAAC,SAAD;KAAc;KAAW;KAAY;IAAO,CAAA,GAC5C,qBAAC,OAAD,EAAA,UAAA,CACE,oBAAC,QAAD,EAAa,IAAM,CAAA,GACnB,oBAAC,QAAD;KACE,IAAG;KACH,OAAO,WAAW,SAAS,mBAAmB;KAC9C,UAAU;KAET;IACG,CAAA,CACH,EAAA,CAAA,CACF;GACL,CAAA,GAAA,oBAAC,OAAD;IAAK,OAAO;IAAe,yBAAA;IAAsB,QAAA;GAAQ,CAAA,CACzD,EAAA,CAAA;GAEJ,oBAAC,aAAD,CAAc,CAAA;GACd,oBAAC,UAAD,EAAQ,KAAK,cAAc,KAAK,KAAO,CAAA;GACvC,oBAAC,UAAD,EAAQ,KAAK,cAAc,OAAO,KAAO,CAAA;EACrC,EAAA,CAAA,CACF;CACN,CAAA,CAAA,EAAA,CAAA;AAEN"}
|
package/dist/ui/forms.mjs
CHANGED
|
@@ -226,28 +226,29 @@ const CheckField = ({ label, name, value, checked, disabled, hint }) => {
|
|
|
226
226
|
});
|
|
227
227
|
};
|
|
228
228
|
/** Labeled textarea with error and hint states. */
|
|
229
|
-
const TextareaField = ({ label, name, value, placeholder, rows = 3, required, hint, error }) => {
|
|
229
|
+
const TextareaField = ({ label, name, id, value, placeholder, rows = 3, required, hint, error }) => {
|
|
230
|
+
const domId = id ?? name;
|
|
230
231
|
return /* @__PURE__ */ jsxs("div", {
|
|
231
232
|
class: fieldWrap,
|
|
232
233
|
children: [
|
|
233
234
|
/* @__PURE__ */ jsx("label", {
|
|
234
235
|
class: fieldLabel,
|
|
235
|
-
for:
|
|
236
|
+
for: domId,
|
|
236
237
|
children: label
|
|
237
238
|
}),
|
|
238
239
|
/* @__PURE__ */ jsx("textarea", {
|
|
239
240
|
class: error ? fieldTextareaError : fieldTextarea,
|
|
240
|
-
id:
|
|
241
|
+
id: domId,
|
|
241
242
|
name,
|
|
242
243
|
placeholder,
|
|
243
244
|
rows,
|
|
244
245
|
required,
|
|
245
246
|
"aria-invalid": error ? "true" : void 0,
|
|
246
|
-
"aria-describedby": fieldDescribedBy(
|
|
247
|
+
"aria-describedby": fieldDescribedBy(domId, error, hint),
|
|
247
248
|
children: value
|
|
248
249
|
}),
|
|
249
250
|
/* @__PURE__ */ jsx(FieldAssistant, {
|
|
250
|
-
name,
|
|
251
|
+
name: domId,
|
|
251
252
|
error,
|
|
252
253
|
hint
|
|
253
254
|
})
|
package/dist/ui/forms.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"forms.mjs","names":[],"sources":["../../src/ui/forms.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\ntype FieldLayout = \"stack\" | \"inline\";\n\nconst fieldWrap = css`\n /* field */\n display: grid;\n gap: var(--space-1);\n /* Don't stretch rows when a neighbouring field is taller (hint text). */\n align-content: start;\n margin-bottom: var(--space-4);\n`;\n\nconst fieldWrapInline = css`\n /* field-inline */\n ${fieldWrap}\n grid-template-columns: auto minmax(0, 1fr);\n align-items: center;\n gap: var(--space-2);\n margin-bottom: 0;\n`;\n\nconst fieldLabel = css`\n /* field-label */\n color: var(--text-primary);\n font-weight: 600;\n font-size: var(--text-sm);\n & span[aria-hidden] {\n color: var(--status-rejected);\n }\n`;\n\nconst fieldInput = css`\n /* field-input */\n width: 100%;\n min-height: 40px;\n padding: 0.5rem 0.75rem;\n border-radius: var(--radius);\n border: 1px solid var(--border);\n background: var(--surface-card);\n color: var(--text-primary);\n font: inherit;\n font-size: var(--text-base);\n box-shadow: var(--shadow);\n transition:\n border-color var(--dur-fast) var(--ease),\n box-shadow var(--dur-fast) var(--ease);\n &::placeholder {\n color: var(--text-muted);\n }\n &:hover:not(:disabled) {\n border-color: color-mix(in srgb, var(--text-secondary) 45%, var(--border));\n }\n &:focus {\n outline: none;\n border-color: var(--ring);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--ring) 25%, transparent);\n }\n &:disabled {\n background: var(--surface-muted);\n color: var(--text-secondary);\n cursor: not-allowed;\n }\n`;\n\nconst fieldInputError = css`\n /* field-input-error */\n ${fieldInput}\n border-color: var(--status-rejected);\n &:focus {\n border-color: var(--status-rejected);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--status-rejected) 25%, transparent);\n }\n`;\n\nconst fieldTextarea = css`\n /* field-textarea */\n ${fieldInput}\n resize: vertical;\n min-height: 5.5rem;\n line-height: var(--leading-normal);\n`;\n\nconst fieldTextareaError = css`\n /* field-textarea-error */\n ${fieldInputError}\n resize: vertical;\n min-height: 5.5rem;\n line-height: var(--leading-normal);\n`;\n\nconst fieldHint = css`\n /* field-hint */\n margin: 0;\n color: var(--text-secondary);\n font-size: var(--text-sm);\n`;\n\nconst fieldError = css`\n /* field-error */\n display: flex;\n gap: var(--space-1);\n align-items: flex-start;\n margin: 0;\n color: var(--status-rejected-fg);\n font-size: var(--text-sm);\n font-weight: 600;\n`;\n\nconst formActions = css`\n /* form-actions */\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-2);\n align-items: center;\n margin-top: var(--space-4);\n`;\n\nconst formActionsEnd = css`\n /* form-actions-end */\n ${formActions}\n justify-content: flex-end;\n`;\n\nconst formActionsSplit = css`\n /* form-actions-split */\n ${formActions}\n justify-content: space-between;\n`;\n\n/** Row of form buttons: primary action first; `align=\"end\"` right-aligns, `\"split\"` spreads. */\nexport const FormActions: FC<{ align?: \"start\" | \"end\" | \"split\"; children?: unknown }> = ({\n align = \"start\",\n children,\n}) => {\n const cls = align === \"end\" ? formActionsEnd : align === \"split\" ? formActionsSplit : formActions;\n return <div class={cls}>{children}</div>;\n};\n\nfunction fieldDescribedBy(\n name: string,\n error: string | undefined,\n hint: string | undefined,\n): string | undefined {\n if (error) {\n return `${name}-error`;\n }\n if (hint) {\n return `${name}-hint`;\n }\n return undefined;\n}\n\n// eslint-disable-next-line promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString>\nconst FieldAssistant: FC<{ name: string; error: string | undefined; hint: string | undefined }> = ({\n name,\n error,\n hint,\n}) => {\n if (error) {\n return (\n <p class={fieldError} id={`${name}-error`} role=\"alert\">\n <Icon name=\"x-circle\" size=\"sm\" />\n <span>{error}</span>\n </p>\n );\n }\n if (hint) {\n return (\n <p class={fieldHint} id={`${name}-hint`}>\n {hint}\n </p>\n );\n }\n return null;\n};\n\n/** Labeled text input with error and hint states. */\n// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const Field: FC<{\n label: string;\n name: string;\n type?: string;\n value?: string;\n placeholder?: string;\n required?: boolean;\n disabled?: boolean;\n autofocus?: boolean;\n pattern?: string;\n min?: string;\n max?: string;\n step?: string;\n layout?: FieldLayout;\n error?: string;\n hint?: string;\n autocomplete?: string;\n}> = ({\n label,\n name,\n type = \"text\",\n value,\n placeholder,\n required,\n disabled,\n autofocus,\n pattern,\n min,\n max,\n step,\n layout = \"stack\",\n error,\n hint,\n autocomplete,\n}) => {\n return (\n <div class={layout === \"inline\" ? fieldWrapInline : fieldWrap}>\n <label class={fieldLabel} for={name}>\n {label}\n {required ? <span aria-hidden=\"true\"> *</span> : null}\n </label>\n <input\n class={error ? fieldInputError : fieldInput}\n id={name}\n name={name}\n type={type}\n value={value}\n placeholder={placeholder}\n required={required}\n disabled={disabled}\n autofocus={autofocus}\n pattern={pattern}\n min={min}\n max={max}\n step={step}\n aria-invalid={error ? \"true\" : undefined}\n aria-describedby={fieldDescribedBy(name, error, hint)}\n autocomplete={autocomplete}\n />\n <FieldAssistant name={name} error={error} hint={hint} />\n </div>\n );\n};\n\nconst checkLabel = css`\n /* check-label */\n display: flex;\n gap: var(--space-2);\n align-items: center;\n font-weight: 500;\n font-size: var(--text-base);\n cursor: pointer;\n`;\n\nconst checkInput = css`\n /* check-input */\n width: 1rem;\n height: 1rem;\n accent-color: var(--accent);\n flex: none;\n`;\n\n/** Inline checkbox row with an optional hint. */\n// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const CheckField: FC<{\n label: unknown;\n name: string;\n value?: string;\n checked?: boolean;\n disabled?: boolean;\n hint?: string;\n}> = ({ label, name, value, checked, disabled, hint }) => {\n return (\n <div class={fieldWrap}>\n <label class={checkLabel} for={name}>\n <input\n class={checkInput}\n type=\"checkbox\"\n id={name}\n name={name}\n value={value}\n checked={checked}\n disabled={disabled}\n />\n {label}\n </label>\n {hint ? (\n <p class={fieldHint} id={`${name}-hint`}>\n {hint}\n </p>\n ) : null}\n </div>\n );\n};\n\n/** Labeled textarea with error and hint states. */\n// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const TextareaField: FC<{\n label: string;\n name: string;\n value?: string;\n placeholder?: string;\n rows?: number;\n required?: boolean;\n hint?: string;\n error?: string;\n}> = ({ label, name, value, placeholder, rows = 3, required, hint, error }) => {\n return (\n <div class={fieldWrap}>\n <label class={fieldLabel} for={name}>\n {label}\n </label>\n <textarea\n class={error ? fieldTextareaError : fieldTextarea}\n id={name}\n name={name}\n placeholder={placeholder}\n rows={rows}\n required={required}\n aria-invalid={error ? \"true\" : undefined}\n aria-describedby={fieldDescribedBy(name, error, hint)}\n >\n {value}\n </textarea>\n <FieldAssistant name={name} error={error} hint={hint} />\n </div>\n );\n};\n\n/** Labeled select dropdown with an optional hint. */\n// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const SelectField: FC<{\n label: string;\n name: string;\n value?: string;\n options: { value: string; label: string }[];\n hint?: string;\n disabled?: boolean;\n required?: boolean;\n layout?: FieldLayout;\n}> = ({ label, name, value, options, hint, disabled, required, layout = \"stack\" }) => {\n return (\n <div class={layout === \"inline\" ? fieldWrapInline : fieldWrap}>\n <label class={fieldLabel} for={name}>\n {label}\n </label>\n <select\n class={fieldInput}\n id={name}\n name={name}\n disabled={disabled}\n required={required}\n aria-describedby={hint ? `${name}-hint` : undefined}\n >\n {options.map((opt) => (\n <option value={opt.value} selected={opt.value === value}>\n {opt.label}\n </option>\n ))}\n </select>\n {hint ? (\n <p class={fieldHint} id={`${name}-hint`}>\n {hint}\n </p>\n ) : null}\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"forms.mjs","names":[],"sources":["../../src/ui/forms.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\ntype FieldLayout = \"stack\" | \"inline\";\n\nconst fieldWrap = css`\n /* field */\n display: grid;\n gap: var(--space-1);\n /* Don't stretch rows when a neighbouring field is taller (hint text). */\n align-content: start;\n margin-bottom: var(--space-4);\n`;\n\nconst fieldWrapInline = css`\n /* field-inline */\n ${fieldWrap}\n grid-template-columns: auto minmax(0, 1fr);\n align-items: center;\n gap: var(--space-2);\n margin-bottom: 0;\n`;\n\nconst fieldLabel = css`\n /* field-label */\n color: var(--text-primary);\n font-weight: 600;\n font-size: var(--text-sm);\n & span[aria-hidden] {\n color: var(--status-rejected);\n }\n`;\n\nconst fieldInput = css`\n /* field-input */\n width: 100%;\n min-height: 40px;\n padding: 0.5rem 0.75rem;\n border-radius: var(--radius);\n border: 1px solid var(--border);\n background: var(--surface-card);\n color: var(--text-primary);\n font: inherit;\n font-size: var(--text-base);\n box-shadow: var(--shadow);\n transition:\n border-color var(--dur-fast) var(--ease),\n box-shadow var(--dur-fast) var(--ease);\n &::placeholder {\n color: var(--text-muted);\n }\n &:hover:not(:disabled) {\n border-color: color-mix(in srgb, var(--text-secondary) 45%, var(--border));\n }\n &:focus {\n outline: none;\n border-color: var(--ring);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--ring) 25%, transparent);\n }\n &:disabled {\n background: var(--surface-muted);\n color: var(--text-secondary);\n cursor: not-allowed;\n }\n`;\n\nconst fieldInputError = css`\n /* field-input-error */\n ${fieldInput}\n border-color: var(--status-rejected);\n &:focus {\n border-color: var(--status-rejected);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--status-rejected) 25%, transparent);\n }\n`;\n\nconst fieldTextarea = css`\n /* field-textarea */\n ${fieldInput}\n resize: vertical;\n min-height: 5.5rem;\n line-height: var(--leading-normal);\n`;\n\nconst fieldTextareaError = css`\n /* field-textarea-error */\n ${fieldInputError}\n resize: vertical;\n min-height: 5.5rem;\n line-height: var(--leading-normal);\n`;\n\nconst fieldHint = css`\n /* field-hint */\n margin: 0;\n color: var(--text-secondary);\n font-size: var(--text-sm);\n`;\n\nconst fieldError = css`\n /* field-error */\n display: flex;\n gap: var(--space-1);\n align-items: flex-start;\n margin: 0;\n color: var(--status-rejected-fg);\n font-size: var(--text-sm);\n font-weight: 600;\n`;\n\nconst formActions = css`\n /* form-actions */\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-2);\n align-items: center;\n margin-top: var(--space-4);\n`;\n\nconst formActionsEnd = css`\n /* form-actions-end */\n ${formActions}\n justify-content: flex-end;\n`;\n\nconst formActionsSplit = css`\n /* form-actions-split */\n ${formActions}\n justify-content: space-between;\n`;\n\n/** Row of form buttons: primary action first; `align=\"end\"` right-aligns, `\"split\"` spreads. */\nexport const FormActions: FC<{ align?: \"start\" | \"end\" | \"split\"; children?: unknown }> = ({\n align = \"start\",\n children,\n}) => {\n const cls = align === \"end\" ? formActionsEnd : align === \"split\" ? formActionsSplit : formActions;\n return <div class={cls}>{children}</div>;\n};\n\nfunction fieldDescribedBy(\n name: string,\n error: string | undefined,\n hint: string | undefined,\n): string | undefined {\n if (error) {\n return `${name}-error`;\n }\n if (hint) {\n return `${name}-hint`;\n }\n return undefined;\n}\n\n// eslint-disable-next-line promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString>\nconst FieldAssistant: FC<{ name: string; error: string | undefined; hint: string | undefined }> = ({\n name,\n error,\n hint,\n}) => {\n if (error) {\n return (\n <p class={fieldError} id={`${name}-error`} role=\"alert\">\n <Icon name=\"x-circle\" size=\"sm\" />\n <span>{error}</span>\n </p>\n );\n }\n if (hint) {\n return (\n <p class={fieldHint} id={`${name}-hint`}>\n {hint}\n </p>\n );\n }\n return null;\n};\n\n/** Labeled text input with error and hint states. */\n// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const Field: FC<{\n label: string;\n name: string;\n type?: string;\n value?: string;\n placeholder?: string;\n required?: boolean;\n disabled?: boolean;\n autofocus?: boolean;\n pattern?: string;\n min?: string;\n max?: string;\n step?: string;\n layout?: FieldLayout;\n error?: string;\n hint?: string;\n autocomplete?: string;\n}> = ({\n label,\n name,\n type = \"text\",\n value,\n placeholder,\n required,\n disabled,\n autofocus,\n pattern,\n min,\n max,\n step,\n layout = \"stack\",\n error,\n hint,\n autocomplete,\n}) => {\n return (\n <div class={layout === \"inline\" ? fieldWrapInline : fieldWrap}>\n <label class={fieldLabel} for={name}>\n {label}\n {required ? <span aria-hidden=\"true\"> *</span> : null}\n </label>\n <input\n class={error ? fieldInputError : fieldInput}\n id={name}\n name={name}\n type={type}\n value={value}\n placeholder={placeholder}\n required={required}\n disabled={disabled}\n autofocus={autofocus}\n pattern={pattern}\n min={min}\n max={max}\n step={step}\n aria-invalid={error ? \"true\" : undefined}\n aria-describedby={fieldDescribedBy(name, error, hint)}\n autocomplete={autocomplete}\n />\n <FieldAssistant name={name} error={error} hint={hint} />\n </div>\n );\n};\n\nconst checkLabel = css`\n /* check-label */\n display: flex;\n gap: var(--space-2);\n align-items: center;\n font-weight: 500;\n font-size: var(--text-base);\n cursor: pointer;\n`;\n\nconst checkInput = css`\n /* check-input */\n width: 1rem;\n height: 1rem;\n accent-color: var(--accent);\n flex: none;\n`;\n\n/** Inline checkbox row with an optional hint. */\n// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const CheckField: FC<{\n label: unknown;\n name: string;\n value?: string;\n checked?: boolean;\n disabled?: boolean;\n hint?: string;\n}> = ({ label, name, value, checked, disabled, hint }) => {\n return (\n <div class={fieldWrap}>\n <label class={checkLabel} for={name}>\n <input\n class={checkInput}\n type=\"checkbox\"\n id={name}\n name={name}\n value={value}\n checked={checked}\n disabled={disabled}\n />\n {label}\n </label>\n {hint ? (\n <p class={fieldHint} id={`${name}-hint`}>\n {hint}\n </p>\n ) : null}\n </div>\n );\n};\n\n/** Labeled textarea with error and hint states. */\n// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const TextareaField: FC<{\n label: string;\n name: string;\n /** DOM id when several forms on a page post the same field name (defaults to `name`). */\n id?: string;\n value?: string;\n placeholder?: string;\n rows?: number;\n required?: boolean;\n hint?: string;\n error?: string;\n}> = ({ label, name, id, value, placeholder, rows = 3, required, hint, error }) => {\n const domId = id ?? name;\n return (\n <div class={fieldWrap}>\n <label class={fieldLabel} for={domId}>\n {label}\n </label>\n <textarea\n class={error ? fieldTextareaError : fieldTextarea}\n id={domId}\n name={name}\n placeholder={placeholder}\n rows={rows}\n required={required}\n aria-invalid={error ? \"true\" : undefined}\n aria-describedby={fieldDescribedBy(domId, error, hint)}\n >\n {value}\n </textarea>\n <FieldAssistant name={domId} error={error} hint={hint} />\n </div>\n );\n};\n\n/** Labeled select dropdown with an optional hint. */\n// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const SelectField: FC<{\n label: string;\n name: string;\n value?: string;\n options: { value: string; label: string }[];\n hint?: string;\n disabled?: boolean;\n required?: boolean;\n layout?: FieldLayout;\n}> = ({ label, name, value, options, hint, disabled, required, layout = \"stack\" }) => {\n return (\n <div class={layout === \"inline\" ? fieldWrapInline : fieldWrap}>\n <label class={fieldLabel} for={name}>\n {label}\n </label>\n <select\n class={fieldInput}\n id={name}\n name={name}\n disabled={disabled}\n required={required}\n aria-describedby={hint ? `${name}-hint` : undefined}\n >\n {options.map((opt) => (\n <option value={opt.value} selected={opt.value === value}>\n {opt.label}\n </option>\n ))}\n </select>\n {hint ? (\n <p class={fieldHint} id={`${name}-hint`}>\n {hint}\n </p>\n ) : null}\n </div>\n );\n};\n"],"mappings":"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"}
|
package/dist/ui/icons/paths.mjs
CHANGED
|
@@ -23,6 +23,7 @@ const ICON_PATHS = {
|
|
|
23
23
|
"git-branch": "<path d=\"M6 3v12\"/><circle cx=\"18\" cy=\"6\" r=\"3\"/><circle cx=\"6\" cy=\"18\" r=\"3\"/><path d=\"M18 9a9 9 0 0 1-9 9\"/>",
|
|
24
24
|
"git-commit": "<circle cx=\"12\" cy=\"12\" r=\"3\"/><path d=\"M3 12h6M15 12h6\"/>",
|
|
25
25
|
activity: "<path d=\"M22 12h-4l-3 9L9 3l-3 9H2\"/>",
|
|
26
|
+
sparkles: "<path d=\"M9.9 15.5A2 2 0 0 0 8.5 14.1l-6.1-1.6a.5.5 0 0 1 0-1L8.5 9.9A2 2 0 0 0 9.9 8.5l1.6-6.1a.5.5 0 0 1 1 0l1.6 6.1a2 2 0 0 0 1.4 1.4l6.1 1.6a.5.5 0 0 1 0 1l-6.1 1.6a2 2 0 0 0-1.4 1.4l-1.6 6.1a.5.5 0 0 1-1 0z\"/><path d=\"M20 3v4M22 5h-4M4 17v2M5 18H3\"/>",
|
|
26
27
|
tag: "<path d=\"M12.6 2.6A2 2 0 0 0 11.2 2H4a2 2 0 0 0-2 2v7.2a2 2 0 0 0 .6 1.4l8.7 8.7a2.4 2.4 0 0 0 3.4 0l6.6-6.6a2.4 2.4 0 0 0 0-3.4z\"/><circle cx=\"7.5\" cy=\"7.5\" r=\".5\" fill=\"currentColor\"/>",
|
|
27
28
|
settings: "<path d=\"M12.2 2h-.4a2 2 0 0 0-2 2v.2a2 2 0 0 1-1 1.7l-.4.3a2 2 0 0 1-2 0l-.2-.1a2 2 0 0 0-2.7.7l-.2.4a2 2 0 0 0 .7 2.7l.2.1a2 2 0 0 1 1 1.7v.5a2 2 0 0 1-1 1.8l-.2.1a2 2 0 0 0-.7 2.7l.2.4a2 2 0 0 0 2.7.7l.2-.1a2 2 0 0 1 2 0l.4.3a2 2 0 0 1 1 1.7v.2a2 2 0 0 0 2 2h.4a2 2 0 0 0 2-2v-.2a2 2 0 0 1 1-1.7l.4-.3a2 2 0 0 1 2 0l.2.1a2 2 0 0 0 2.7-.7l.2-.4a2 2 0 0 0-.7-2.7l-.2-.1a2 2 0 0 1-1-1.8v-.5a2 2 0 0 1 1-1.7l.2-.1a2 2 0 0 0 .7-2.7l-.2-.4a2 2 0 0 0-2.7-.7l-.2.1a2 2 0 0 1-2 0l-.4-.3a2 2 0 0 1-1-1.7V4a2 2 0 0 0-2-2z\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/>",
|
|
28
29
|
users: "<path d=\"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2\"/><circle cx=\"9\" cy=\"7\" r=\"4\"/><path d=\"M22 21v-2a4 4 0 0 0-3-3.9M16 3.1a4 4 0 0 1 0 7.8\"/>",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.mjs","names":[],"sources":["../../../src/ui/icons/paths.ts"],"sourcesContent":["/**\n * Icon set: inner SVG markup per icon on a 24x24 grid, stroke-based\n * (Lucide-style, ISC). Add an icon here and it is in the sprite and the\n * `IconName` union automatically.\n */\nexport const ICON_PATHS = {\n check: '<path d=\"M20 6 9 17l-5-5\"/>',\n x: '<path d=\"M18 6 6 18M6 6l12 12\"/>',\n plus: '<path d=\"M5 12h14M12 5v14\"/>',\n \"chevron-down\": '<path d=\"m6 9 6 6 6-6\"/>',\n \"chevron-up\": '<path d=\"m18 15-6-6-6 6\"/>',\n \"chevron-right\": '<path d=\"m9 18 6-6-6-6\"/>',\n \"chevron-left\": '<path d=\"m15 18-6-6 6-6\"/>',\n \"chevrons-left\": '<path d=\"m11 17-5-5 5-5M18 17l-5-5 5-5\"/>',\n \"arrow-right\": '<path d=\"M5 12h14M12 5l7 7-7 7\"/>',\n folder:\n '<path d=\"M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z\"/>',\n layers: '<path d=\"m12 2 10 5-10 5L2 7zM2 17l10 5 10-5M2 12l10 5 10-5\"/>',\n image:\n '<rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><circle cx=\"9\" cy=\"9\" r=\"2\"/><path d=\"m21 15-3.1-3.1a2 2 0 0 0-2.8 0L6 21\"/>',\n \"git-branch\":\n '<path d=\"M6 3v12\"/><circle cx=\"18\" cy=\"6\" r=\"3\"/><circle cx=\"6\" cy=\"18\" r=\"3\"/><path d=\"M18 9a9 9 0 0 1-9 9\"/>',\n \"git-commit\": '<circle cx=\"12\" cy=\"12\" r=\"3\"/><path d=\"M3 12h6M15 12h6\"/>',\n activity: '<path d=\"M22 12h-4l-3 9L9 3l-3 9H2\"/>',\n tag: '<path d=\"M12.6 2.6A2 2 0 0 0 11.2 2H4a2 2 0 0 0-2 2v7.2a2 2 0 0 0 .6 1.4l8.7 8.7a2.4 2.4 0 0 0 3.4 0l6.6-6.6a2.4 2.4 0 0 0 0-3.4z\"/><circle cx=\"7.5\" cy=\"7.5\" r=\".5\" fill=\"currentColor\"/>',\n settings:\n '<path d=\"M12.2 2h-.4a2 2 0 0 0-2 2v.2a2 2 0 0 1-1 1.7l-.4.3a2 2 0 0 1-2 0l-.2-.1a2 2 0 0 0-2.7.7l-.2.4a2 2 0 0 0 .7 2.7l.2.1a2 2 0 0 1 1 1.7v.5a2 2 0 0 1-1 1.8l-.2.1a2 2 0 0 0-.7 2.7l.2.4a2 2 0 0 0 2.7.7l.2-.1a2 2 0 0 1 2 0l.4.3a2 2 0 0 1 1 1.7v.2a2 2 0 0 0 2 2h.4a2 2 0 0 0 2-2v-.2a2 2 0 0 1 1-1.7l.4-.3a2 2 0 0 1 2 0l.2.1a2 2 0 0 0 2.7-.7l.2-.4a2 2 0 0 0-.7-2.7l-.2-.1a2 2 0 0 1-1-1.8v-.5a2 2 0 0 1 1-1.7l.2-.1a2 2 0 0 0 .7-2.7l-.2-.4a2 2 0 0 0-2.7-.7l-.2.1a2 2 0 0 1-2 0l-.4-.3a2 2 0 0 1-1-1.7V4a2 2 0 0 0-2-2z\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/>',\n users:\n '<path d=\"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2\"/><circle cx=\"9\" cy=\"7\" r=\"4\"/><path d=\"M22 21v-2a4 4 0 0 0-3-3.9M16 3.1a4 4 0 0 1 0 7.8\"/>',\n user: '<path d=\"M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2\"/><circle cx=\"12\" cy=\"7\" r=\"4\"/>',\n key: '<path d=\"m21 2-9.6 9.6M15.5 7.5l3 3L22 7l-3-3\"/><circle cx=\"7.5\" cy=\"15.5\" r=\"5.5\"/>',\n bell: '<path d=\"M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9M10.3 21a1.9 1.9 0 0 0 3.4 0\"/>',\n webhook:\n '<path d=\"M18 16.98h-5.99c-1.1 0-1.95.94-2.48 1.9A4 4 0 0 1 2 17c.01-.7.2-1.4.57-2M9 17l3.13-5.78c.53-.97.43-2.22-.26-3.07a4 4 0 1 1 6.58-4.2c.38.6.58 1.3.57 2M6 17a4 4 0 0 1 6-3.5\"/><path d=\"M12 17h6a2 2 0 1 1 0 4\"/>',\n code: '<path d=\"m16 18 6-6-6-6M8 6l-6 6 6 6\"/>',\n terminal: '<path d=\"m4 17 6-6-6-6M12 19h8\"/>',\n shield:\n '<path d=\"M20 13c0 5-3.5 7.5-7.7 8.9a1 1 0 0 1-.6 0C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.2-2.7a1.2 1.2 0 0 1 1.6 0C14.5 3.8 17 5 19 5a1 1 0 0 1 1 1z\"/>',\n search: '<circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.3-4.3\"/>',\n sun: '<circle cx=\"12\" cy=\"12\" r=\"4\"/><path d=\"M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M6.3 17.7l-1.4 1.4M19.1 4.9l-1.4 1.4\"/>',\n moon: '<path d=\"M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z\"/>',\n monitor: '<rect width=\"20\" height=\"14\" x=\"2\" y=\"3\" rx=\"2\"/><path d=\"M8 21h8M12 17v4\"/>',\n menu: '<path d=\"M4 6h16M4 12h16M4 18h16\"/>',\n \"more-horizontal\":\n '<circle cx=\"12\" cy=\"12\" r=\"1\"/><circle cx=\"19\" cy=\"12\" r=\"1\"/><circle cx=\"5\" cy=\"12\" r=\"1\"/>',\n \"log-out\": '<path d=\"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4M16 17l5-5-5-5M21 12H9\"/>',\n \"external-link\":\n '<path d=\"M15 3h6v6M10 14 21 3M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\"/>',\n copy: '<rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/>',\n \"check-circle\": '<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"m9 12 2 2 4-4\"/>',\n \"x-circle\": '<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"m15 9-6 6M9 9l6 6\"/>',\n \"alert-triangle\":\n '<path d=\"m21.7 18-8-14a2 2 0 0 0-3.5 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.7-3M12 9v4M12 17h.01\"/>',\n info: '<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 16v-4M12 8h.01\"/>',\n clock: '<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 6v6l4 2\"/>',\n loader: '<path d=\"M21 12a9 9 0 1 1-6.2-8.6\"/>',\n \"message-square\": '<path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/>',\n columns: '<rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M12 3v18\"/>',\n maximize:\n '<path d=\"M8 3H5a2 2 0 0 0-2 2v3M21 8V5a2 2 0 0 0-2-2h-3M3 16v3a2 2 0 0 0 2 2h3M16 21h3a2 2 0 0 0 2-2v-3\"/>',\n \"zoom-in\": '<circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.3-4.3M11 8v6M8 11h6\"/>',\n book: '<path d=\"M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1 0-5H20\"/>',\n server:\n '<rect width=\"20\" height=\"8\" x=\"2\" y=\"2\" rx=\"2\"/><rect width=\"20\" height=\"8\" x=\"2\" y=\"14\" rx=\"2\"/><path d=\"M6 6h.01M6 18h.01\"/>',\n} as const;\n\n/** Every icon name available in the sprite. */\nexport type IconName = keyof typeof ICON_PATHS;\n"],"mappings":";;;;;;;;;AAKA,MAAa,aAAa;CACxB,OAAO;CACP,GAAG;CACH,MAAM;CACN,gBAAgB;CAChB,cAAc;CACd,iBAAiB;CACjB,gBAAgB;CAChB,iBAAiB;CACjB,eAAe;CACf,QACE;CACF,QAAQ;CACR,OACE;CACF,cACE;CACF,cAAc;CACd,UAAU;CACV,KAAK;CACL,UACE;CACF,OACE;CACF,MAAM;CACN,KAAK;CACL,MAAM;CACN,SACE;CACF,MAAM;CACN,UAAU;CACV,QACE;CACF,QAAQ;CACR,KAAK;CACL,MAAM;CACN,SAAS;CACT,MAAM;CACN,mBACE;CACF,WAAW;CACX,iBACE;CACF,MAAM;CACN,gBAAgB;CAChB,YAAY;CACZ,kBACE;CACF,MAAM;CACN,OAAO;CACP,QAAQ;CACR,kBAAkB;CAClB,SAAS;CACT,UACE;CACF,WAAW;CACX,MAAM;CACN,QACE;AACJ"}
|
|
1
|
+
{"version":3,"file":"paths.mjs","names":[],"sources":["../../../src/ui/icons/paths.ts"],"sourcesContent":["/**\n * Icon set: inner SVG markup per icon on a 24x24 grid, stroke-based\n * (Lucide-style, ISC). Add an icon here and it is in the sprite and the\n * `IconName` union automatically.\n */\nexport const ICON_PATHS = {\n check: '<path d=\"M20 6 9 17l-5-5\"/>',\n x: '<path d=\"M18 6 6 18M6 6l12 12\"/>',\n plus: '<path d=\"M5 12h14M12 5v14\"/>',\n \"chevron-down\": '<path d=\"m6 9 6 6 6-6\"/>',\n \"chevron-up\": '<path d=\"m18 15-6-6-6 6\"/>',\n \"chevron-right\": '<path d=\"m9 18 6-6-6-6\"/>',\n \"chevron-left\": '<path d=\"m15 18-6-6 6-6\"/>',\n \"chevrons-left\": '<path d=\"m11 17-5-5 5-5M18 17l-5-5 5-5\"/>',\n \"arrow-right\": '<path d=\"M5 12h14M12 5l7 7-7 7\"/>',\n folder:\n '<path d=\"M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z\"/>',\n layers: '<path d=\"m12 2 10 5-10 5L2 7zM2 17l10 5 10-5M2 12l10 5 10-5\"/>',\n image:\n '<rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><circle cx=\"9\" cy=\"9\" r=\"2\"/><path d=\"m21 15-3.1-3.1a2 2 0 0 0-2.8 0L6 21\"/>',\n \"git-branch\":\n '<path d=\"M6 3v12\"/><circle cx=\"18\" cy=\"6\" r=\"3\"/><circle cx=\"6\" cy=\"18\" r=\"3\"/><path d=\"M18 9a9 9 0 0 1-9 9\"/>',\n \"git-commit\": '<circle cx=\"12\" cy=\"12\" r=\"3\"/><path d=\"M3 12h6M15 12h6\"/>',\n activity: '<path d=\"M22 12h-4l-3 9L9 3l-3 9H2\"/>',\n sparkles:\n '<path d=\"M9.9 15.5A2 2 0 0 0 8.5 14.1l-6.1-1.6a.5.5 0 0 1 0-1L8.5 9.9A2 2 0 0 0 9.9 8.5l1.6-6.1a.5.5 0 0 1 1 0l1.6 6.1a2 2 0 0 0 1.4 1.4l6.1 1.6a.5.5 0 0 1 0 1l-6.1 1.6a2 2 0 0 0-1.4 1.4l-1.6 6.1a.5.5 0 0 1-1 0z\"/><path d=\"M20 3v4M22 5h-4M4 17v2M5 18H3\"/>',\n tag: '<path d=\"M12.6 2.6A2 2 0 0 0 11.2 2H4a2 2 0 0 0-2 2v7.2a2 2 0 0 0 .6 1.4l8.7 8.7a2.4 2.4 0 0 0 3.4 0l6.6-6.6a2.4 2.4 0 0 0 0-3.4z\"/><circle cx=\"7.5\" cy=\"7.5\" r=\".5\" fill=\"currentColor\"/>',\n settings:\n '<path d=\"M12.2 2h-.4a2 2 0 0 0-2 2v.2a2 2 0 0 1-1 1.7l-.4.3a2 2 0 0 1-2 0l-.2-.1a2 2 0 0 0-2.7.7l-.2.4a2 2 0 0 0 .7 2.7l.2.1a2 2 0 0 1 1 1.7v.5a2 2 0 0 1-1 1.8l-.2.1a2 2 0 0 0-.7 2.7l.2.4a2 2 0 0 0 2.7.7l.2-.1a2 2 0 0 1 2 0l.4.3a2 2 0 0 1 1 1.7v.2a2 2 0 0 0 2 2h.4a2 2 0 0 0 2-2v-.2a2 2 0 0 1 1-1.7l.4-.3a2 2 0 0 1 2 0l.2.1a2 2 0 0 0 2.7-.7l.2-.4a2 2 0 0 0-.7-2.7l-.2-.1a2 2 0 0 1-1-1.8v-.5a2 2 0 0 1 1-1.7l.2-.1a2 2 0 0 0 .7-2.7l-.2-.4a2 2 0 0 0-2.7-.7l-.2.1a2 2 0 0 1-2 0l-.4-.3a2 2 0 0 1-1-1.7V4a2 2 0 0 0-2-2z\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/>',\n users:\n '<path d=\"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2\"/><circle cx=\"9\" cy=\"7\" r=\"4\"/><path d=\"M22 21v-2a4 4 0 0 0-3-3.9M16 3.1a4 4 0 0 1 0 7.8\"/>',\n user: '<path d=\"M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2\"/><circle cx=\"12\" cy=\"7\" r=\"4\"/>',\n key: '<path d=\"m21 2-9.6 9.6M15.5 7.5l3 3L22 7l-3-3\"/><circle cx=\"7.5\" cy=\"15.5\" r=\"5.5\"/>',\n bell: '<path d=\"M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9M10.3 21a1.9 1.9 0 0 0 3.4 0\"/>',\n webhook:\n '<path d=\"M18 16.98h-5.99c-1.1 0-1.95.94-2.48 1.9A4 4 0 0 1 2 17c.01-.7.2-1.4.57-2M9 17l3.13-5.78c.53-.97.43-2.22-.26-3.07a4 4 0 1 1 6.58-4.2c.38.6.58 1.3.57 2M6 17a4 4 0 0 1 6-3.5\"/><path d=\"M12 17h6a2 2 0 1 1 0 4\"/>',\n code: '<path d=\"m16 18 6-6-6-6M8 6l-6 6 6 6\"/>',\n terminal: '<path d=\"m4 17 6-6-6-6M12 19h8\"/>',\n shield:\n '<path d=\"M20 13c0 5-3.5 7.5-7.7 8.9a1 1 0 0 1-.6 0C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.2-2.7a1.2 1.2 0 0 1 1.6 0C14.5 3.8 17 5 19 5a1 1 0 0 1 1 1z\"/>',\n search: '<circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.3-4.3\"/>',\n sun: '<circle cx=\"12\" cy=\"12\" r=\"4\"/><path d=\"M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M6.3 17.7l-1.4 1.4M19.1 4.9l-1.4 1.4\"/>',\n moon: '<path d=\"M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z\"/>',\n monitor: '<rect width=\"20\" height=\"14\" x=\"2\" y=\"3\" rx=\"2\"/><path d=\"M8 21h8M12 17v4\"/>',\n menu: '<path d=\"M4 6h16M4 12h16M4 18h16\"/>',\n \"more-horizontal\":\n '<circle cx=\"12\" cy=\"12\" r=\"1\"/><circle cx=\"19\" cy=\"12\" r=\"1\"/><circle cx=\"5\" cy=\"12\" r=\"1\"/>',\n \"log-out\": '<path d=\"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4M16 17l5-5-5-5M21 12H9\"/>',\n \"external-link\":\n '<path d=\"M15 3h6v6M10 14 21 3M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\"/>',\n copy: '<rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/>',\n \"check-circle\": '<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"m9 12 2 2 4-4\"/>',\n \"x-circle\": '<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"m15 9-6 6M9 9l6 6\"/>',\n \"alert-triangle\":\n '<path d=\"m21.7 18-8-14a2 2 0 0 0-3.5 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.7-3M12 9v4M12 17h.01\"/>',\n info: '<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 16v-4M12 8h.01\"/>',\n clock: '<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 6v6l4 2\"/>',\n loader: '<path d=\"M21 12a9 9 0 1 1-6.2-8.6\"/>',\n \"message-square\": '<path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/>',\n columns: '<rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M12 3v18\"/>',\n maximize:\n '<path d=\"M8 3H5a2 2 0 0 0-2 2v3M21 8V5a2 2 0 0 0-2-2h-3M3 16v3a2 2 0 0 0 2 2h3M16 21h3a2 2 0 0 0 2-2v-3\"/>',\n \"zoom-in\": '<circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.3-4.3M11 8v6M8 11h6\"/>',\n book: '<path d=\"M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1 0-5H20\"/>',\n server:\n '<rect width=\"20\" height=\"8\" x=\"2\" y=\"2\" rx=\"2\"/><rect width=\"20\" height=\"8\" x=\"2\" y=\"14\" rx=\"2\"/><path d=\"M6 6h.01M6 18h.01\"/>',\n} as const;\n\n/** Every icon name available in the sprite. */\nexport type IconName = keyof typeof ICON_PATHS;\n"],"mappings":";;;;;;;;;AAKA,MAAa,aAAa;CACxB,OAAO;CACP,GAAG;CACH,MAAM;CACN,gBAAgB;CAChB,cAAc;CACd,iBAAiB;CACjB,gBAAgB;CAChB,iBAAiB;CACjB,eAAe;CACf,QACE;CACF,QAAQ;CACR,OACE;CACF,cACE;CACF,cAAc;CACd,UAAU;CACV,UACE;CACF,KAAK;CACL,UACE;CACF,OACE;CACF,MAAM;CACN,KAAK;CACL,MAAM;CACN,SACE;CACF,MAAM;CACN,UAAU;CACV,QACE;CACF,QAAQ;CACR,KAAK;CACL,MAAM;CACN,SAAS;CACT,MAAM;CACN,mBACE;CACF,WAAW;CACX,iBACE;CACF,MAAM;CACN,gBAAgB;CAChB,YAAY;CACZ,kBACE;CACF,MAAM;CACN,OAAO;CACP,QAAQ;CACR,kBAAkB;CAClB,SAAS;CACT,UACE;CACF,WAAW;CACX,MAAM;CACN,QACE;AACJ"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { css } from "./css.mjs";
|
|
5
|
+
import { Button } from "./buttons.mjs";
|
|
6
|
+
import { Meta } from "./feedback.mjs";
|
|
7
|
+
import { jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
8
|
+
//#region src/ui/preview-embed.tsx
|
|
9
|
+
/**
|
|
10
|
+
* Sandbox of the embedded story frame (ADR 0006, decision 9). Scripts only: no `allow-same-origin`,
|
|
11
|
+
* so a customer's Storybook never runs with the app's origin and its cookies, and no top navigation,
|
|
12
|
+
* forms or popups.
|
|
13
|
+
*/
|
|
14
|
+
const PREVIEW_SANDBOX = "allow-scripts";
|
|
15
|
+
const previewHost = css`
|
|
16
|
+
/* preview-embed */
|
|
17
|
+
display: grid;
|
|
18
|
+
min-height: 12rem;
|
|
19
|
+
border: 1px solid var(--border-subtle);
|
|
20
|
+
border-radius: var(--radius);
|
|
21
|
+
background: var(--surface-muted);
|
|
22
|
+
overflow: hidden;
|
|
23
|
+
`;
|
|
24
|
+
const previewIdle = css`
|
|
25
|
+
/* preview-embed-idle */
|
|
26
|
+
display: grid;
|
|
27
|
+
gap: 0.5rem;
|
|
28
|
+
place-items: center;
|
|
29
|
+
align-content: center;
|
|
30
|
+
padding: 2rem 1rem;
|
|
31
|
+
text-align: center;
|
|
32
|
+
`;
|
|
33
|
+
const previewFrame = css`
|
|
34
|
+
/* preview-embed-frame */
|
|
35
|
+
display: block;
|
|
36
|
+
width: 100%;
|
|
37
|
+
height: 28rem;
|
|
38
|
+
border: 0;
|
|
39
|
+
background: var(--surface-card);
|
|
40
|
+
`;
|
|
41
|
+
/**
|
|
42
|
+
* A published story in a sandboxed frame that loads on click. The frame is inert markup (a
|
|
43
|
+
* `<template>`) until the visitor asks for it, so opening a page never runs the customer's
|
|
44
|
+
* Storybook; the shell script moves the frame in on click and, for a signed URL, re-points it
|
|
45
|
+
* to a fresh token before the current one expires.
|
|
46
|
+
*/
|
|
47
|
+
const PreviewEmbed = ({ src, title, refresh }) => /* @__PURE__ */ jsxs("div", {
|
|
48
|
+
class: previewHost,
|
|
49
|
+
"data-preview-embed": true,
|
|
50
|
+
...refresh ? {
|
|
51
|
+
"data-preview-refresh-url": refresh.url,
|
|
52
|
+
"data-preview-path": refresh.path,
|
|
53
|
+
"data-preview-refresh-seconds": String(refresh.seconds)
|
|
54
|
+
} : {},
|
|
55
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
56
|
+
class: previewIdle,
|
|
57
|
+
"data-preview-idle": true,
|
|
58
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
59
|
+
variant: "secondary",
|
|
60
|
+
size: "sm",
|
|
61
|
+
icon: "image",
|
|
62
|
+
"data-preview-load": true,
|
|
63
|
+
children: "Load preview"
|
|
64
|
+
}), /* @__PURE__ */ jsx(Meta, {
|
|
65
|
+
as: "span",
|
|
66
|
+
children: "Runs the published story in a sandboxed frame, only when you ask."
|
|
67
|
+
})]
|
|
68
|
+
}), /* @__PURE__ */ jsx("template", {
|
|
69
|
+
"data-preview-template": true,
|
|
70
|
+
children: /* @__PURE__ */ jsx("iframe", {
|
|
71
|
+
class: previewFrame,
|
|
72
|
+
src,
|
|
73
|
+
title,
|
|
74
|
+
sandbox: PREVIEW_SANDBOX,
|
|
75
|
+
referrerpolicy: "no-referrer"
|
|
76
|
+
})
|
|
77
|
+
})]
|
|
78
|
+
});
|
|
79
|
+
//#endregion
|
|
80
|
+
export { PREVIEW_SANDBOX, PreviewEmbed };
|
|
81
|
+
|
|
82
|
+
//# sourceMappingURL=preview-embed.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview-embed.mjs","names":[],"sources":["../../src/ui/preview-embed.tsx"],"sourcesContent":["import type { FC } from \"hono/jsx\";\nimport { Button } from \"./buttons.tsx\";\nimport { css } from \"./css.ts\";\nimport { Meta } from \"./feedback.tsx\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\n/**\n * Sandbox of the embedded story frame (ADR 0006, decision 9). Scripts only: no `allow-same-origin`,\n * so a customer's Storybook never runs with the app's origin and its cookies, and no top navigation,\n * forms or popups.\n */\nexport const PREVIEW_SANDBOX = \"allow-scripts\";\n\nconst previewHost = css`\n /* preview-embed */\n display: grid;\n min-height: 12rem;\n border: 1px solid var(--border-subtle);\n border-radius: var(--radius);\n background: var(--surface-muted);\n overflow: hidden;\n`;\n\nconst previewIdle = css`\n /* preview-embed-idle */\n display: grid;\n gap: 0.5rem;\n place-items: center;\n align-content: center;\n padding: 2rem 1rem;\n text-align: center;\n`;\n\nconst previewFrame = css`\n /* preview-embed-frame */\n display: block;\n width: 100%;\n height: 28rem;\n border: 0;\n background: var(--surface-card);\n`;\n\n/**\n * A published story in a sandboxed frame that loads on click. The frame is inert markup (a\n * `<template>`) until the visitor asks for it, so opening a page never runs the customer's\n * Storybook; the shell script moves the frame in on click and, for a signed URL, re-points it\n * to a fresh token before the current one expires.\n */\nexport const PreviewEmbed: FC<{\n src: string;\n title: string;\n /** For a frame under a signed URL: where to ask for a fresh root, the path after it, how often. */\n refresh?: { url: string; path: string; seconds: number };\n}> = ({ src, title, refresh }) => (\n <div\n class={previewHost}\n data-preview-embed\n {...(refresh\n ? {\n \"data-preview-refresh-url\": refresh.url,\n \"data-preview-path\": refresh.path,\n \"data-preview-refresh-seconds\": String(refresh.seconds),\n }\n : {})}\n >\n <div class={previewIdle} data-preview-idle>\n <Button variant=\"secondary\" size=\"sm\" icon=\"image\" data-preview-load>\n Load preview\n </Button>\n <Meta as=\"span\">Runs the published story in a sandboxed frame, only when you ask.</Meta>\n </div>\n <template data-preview-template>\n <iframe\n class={previewFrame}\n src={src}\n title={title}\n sandbox={PREVIEW_SANDBOX}\n referrerpolicy=\"no-referrer\"\n />\n </template>\n </div>\n);\n"],"mappings":";;;;;;;;;;;;;AAYA,MAAa,kBAAkB;AAE/B,MAAM,cAAc,GAAG;;;;;;;;;AAUvB,MAAM,cAAc,GAAG;;;;;;;;;AAUvB,MAAM,eAAe,GAAG;;;;;;;;;;;;;;AAexB,MAAa,gBAKP,EAAE,KAAK,OAAO,cAClB,qBAAC,OAAD;CACE,OAAO;CACP,sBAAA;CACA,GAAK,UACD;EACE,4BAA4B,QAAQ;EACpC,qBAAqB,QAAQ;EAC7B,gCAAgC,OAAO,QAAQ,OAAO;CACxD,IACA,CAAC;CATP,UAAA,CAWE,qBAAC,OAAD;EAAK,OAAO;EAAa,qBAAA;EAAzB,UAAA,CACE,oBAAC,QAAD;GAAQ,SAAQ;GAAY,MAAK;GAAK,MAAK;GAAQ,qBAAA;GAAkB,UAAA;EAE7D,CAAA,GACR,oBAAC,MAAD;GAAM,IAAG;GAAO,UAAA;EAAuE,CAAA,CACpF;CACL,CAAA,GAAA,oBAAC,YAAD;EAAU,yBAAA;EACR,UAAA,oBAAC,UAAD;GACE,OAAO;GACF;GACE;GACP,SAAS;GACT,gBAAe;EAChB,CAAA;CACO,CAAA,CACP"}
|
|
@@ -12,6 +12,27 @@ function csrfPart() {
|
|
|
12
12
|
if(meta){ e.detail.headers['x-csrf-token']=meta.getAttribute('content'); }
|
|
13
13
|
});`;
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* System page: report the origin the admin opened it with, so a deployment learns its public URL
|
|
17
|
+
* without a redeploy (ADR 0007, decision 6). The server keeps the first report and answers 201 only
|
|
18
|
+
* when it stored a new one, which is when the page reloads to show it.
|
|
19
|
+
*/
|
|
20
|
+
function originPart() {
|
|
21
|
+
return `
|
|
22
|
+
function reportOrigin(){
|
|
23
|
+
var el=document.querySelector('[data-report-origin]');
|
|
24
|
+
if(!el || el.getAttribute('data-reported')) return;
|
|
25
|
+
el.setAttribute('data-reported','1');
|
|
26
|
+
var meta=document.querySelector('meta[name="csrf-token"]');
|
|
27
|
+
fetch(el.getAttribute('data-report-origin'),{
|
|
28
|
+
method:'POST',
|
|
29
|
+
credentials:'same-origin',
|
|
30
|
+
headers:{'content-type':'application/json','x-csrf-token':meta ? meta.getAttribute('content') : ''},
|
|
31
|
+
body:JSON.stringify({origin:location.origin})
|
|
32
|
+
}).then(function(r){ if(r.status===201) location.reload(); }).catch(function(){});
|
|
33
|
+
}
|
|
34
|
+
inits.push(reportOrigin);`;
|
|
35
|
+
}
|
|
15
36
|
/** Theme menu: apply, persist, and mark the current choice. */
|
|
16
37
|
function themePart() {
|
|
17
38
|
return `
|
|
@@ -206,6 +227,67 @@ function filterPart() {
|
|
|
206
227
|
scope.querySelectorAll('[data-filter-empty]').forEach(function(el){ el.hidden=shown>0; });
|
|
207
228
|
});`;
|
|
208
229
|
}
|
|
230
|
+
/**
|
|
231
|
+
* Click-to-load story previews: move the inert frame out of its template (nothing runs before). A
|
|
232
|
+
* frame under a signed URL first asks for a fresh root, so a page left open for hours does not mount
|
|
233
|
+
* an expired token; it keeps the rendered one when that request fails. Afterwards the root is renewed
|
|
234
|
+
* every `data-preview-refresh-seconds`; a failed renewal is retried a few times a minute apart.
|
|
235
|
+
*/
|
|
236
|
+
function previewPart() {
|
|
237
|
+
return `
|
|
238
|
+
document.addEventListener('click',function(e){
|
|
239
|
+
var btn=e.target instanceof Element ? e.target.closest('[data-preview-load]') : null;
|
|
240
|
+
var host=btn ? btn.closest('[data-preview-embed]') : null;
|
|
241
|
+
var tpl=host ? host.querySelector('template[data-preview-template]') : null;
|
|
242
|
+
if(!host||!tpl||host.getAttribute('data-preview-loading')) return;
|
|
243
|
+
host.setAttribute('data-preview-loading','1');
|
|
244
|
+
var idle=host.querySelector('[data-preview-idle]');
|
|
245
|
+
var path=host.getAttribute('data-preview-path');
|
|
246
|
+
var fresh=fetchPreviewBase(host).catch(function(){ return null; });
|
|
247
|
+
fresh.then(function(base){
|
|
248
|
+
var content=document.importNode(tpl.content,true);
|
|
249
|
+
var frame=content.querySelector('iframe');
|
|
250
|
+
if(frame&&base&&path) frame.src=base+path;
|
|
251
|
+
if(idle) idle.hidden=true;
|
|
252
|
+
host.appendChild(content);
|
|
253
|
+
tpl.remove();
|
|
254
|
+
keepPreviewFresh(host);
|
|
255
|
+
});
|
|
256
|
+
});`;
|
|
257
|
+
}
|
|
258
|
+
/** Fetch a fresh signed root for a preview host and keep it renewed (see {@link previewPart}). */
|
|
259
|
+
function previewRefreshPart() {
|
|
260
|
+
return `
|
|
261
|
+
function fetchPreviewBase(host){
|
|
262
|
+
var url=host.getAttribute('data-preview-refresh-url');
|
|
263
|
+
if(!url) return Promise.resolve(null);
|
|
264
|
+
return fetch(url,{headers:{accept:'application/json'}}).then(function(r){
|
|
265
|
+
if(!r.ok) throw new Error('preview-token');
|
|
266
|
+
return r.json();
|
|
267
|
+
}).then(function(body){ return body&&body.base ? body.base : null; });
|
|
268
|
+
}
|
|
269
|
+
function keepPreviewFresh(host){
|
|
270
|
+
var url=host.getAttribute('data-preview-refresh-url');
|
|
271
|
+
var path=host.getAttribute('data-preview-path');
|
|
272
|
+
var seconds=Number(host.getAttribute('data-preview-refresh-seconds'));
|
|
273
|
+
if(!url||!path||!(seconds>0)) return;
|
|
274
|
+
var RETRY_MS=60000, MAX_RETRIES=3;
|
|
275
|
+
function renew(tries){
|
|
276
|
+
var frame=host.querySelector('iframe');
|
|
277
|
+
if(!host.isConnected||!frame) return;
|
|
278
|
+
fetchPreviewBase(host).then(function(base){
|
|
279
|
+
if(base) frame.src=base+path;
|
|
280
|
+
else throw new Error('preview-token');
|
|
281
|
+
}).catch(function(){
|
|
282
|
+
if(tries<MAX_RETRIES) setTimeout(function(){ renew(tries+1); },RETRY_MS);
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
var timer=setInterval(function(){
|
|
286
|
+
if(!host.isConnected||!host.querySelector('iframe')){ clearInterval(timer); return; }
|
|
287
|
+
renew(0);
|
|
288
|
+
},seconds*1000);
|
|
289
|
+
}`;
|
|
290
|
+
}
|
|
209
291
|
/** Thin top progress bar while an HTMX request (e.g. snapshot navigation) is in flight. */
|
|
210
292
|
function loadingPart() {
|
|
211
293
|
return `
|
|
@@ -237,6 +319,7 @@ function clientScript() {
|
|
|
237
319
|
if(!shell) return;
|
|
238
320
|
var inits=[];${[
|
|
239
321
|
csrfPart(),
|
|
322
|
+
originPart(),
|
|
240
323
|
themePart(),
|
|
241
324
|
sidebarPart(),
|
|
242
325
|
dropdownPart(),
|
|
@@ -244,6 +327,8 @@ function clientScript() {
|
|
|
244
327
|
formPart(),
|
|
245
328
|
copyPart(),
|
|
246
329
|
filterPart(),
|
|
330
|
+
previewPart(),
|
|
331
|
+
previewRefreshPart(),
|
|
247
332
|
loadingPart(),
|
|
248
333
|
focusPart(),
|
|
249
334
|
reviewScript()
|