@storyshelf/app 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +18 -0
- package/dist/app-types.d.mts +54 -0
- package/dist/asset-manifest.mjs +41 -0
- package/dist/asset-manifest.mjs.map +1 -0
- package/dist/asset-paths.mjs +23 -0
- package/dist/asset-paths.mjs.map +1 -0
- package/dist/assets/htmx.min.js +3304 -0
- package/dist/assets/mark.svg +1 -0
- package/dist/capture-setup.mjs +139 -0
- package/dist/capture-setup.mjs.map +1 -0
- package/dist/credential-keys.mjs +20 -0
- package/dist/credential-keys.mjs.map +1 -0
- package/dist/credentials.mjs +56 -0
- package/dist/credentials.mjs.map +1 -0
- package/dist/index.d.mts +18 -0
- package/dist/index.mjs +310 -0
- package/dist/index.mjs.map +1 -0
- package/dist/lifecycle.mjs +132 -0
- package/dist/lifecycle.mjs.map +1 -0
- package/dist/middleware/auth-gate.mjs +36 -0
- package/dist/middleware/auth-gate.mjs.map +1 -0
- package/dist/middleware/csrf.mjs +75 -0
- package/dist/middleware/csrf.mjs.map +1 -0
- package/dist/middleware/rate-limit.mjs +68 -0
- package/dist/middleware/rate-limit.mjs.map +1 -0
- package/dist/middleware/request-log.mjs +30 -0
- package/dist/middleware/request-log.mjs.map +1 -0
- package/dist/middleware/server-timing.mjs +39 -0
- package/dist/middleware/server-timing.mjs.map +1 -0
- package/dist/middleware/setup-gate.mjs +37 -0
- package/dist/middleware/setup-gate.mjs.map +1 -0
- package/dist/middleware/store-scope.mjs +33 -0
- package/dist/middleware/store-scope.mjs.map +1 -0
- package/dist/notify.d.mts +17 -0
- package/dist/notify.mjs +141 -0
- package/dist/notify.mjs.map +1 -0
- package/dist/pages/admin-system.mjs +243 -0
- package/dist/pages/admin-system.mjs.map +1 -0
- package/dist/pages/build-detail.mjs +392 -0
- package/dist/pages/build-detail.mjs.map +1 -0
- package/dist/pages/build-diff-actions.mjs +162 -0
- package/dist/pages/build-diff-actions.mjs.map +1 -0
- package/dist/pages/build-diff-comments.mjs +104 -0
- package/dist/pages/build-diff-comments.mjs.map +1 -0
- package/dist/pages/build-diff-header.mjs +117 -0
- package/dist/pages/build-diff-header.mjs.map +1 -0
- package/dist/pages/build-diff-model.mjs +48 -0
- package/dist/pages/build-diff-model.mjs.map +1 -0
- package/dist/pages/build-diff-nav.mjs +122 -0
- package/dist/pages/build-diff-nav.mjs.map +1 -0
- package/dist/pages/build-diff-shortcuts.mjs +78 -0
- package/dist/pages/build-diff-shortcuts.mjs.map +1 -0
- package/dist/pages/build-diff-stale.mjs +70 -0
- package/dist/pages/build-diff-stale.mjs.map +1 -0
- package/dist/pages/build-diff-viewer.mjs +70 -0
- package/dist/pages/build-diff-viewer.mjs.map +1 -0
- package/dist/pages/build-diff.mjs +110 -0
- package/dist/pages/build-diff.mjs.map +1 -0
- package/dist/pages/compute-jobs.mjs +165 -0
- package/dist/pages/compute-jobs.mjs.map +1 -0
- package/dist/pages/invite.mjs +91 -0
- package/dist/pages/invite.mjs.map +1 -0
- package/dist/pages/label-detail.mjs +154 -0
- package/dist/pages/label-detail.mjs.map +1 -0
- package/dist/pages/library.mjs +343 -0
- package/dist/pages/library.mjs.map +1 -0
- package/dist/pages/login.mjs +117 -0
- package/dist/pages/login.mjs.map +1 -0
- package/dist/pages/passkey-ceremony.mjs +97 -0
- package/dist/pages/passkey-ceremony.mjs.map +1 -0
- package/dist/pages/profile.mjs +364 -0
- package/dist/pages/profile.mjs.map +1 -0
- package/dist/pages/project-builds-table.mjs +151 -0
- package/dist/pages/project-builds-table.mjs.map +1 -0
- package/dist/pages/project-builds.mjs +143 -0
- package/dist/pages/project-builds.mjs.map +1 -0
- package/dist/pages/project-create.mjs +90 -0
- package/dist/pages/project-create.mjs.map +1 -0
- package/dist/pages/project-settings.mjs +95 -0
- package/dist/pages/project-settings.mjs.map +1 -0
- package/dist/pages/projects-card.mjs +194 -0
- package/dist/pages/projects-card.mjs.map +1 -0
- package/dist/pages/projects.mjs +129 -0
- package/dist/pages/projects.mjs.map +1 -0
- package/dist/pages/root.mjs +98 -0
- package/dist/pages/root.mjs.map +1 -0
- package/dist/pages/settings-general.mjs +138 -0
- package/dist/pages/settings-general.mjs.map +1 -0
- package/dist/pages/settings-labels.mjs +107 -0
- package/dist/pages/settings-labels.mjs.map +1 -0
- package/dist/pages/settings-members.mjs +192 -0
- package/dist/pages/settings-members.mjs.map +1 -0
- package/dist/pages/settings-notifications.mjs +240 -0
- package/dist/pages/settings-notifications.mjs.map +1 -0
- package/dist/pages/settings-status.mjs +137 -0
- package/dist/pages/settings-status.mjs.map +1 -0
- package/dist/pages/settings-tests.mjs +111 -0
- package/dist/pages/settings-tests.mjs.map +1 -0
- package/dist/pages/settings-tokens.mjs +90 -0
- package/dist/pages/settings-tokens.mjs.map +1 -0
- package/dist/pages/settings-webhooks.mjs +104 -0
- package/dist/pages/settings-webhooks.mjs.map +1 -0
- package/dist/pages/storybook.mjs +116 -0
- package/dist/pages/storybook.mjs.map +1 -0
- package/dist/retention-timer.mjs +65 -0
- package/dist/retention-timer.mjs.map +1 -0
- package/dist/routers/admin-pages.mjs +48 -0
- package/dist/routers/admin-pages.mjs.map +1 -0
- package/dist/routers/admin.mjs +113 -0
- package/dist/routers/admin.mjs.map +1 -0
- package/dist/routers/assets.mjs +64 -0
- package/dist/routers/assets.mjs.map +1 -0
- package/dist/routers/attempts.mjs +96 -0
- package/dist/routers/attempts.mjs.map +1 -0
- package/dist/routers/auth-sync.mjs +64 -0
- package/dist/routers/auth-sync.mjs.map +1 -0
- package/dist/routers/auth.mjs +236 -0
- package/dist/routers/auth.mjs.map +1 -0
- package/dist/routers/baseline-guard.mjs +83 -0
- package/dist/routers/baseline-guard.mjs.map +1 -0
- package/dist/routers/builds.handlers.mjs +217 -0
- package/dist/routers/builds.handlers.mjs.map +1 -0
- package/dist/routers/builds.mjs +381 -0
- package/dist/routers/builds.mjs.map +1 -0
- package/dist/routers/comments.mjs +109 -0
- package/dist/routers/comments.mjs.map +1 -0
- package/dist/routers/flash.mjs +35 -0
- package/dist/routers/flash.mjs.map +1 -0
- package/dist/routers/health-report.mjs +159 -0
- package/dist/routers/health-report.mjs.map +1 -0
- package/dist/routers/health.mjs +46 -0
- package/dist/routers/health.mjs.map +1 -0
- package/dist/routers/helpers.mjs +110 -0
- package/dist/routers/helpers.mjs.map +1 -0
- package/dist/routers/htmx.mjs +24 -0
- package/dist/routers/htmx.mjs.map +1 -0
- package/dist/routers/labels.mjs +118 -0
- package/dist/routers/labels.mjs.map +1 -0
- package/dist/routers/media.mjs +53 -0
- package/dist/routers/media.mjs.map +1 -0
- package/dist/routers/members.mjs +177 -0
- package/dist/routers/members.mjs.map +1 -0
- package/dist/routers/notification-channels.mjs +190 -0
- package/dist/routers/notification-channels.mjs.map +1 -0
- package/dist/routers/notification-subscriptions.mjs +120 -0
- package/dist/routers/notification-subscriptions.mjs.map +1 -0
- package/dist/routers/profile.mjs +276 -0
- package/dist/routers/profile.mjs.map +1 -0
- package/dist/routers/projects.mjs +133 -0
- package/dist/routers/projects.mjs.map +1 -0
- package/dist/routers/review-next.mjs +47 -0
- package/dist/routers/review-next.mjs.map +1 -0
- package/dist/routers/schemas.mjs +337 -0
- package/dist/routers/schemas.mjs.map +1 -0
- package/dist/routers/settings-general.mjs +104 -0
- package/dist/routers/settings-general.mjs.map +1 -0
- package/dist/routers/settings-labels.mjs +52 -0
- package/dist/routers/settings-labels.mjs.map +1 -0
- package/dist/routers/settings-members.mjs +58 -0
- package/dist/routers/settings-members.mjs.map +1 -0
- package/dist/routers/settings-notifications.mjs +108 -0
- package/dist/routers/settings-notifications.mjs.map +1 -0
- package/dist/routers/settings-status.mjs +77 -0
- package/dist/routers/settings-status.mjs.map +1 -0
- package/dist/routers/settings-tokens.mjs +40 -0
- package/dist/routers/settings-tokens.mjs.map +1 -0
- package/dist/routers/settings-webhooks.mjs +67 -0
- package/dist/routers/settings-webhooks.mjs.map +1 -0
- package/dist/routers/settings.handlers.mjs +149 -0
- package/dist/routers/settings.handlers.mjs.map +1 -0
- package/dist/routers/settings.mjs +25 -0
- package/dist/routers/settings.mjs.map +1 -0
- package/dist/routers/snapshots.mjs +240 -0
- package/dist/routers/snapshots.mjs.map +1 -0
- package/dist/routers/status-configs.mjs +108 -0
- package/dist/routers/status-configs.mjs.map +1 -0
- package/dist/routers/storybook.mjs +171 -0
- package/dist/routers/storybook.mjs.map +1 -0
- package/dist/routers/tokens.mjs +91 -0
- package/dist/routers/tokens.mjs.map +1 -0
- package/dist/routers/ui.mjs +166 -0
- package/dist/routers/ui.mjs.map +1 -0
- package/dist/routers/webhooks.mjs +97 -0
- package/dist/routers/webhooks.mjs.map +1 -0
- package/dist/routers/well-known.mjs +43 -0
- package/dist/routers/well-known.mjs.map +1 -0
- package/dist/runtime.mjs +52 -0
- package/dist/runtime.mjs.map +1 -0
- package/dist/store.mjs +20 -0
- package/dist/store.mjs.map +1 -0
- package/dist/ui/avatar.mjs +76 -0
- package/dist/ui/avatar.mjs.map +1 -0
- package/dist/ui/buttons.mjs +205 -0
- package/dist/ui/buttons.mjs.map +1 -0
- package/dist/ui/code-block.mjs +47 -0
- package/dist/ui/code-block.mjs.map +1 -0
- package/dist/ui/compare-stage.mjs +344 -0
- package/dist/ui/compare-stage.mjs.map +1 -0
- package/dist/ui/csrf-field.mjs +20 -0
- package/dist/ui/csrf-field.mjs.map +1 -0
- package/dist/ui/css.mjs +36 -0
- package/dist/ui/css.mjs.map +1 -0
- package/dist/ui/document.mjs +130 -0
- package/dist/ui/document.mjs.map +1 -0
- package/dist/ui/dropdown.mjs +181 -0
- package/dist/ui/dropdown.mjs.map +1 -0
- package/dist/ui/feedback.mjs +341 -0
- package/dist/ui/feedback.mjs.map +1 -0
- package/dist/ui/filter-input.mjs +58 -0
- package/dist/ui/filter-input.mjs.map +1 -0
- package/dist/ui/forms.mjs +291 -0
- package/dist/ui/forms.mjs.map +1 -0
- package/dist/ui/icons/icon.mjs +53 -0
- package/dist/ui/icons/icon.mjs.map +1 -0
- package/dist/ui/icons/paths.mjs +61 -0
- package/dist/ui/icons/paths.mjs.map +1 -0
- package/dist/ui/icons/sprite.mjs +26 -0
- package/dist/ui/icons/sprite.mjs.map +1 -0
- package/dist/ui/kbd.mjs +33 -0
- package/dist/ui/kbd.mjs.map +1 -0
- package/dist/ui/layout.mjs +173 -0
- package/dist/ui/layout.mjs.map +1 -0
- package/dist/ui/progress.mjs +44 -0
- package/dist/ui/progress.mjs.map +1 -0
- package/dist/ui/segmented.mjs +77 -0
- package/dist/ui/segmented.mjs.map +1 -0
- package/dist/ui/shell/account.mjs +161 -0
- package/dist/ui/shell/account.mjs.map +1 -0
- package/dist/ui/shell/bare.mjs +69 -0
- package/dist/ui/shell/bare.mjs.map +1 -0
- package/dist/ui/shell/boot-script.mjs +46 -0
- package/dist/ui/shell/boot-script.mjs.map +1 -0
- package/dist/ui/shell/client-script.mjs +259 -0
- package/dist/ui/shell/client-script.mjs.map +1 -0
- package/dist/ui/shell/nav.mjs +111 -0
- package/dist/ui/shell/nav.mjs.map +1 -0
- package/dist/ui/shell/review-script.mjs +195 -0
- package/dist/ui/shell/review-script.mjs.map +1 -0
- package/dist/ui/shell/sidebar.mjs +323 -0
- package/dist/ui/shell/sidebar.mjs.map +1 -0
- package/dist/ui/shell/topbar.mjs +268 -0
- package/dist/ui/shell/topbar.mjs.map +1 -0
- package/dist/ui/stacks.mjs +82 -0
- package/dist/ui/stacks.mjs.map +1 -0
- package/dist/ui/styles/base.mjs +41 -0
- package/dist/ui/styles/base.mjs.map +1 -0
- package/dist/ui/styles/behavior.mjs +43 -0
- package/dist/ui/styles/behavior.mjs.map +1 -0
- package/dist/ui/styles/review-layout.mjs +347 -0
- package/dist/ui/styles/review-layout.mjs.map +1 -0
- package/dist/ui/styles/review.mjs +86 -0
- package/dist/ui/styles/review.mjs.map +1 -0
- package/dist/ui/styles/tokens.mjs +132 -0
- package/dist/ui/styles/tokens.mjs.map +1 -0
- package/dist/ui/styles.mjs +19 -0
- package/dist/ui/styles.mjs.map +1 -0
- package/dist/ui/subnav.mjs +91 -0
- package/dist/ui/subnav.mjs.map +1 -0
- package/dist/ui/table.mjs +73 -0
- package/dist/ui/table.mjs.map +1 -0
- package/dist/ui/theme.mjs +64 -0
- package/dist/ui/theme.mjs.map +1 -0
- package/dist/ui/thumbnail.mjs +51 -0
- package/dist/ui/thumbnail.mjs.map +1 -0
- package/dist/ui/time.mjs +50 -0
- package/dist/ui/time.mjs.map +1 -0
- package/package.json +92 -0
package/dist/ui/css.mjs
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { createCssContext } from "hono/css";
|
|
5
|
+
//#region src/ui/css.ts
|
|
6
|
+
/**
|
|
7
|
+
* hono/css building blocks for the server-rendered UI.
|
|
8
|
+
*
|
|
9
|
+
* A single custom context so every family module shares one `<style>`
|
|
10
|
+
* tag (mounted in `DocumentLayout`) and one readable naming scheme:
|
|
11
|
+
* start each `css` template with a label comment and it becomes the
|
|
12
|
+
* class-name prefix (`ss-btn-<hash>`). Unlabeled templates fall back to
|
|
13
|
+
* the default `css-<hash>`.
|
|
14
|
+
*
|
|
15
|
+
* Rules (enforced by `ui/consistency.test.ts`, see the `ui-building-blocks`
|
|
16
|
+
* skill for the full contract):
|
|
17
|
+
* - Import `css`/`cx`/`Style` from here, never from `hono/css` directly.
|
|
18
|
+
* - One `css` template per variant; compose via `${base}` extension so the
|
|
19
|
+
* label (and readable class name) survives. Do NOT compose variants with
|
|
20
|
+
* `cx` — it merges styles into a new unlabeled `css-<hash>` class.
|
|
21
|
+
* - Pseudo-states via `&` nesting inside the template.
|
|
22
|
+
* - Never interpolate runtime values (brand tokens stay as CSS vars in
|
|
23
|
+
* the global token `<style>` tag). Static maps only.
|
|
24
|
+
*/
|
|
25
|
+
const context = createCssContext({
|
|
26
|
+
id: "storyshelf-css",
|
|
27
|
+
classNameSlug: (hash, label) => label ? `ss-${label}-${hash}` : hash
|
|
28
|
+
});
|
|
29
|
+
/** Create a labeled, hashed class for a static declaration block. */
|
|
30
|
+
const css = (strings, ...values) => context.css(strings, ...values);
|
|
31
|
+
/** Collects every used class into `<style id="storyshelf-css">`. */
|
|
32
|
+
const Style = (args) => context.Style(args);
|
|
33
|
+
//#endregion
|
|
34
|
+
export { Style, css };
|
|
35
|
+
|
|
36
|
+
//# sourceMappingURL=css.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"css.mjs","names":[],"sources":["../../src/ui/css.ts"],"sourcesContent":["/**\n * hono/css building blocks for the server-rendered UI.\n *\n * A single custom context so every family module shares one `<style>`\n * tag (mounted in `DocumentLayout`) and one readable naming scheme:\n * start each `css` template with a label comment and it becomes the\n * class-name prefix (`ss-btn-<hash>`). Unlabeled templates fall back to\n * the default `css-<hash>`.\n *\n * Rules (enforced by `ui/consistency.test.ts`, see the `ui-building-blocks`\n * skill for the full contract):\n * - Import `css`/`cx`/`Style` from here, never from `hono/css` directly.\n * - One `css` template per variant; compose via `${base}` extension so the\n * label (and readable class name) survives. Do NOT compose variants with\n * `cx` — it merges styles into a new unlabeled `css-<hash>` class.\n * - Pseudo-states via `&` nesting inside the template.\n * - Never interpolate runtime values (brand tokens stay as CSS vars in\n * the global token `<style>` tag). Static maps only.\n */\nimport { createCssContext } from \"hono/css\";\nimport type { HtmlEscapedString } from \"hono/utils/html\";\n\n/** Values allowed inside a `css` template (static only, never user input). */\nexport type CssValue = string | number | false | null | undefined | Promise<string>;\n\n/** A `css` template function with a publicly nameable type (see below). */\nexport type CssTemplate = (strings: TemplateStringsArray, ...values: CssValue[]) => Promise<string>;\n\n/** Class-name composer with a publicly nameable type (see below). */\nexport type CxFn = (\n ...args: Array<string | boolean | null | undefined | Promise<string>>\n) => Promise<string>;\n\n/** The `<Style />` tag with a publicly nameable type (see below). */\nexport type StyleTag = (args?: { children?: Promise<string>; nonce?: string }) => HtmlEscapedString;\n\nconst context = createCssContext({\n id: \"storyshelf-css\",\n classNameSlug: (hash, label) => (label ? `ss-${label}-${hash}` : hash),\n});\n\n// Thin wrappers (not direct re-exports): hono/css declares its function\n// types via private names, which `tsc --noEmit` rejects under\n// `declaration: true` (TS4023). These annotations keep the public surface\n// nameable while returning the identical runtime objects.\n/** Create a labeled, hashed class for a static declaration block. */\n// eslint-disable-next-line promise-function-async -- thin typed wrapper returning hono/css's own promise\nexport const css: CssTemplate = (strings, ...values) => context.css(strings, ...values);\n\n/** Compose classes and conditional values (falsy-safe). */\n// eslint-disable-next-line promise-function-async -- thin typed wrapper returning hono/css's own promise\nexport const cx: CxFn = (...args) => context.cx(...args);\n\n/** Collects every used class into `<style id=\"storyshelf-css\">`. */\nexport const Style: StyleTag = (args) => context.Style(args);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAoCA,MAAM,UAAU,iBAAiB;CAC/B,IAAI;CACJ,gBAAgB,MAAM,UAAW,QAAQ,MAAM,MAAM,GAAG,SAAS;AACnE,CAAC;;AAQD,MAAa,OAAoB,SAAS,GAAG,WAAW,QAAQ,IAAI,SAAS,GAAG,MAAM;;AAOtF,MAAa,SAAmB,SAAS,QAAQ,MAAM,IAAI"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { getStore } from "../store.mjs";
|
|
5
|
+
import { getCsrfToken } from "../middleware/csrf.mjs";
|
|
6
|
+
import { Style, css } from "./css.mjs";
|
|
7
|
+
import { assetManifest } from "../asset-manifest.mjs";
|
|
8
|
+
import { BareShell } from "./shell/bare.mjs";
|
|
9
|
+
import { bootScript } from "./shell/boot-script.mjs";
|
|
10
|
+
import { clientScript } from "./shell/client-script.mjs";
|
|
11
|
+
import { Sidebar } from "./shell/sidebar.mjs";
|
|
12
|
+
import { ToastRegion, TopBar } from "./shell/topbar.mjs";
|
|
13
|
+
import { baseStyle } from "./styles.mjs";
|
|
14
|
+
import { DARK_THEME, LIGHT_THEME } from "./theme.mjs";
|
|
15
|
+
import { Fragment, jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
16
|
+
import { raw } from "hono/html";
|
|
17
|
+
//#region src/ui/document.tsx
|
|
18
|
+
const shellRoot = css`
|
|
19
|
+
/* shell */
|
|
20
|
+
display: grid;
|
|
21
|
+
grid-template-columns: var(--sidebar-width) minmax(0, 1fr);
|
|
22
|
+
min-height: 100vh;
|
|
23
|
+
@media (max-width: 880px) {
|
|
24
|
+
& {
|
|
25
|
+
grid-template-columns: minmax(0, 1fr);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
`;
|
|
29
|
+
const shellContent = css`
|
|
30
|
+
/* shell-content */
|
|
31
|
+
width: 100%;
|
|
32
|
+
max-width: 1200px;
|
|
33
|
+
margin: 0 auto;
|
|
34
|
+
padding: var(--space-6) var(--space-6) var(--space-8);
|
|
35
|
+
min-width: 0;
|
|
36
|
+
@media (max-width: 880px) {
|
|
37
|
+
& {
|
|
38
|
+
padding: var(--space-4);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
`;
|
|
42
|
+
const shellContentWide = css`
|
|
43
|
+
/* shell-content-wide */
|
|
44
|
+
${shellContent}
|
|
45
|
+
max-width: none;
|
|
46
|
+
`;
|
|
47
|
+
const shellBackdrop = css`
|
|
48
|
+
/* shell-backdrop */
|
|
49
|
+
position: fixed;
|
|
50
|
+
inset: 0;
|
|
51
|
+
z-index: 55;
|
|
52
|
+
background: rgb(0 0 0 / 0.4);
|
|
53
|
+
&[hidden] {
|
|
54
|
+
display: none;
|
|
55
|
+
}
|
|
56
|
+
`;
|
|
57
|
+
/** Full HTML document shell with sidebar, top bar, theme, toasts, and HTMX. */
|
|
58
|
+
const DocumentLayout = ({ title, nav, layout = "default", children }) => {
|
|
59
|
+
const { ui, config, sessionId } = getStore();
|
|
60
|
+
const name = ui.name ?? "StoryShelf";
|
|
61
|
+
const logo = ui.logo ?? assetManifest.mark.href;
|
|
62
|
+
const light = ui.lightTheme ?? LIGHT_THEME;
|
|
63
|
+
const dark = ui.darkTheme ?? DARK_THEME;
|
|
64
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [raw("<!DOCTYPE html>"), /* @__PURE__ */ jsxs("html", {
|
|
65
|
+
lang: "en",
|
|
66
|
+
"data-theme": "system",
|
|
67
|
+
children: [/* @__PURE__ */ jsxs("head", { children: [
|
|
68
|
+
/* @__PURE__ */ jsx("meta", { charset: "utf-8" }),
|
|
69
|
+
/* @__PURE__ */ jsx("meta", {
|
|
70
|
+
name: "viewport",
|
|
71
|
+
content: "width=device-width, initial-scale=1"
|
|
72
|
+
}),
|
|
73
|
+
/* @__PURE__ */ jsx("meta", {
|
|
74
|
+
name: "color-scheme",
|
|
75
|
+
content: "light dark"
|
|
76
|
+
}),
|
|
77
|
+
/* @__PURE__ */ jsx("meta", {
|
|
78
|
+
name: "csrf-token",
|
|
79
|
+
content: getCsrfToken(config.secret, sessionId)
|
|
80
|
+
}),
|
|
81
|
+
/* @__PURE__ */ jsx("link", {
|
|
82
|
+
rel: "icon",
|
|
83
|
+
type: "image/svg+xml",
|
|
84
|
+
href: assetManifest.mark.href
|
|
85
|
+
}),
|
|
86
|
+
/* @__PURE__ */ jsxs("title", { children: [
|
|
87
|
+
title,
|
|
88
|
+
" · ",
|
|
89
|
+
name
|
|
90
|
+
] }),
|
|
91
|
+
/* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: baseStyle(light, dark) } }),
|
|
92
|
+
/* @__PURE__ */ jsx(Style, {}),
|
|
93
|
+
/* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: bootScript() } })
|
|
94
|
+
] }), /* @__PURE__ */ jsxs("body", { children: [
|
|
95
|
+
/* @__PURE__ */ jsx("a", {
|
|
96
|
+
class: "skip-link",
|
|
97
|
+
href: "#main-content",
|
|
98
|
+
children: "Skip to content"
|
|
99
|
+
}),
|
|
100
|
+
layout === "bare" ? /* @__PURE__ */ jsx(BareShell, {
|
|
101
|
+
name,
|
|
102
|
+
logo,
|
|
103
|
+
children
|
|
104
|
+
}) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
|
|
105
|
+
class: shellRoot,
|
|
106
|
+
children: [/* @__PURE__ */ jsx(Sidebar, {
|
|
107
|
+
nav,
|
|
108
|
+
name,
|
|
109
|
+
logo
|
|
110
|
+
}), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(TopBar, { nav }), /* @__PURE__ */ jsx("main", {
|
|
111
|
+
id: "main-content",
|
|
112
|
+
class: layout === "wide" ? shellContentWide : shellContent,
|
|
113
|
+
tabindex: -1,
|
|
114
|
+
children
|
|
115
|
+
})] })]
|
|
116
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
117
|
+
class: shellBackdrop,
|
|
118
|
+
"data-sidebar-backdrop": true,
|
|
119
|
+
hidden: true
|
|
120
|
+
})] }),
|
|
121
|
+
/* @__PURE__ */ jsx(ToastRegion, {}),
|
|
122
|
+
/* @__PURE__ */ jsx("script", { src: assetManifest.htmx.href }),
|
|
123
|
+
/* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: clientScript() } })
|
|
124
|
+
] })]
|
|
125
|
+
})] });
|
|
126
|
+
};
|
|
127
|
+
//#endregion
|
|
128
|
+
export { DocumentLayout };
|
|
129
|
+
|
|
130
|
+
//# sourceMappingURL=document.mjs.map
|
|
@@ -0,0 +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 { clientScript } from \"./shell/client-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 dangerouslySetInnerHTML={{ __html: clientScript() }} />\n </body>\n </html>\n </>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AA6BA,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,yBAAyB,EAAE,QAAQ,aAAa,EAAE,EAAI,CAAA;EAC1D,EAAA,CAAA,CACF;CACN,CAAA,CAAA,EAAA,CAAA;AAEN"}
|
|
@@ -0,0 +1,181 @@
|
|
|
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/dropdown.tsx
|
|
8
|
+
const dropdownRoot = css`
|
|
9
|
+
/* dropdown */
|
|
10
|
+
position: relative;
|
|
11
|
+
display: inline-block;
|
|
12
|
+
vertical-align: top;
|
|
13
|
+
`;
|
|
14
|
+
const dropdownRootBlock = css`
|
|
15
|
+
/* dropdown-block */
|
|
16
|
+
${dropdownRoot}
|
|
17
|
+
display: block;
|
|
18
|
+
width: 100%;
|
|
19
|
+
`;
|
|
20
|
+
const dropdownTrigger = css`
|
|
21
|
+
/* dropdown-trigger */
|
|
22
|
+
list-style: none;
|
|
23
|
+
display: inline-flex;
|
|
24
|
+
align-items: center;
|
|
25
|
+
gap: 0.4rem;
|
|
26
|
+
min-height: 36px;
|
|
27
|
+
padding: 0.25rem 0.6rem;
|
|
28
|
+
border: 1px solid var(--border);
|
|
29
|
+
border-radius: var(--radius);
|
|
30
|
+
background: var(--surface-card);
|
|
31
|
+
color: var(--text-primary);
|
|
32
|
+
font-size: var(--text-base);
|
|
33
|
+
font-weight: 600;
|
|
34
|
+
cursor: pointer;
|
|
35
|
+
&::-webkit-details-marker {
|
|
36
|
+
display: none;
|
|
37
|
+
}
|
|
38
|
+
&:hover {
|
|
39
|
+
background: var(--surface-subtle);
|
|
40
|
+
}
|
|
41
|
+
`;
|
|
42
|
+
const dropdownTriggerGhost = css`
|
|
43
|
+
/* dropdown-trigger-ghost */
|
|
44
|
+
${dropdownTrigger}
|
|
45
|
+
min-height: 34px;
|
|
46
|
+
width: 100%;
|
|
47
|
+
justify-content: space-between;
|
|
48
|
+
border-color: transparent;
|
|
49
|
+
background: transparent;
|
|
50
|
+
box-shadow: none;
|
|
51
|
+
&:hover {
|
|
52
|
+
background: var(--surface-muted);
|
|
53
|
+
}
|
|
54
|
+
`;
|
|
55
|
+
const dropdownMenu = css`
|
|
56
|
+
/* dropdown-menu */
|
|
57
|
+
position: absolute;
|
|
58
|
+
z-index: 50;
|
|
59
|
+
top: calc(100% + 4px);
|
|
60
|
+
left: 0;
|
|
61
|
+
min-width: 12rem;
|
|
62
|
+
padding: 0.25rem;
|
|
63
|
+
background: var(--surface-card);
|
|
64
|
+
border: 1px solid var(--border);
|
|
65
|
+
border-radius: var(--radius);
|
|
66
|
+
box-shadow: var(--shadow-2);
|
|
67
|
+
`;
|
|
68
|
+
const dropdownMenuEnd = css`
|
|
69
|
+
/* dropdown-menu-end */
|
|
70
|
+
${dropdownMenu}
|
|
71
|
+
left: auto;
|
|
72
|
+
right: 0;
|
|
73
|
+
`;
|
|
74
|
+
const dropdownMenuAbove = css`
|
|
75
|
+
/* dropdown-menu-above */
|
|
76
|
+
${dropdownMenu}
|
|
77
|
+
top: auto;
|
|
78
|
+
bottom: calc(100% + 4px);
|
|
79
|
+
`;
|
|
80
|
+
const dropdownMenuAboveEnd = css`
|
|
81
|
+
/* dropdown-menu-above-end */
|
|
82
|
+
${dropdownMenuEnd}
|
|
83
|
+
top: auto;
|
|
84
|
+
bottom: calc(100% + 4px);
|
|
85
|
+
`;
|
|
86
|
+
const menuClasses = {
|
|
87
|
+
below: {
|
|
88
|
+
start: dropdownMenu,
|
|
89
|
+
end: dropdownMenuEnd
|
|
90
|
+
},
|
|
91
|
+
above: {
|
|
92
|
+
start: dropdownMenuAbove,
|
|
93
|
+
end: dropdownMenuAboveEnd
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
const dropdownItem = css`
|
|
97
|
+
/* dropdown-item */
|
|
98
|
+
appearance: none;
|
|
99
|
+
display: flex;
|
|
100
|
+
align-items: center;
|
|
101
|
+
gap: 0.5rem;
|
|
102
|
+
width: 100%;
|
|
103
|
+
padding: 0.45rem 0.6rem;
|
|
104
|
+
border: 0;
|
|
105
|
+
border-radius: var(--radius-sm);
|
|
106
|
+
background: transparent;
|
|
107
|
+
color: var(--text-primary);
|
|
108
|
+
font: inherit;
|
|
109
|
+
font-size: var(--text-base);
|
|
110
|
+
text-align: left;
|
|
111
|
+
text-decoration: none;
|
|
112
|
+
cursor: pointer;
|
|
113
|
+
&:hover {
|
|
114
|
+
background: var(--surface-muted);
|
|
115
|
+
text-decoration: none;
|
|
116
|
+
}
|
|
117
|
+
&[aria-current="true"] {
|
|
118
|
+
background: var(--accent-subtle);
|
|
119
|
+
color: var(--accent-fg);
|
|
120
|
+
}
|
|
121
|
+
`;
|
|
122
|
+
const dropdownDivider = css`
|
|
123
|
+
/* dropdown-divider */
|
|
124
|
+
height: 1px;
|
|
125
|
+
margin: 0.25rem 0;
|
|
126
|
+
background: var(--border-subtle);
|
|
127
|
+
`;
|
|
128
|
+
/** Popover menu built on native `<details>`; `align="end"` opens leftward. */
|
|
129
|
+
const Dropdown = ({ label, align = "start", ariaLabel, iconOnly = false, placement = "below", block = false, children }) => {
|
|
130
|
+
return /* @__PURE__ */ jsxs("details", {
|
|
131
|
+
class: block ? dropdownRootBlock : dropdownRoot,
|
|
132
|
+
"data-dropdown": true,
|
|
133
|
+
children: [/* @__PURE__ */ jsxs("summary", {
|
|
134
|
+
class: block ? dropdownTriggerGhost : dropdownTrigger,
|
|
135
|
+
"aria-label": ariaLabel,
|
|
136
|
+
children: [label, iconOnly ? null : /* @__PURE__ */ jsx(Icon, {
|
|
137
|
+
name: placement === "above" ? "chevron-up" : "chevron-down",
|
|
138
|
+
size: "sm"
|
|
139
|
+
})]
|
|
140
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
141
|
+
class: menuClasses[placement][align],
|
|
142
|
+
role: "menu",
|
|
143
|
+
children
|
|
144
|
+
})]
|
|
145
|
+
});
|
|
146
|
+
};
|
|
147
|
+
/** Menu entry: a link when `href` is set, otherwise a button (forms, actions). */
|
|
148
|
+
const DropdownItem = ({ href, icon, current = false, children, ...rest }) => {
|
|
149
|
+
const content = /* @__PURE__ */ jsxs(Fragment, { children: [icon ? /* @__PURE__ */ jsx(Icon, {
|
|
150
|
+
name: icon,
|
|
151
|
+
size: "sm"
|
|
152
|
+
}) : null, children] });
|
|
153
|
+
const aria = current ? "true" : void 0;
|
|
154
|
+
if (href) return /* @__PURE__ */ jsx("a", {
|
|
155
|
+
class: dropdownItem,
|
|
156
|
+
role: "menuitem",
|
|
157
|
+
href,
|
|
158
|
+
"aria-current": aria,
|
|
159
|
+
...rest,
|
|
160
|
+
children: content
|
|
161
|
+
});
|
|
162
|
+
return /* @__PURE__ */ jsx("button", {
|
|
163
|
+
class: dropdownItem,
|
|
164
|
+
role: "menuitem",
|
|
165
|
+
type: "button",
|
|
166
|
+
"aria-current": aria,
|
|
167
|
+
...rest,
|
|
168
|
+
children: content
|
|
169
|
+
});
|
|
170
|
+
};
|
|
171
|
+
/** Hairline between menu groups. */
|
|
172
|
+
const DropdownDivider = () => {
|
|
173
|
+
return /* @__PURE__ */ jsx("div", {
|
|
174
|
+
class: dropdownDivider,
|
|
175
|
+
role: "separator"
|
|
176
|
+
});
|
|
177
|
+
};
|
|
178
|
+
//#endregion
|
|
179
|
+
export { Dropdown, DropdownDivider, DropdownItem };
|
|
180
|
+
|
|
181
|
+
//# sourceMappingURL=dropdown.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dropdown.mjs","names":[],"sources":["../../src/ui/dropdown.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\nconst dropdownRoot = css`\n /* dropdown */\n position: relative;\n display: inline-block;\n vertical-align: top;\n`;\n\nconst dropdownRootBlock = css`\n /* dropdown-block */\n ${dropdownRoot}\n display: block;\n width: 100%;\n`;\n\nconst dropdownTrigger = css`\n /* dropdown-trigger */\n list-style: none;\n display: inline-flex;\n align-items: center;\n gap: 0.4rem;\n min-height: 36px;\n padding: 0.25rem 0.6rem;\n border: 1px solid var(--border);\n border-radius: var(--radius);\n background: var(--surface-card);\n color: var(--text-primary);\n font-size: var(--text-base);\n font-weight: 600;\n cursor: pointer;\n &::-webkit-details-marker {\n display: none;\n }\n &:hover {\n background: var(--surface-subtle);\n }\n`;\n\nconst dropdownTriggerGhost = css`\n /* dropdown-trigger-ghost */\n ${dropdownTrigger}\n min-height: 34px;\n width: 100%;\n justify-content: space-between;\n border-color: transparent;\n background: transparent;\n box-shadow: none;\n &:hover {\n background: var(--surface-muted);\n }\n`;\n\nconst dropdownMenu = css`\n /* dropdown-menu */\n position: absolute;\n z-index: 50;\n top: calc(100% + 4px);\n left: 0;\n min-width: 12rem;\n padding: 0.25rem;\n background: var(--surface-card);\n border: 1px solid var(--border);\n border-radius: var(--radius);\n box-shadow: var(--shadow-2);\n`;\n\nconst dropdownMenuEnd = css`\n /* dropdown-menu-end */\n ${dropdownMenu}\n left: auto;\n right: 0;\n`;\n\nconst dropdownMenuAbove = css`\n /* dropdown-menu-above */\n ${dropdownMenu}\n top: auto;\n bottom: calc(100% + 4px);\n`;\n\nconst dropdownMenuAboveEnd = css`\n /* dropdown-menu-above-end */\n ${dropdownMenuEnd}\n top: auto;\n bottom: calc(100% + 4px);\n`;\n\nconst menuClasses = {\n below: { start: dropdownMenu, end: dropdownMenuEnd },\n above: { start: dropdownMenuAbove, end: dropdownMenuAboveEnd },\n};\n\nconst dropdownItem = css`\n /* dropdown-item */\n appearance: none;\n display: flex;\n align-items: center;\n gap: 0.5rem;\n width: 100%;\n padding: 0.45rem 0.6rem;\n border: 0;\n border-radius: var(--radius-sm);\n background: transparent;\n color: var(--text-primary);\n font: inherit;\n font-size: var(--text-base);\n text-align: left;\n text-decoration: none;\n cursor: pointer;\n &:hover {\n background: var(--surface-muted);\n text-decoration: none;\n }\n &[aria-current=\"true\"] {\n background: var(--accent-subtle);\n color: var(--accent-fg);\n }\n`;\n\nconst dropdownDivider = css`\n /* dropdown-divider */\n height: 1px;\n margin: 0.25rem 0;\n background: var(--border-subtle);\n`;\n\n/** Popover menu built on native `<details>`; `align=\"end\"` opens leftward. */\nexport const Dropdown: FC<{\n label: unknown;\n align?: \"start\" | \"end\";\n /** Accessible name when the label is only an icon or avatar. */\n ariaLabel?: string;\n /** Hide the trailing chevron (icon-only triggers). */\n iconOnly?: boolean;\n /** Open upward (menus anchored at the bottom of the screen). */\n placement?: \"below\" | \"above\";\n /** Full-width, borderless trigger (sidebar rows). */\n block?: boolean;\n children?: unknown;\n}> = ({\n label,\n align = \"start\",\n ariaLabel,\n iconOnly = false,\n placement = \"below\",\n block = false,\n children,\n}) => {\n return (\n <details class={block ? dropdownRootBlock : dropdownRoot} data-dropdown>\n <summary class={block ? dropdownTriggerGhost : dropdownTrigger} aria-label={ariaLabel}>\n {label}\n {iconOnly ? null : (\n <Icon name={placement === \"above\" ? \"chevron-up\" : \"chevron-down\"} size=\"sm\" />\n )}\n </summary>\n <div class={menuClasses[placement][align]} role=\"menu\">\n {children}\n </div>\n </details>\n );\n};\n\n/** Menu entry: a link when `href` is set, otherwise a button (forms, actions). */\nexport const DropdownItem: FC<{\n href?: string;\n icon?: IconName;\n current?: boolean;\n children?: unknown;\n [key: string]: unknown;\n}> = ({ href, icon, current = false, children, ...rest }) => {\n const content = (\n <>\n {icon ? <Icon name={icon} size=\"sm\" /> : null}\n {children}\n </>\n );\n const aria = current ? \"true\" : undefined;\n if (href) {\n return (\n <a class={dropdownItem} role=\"menuitem\" href={href} aria-current={aria} {...rest}>\n {content}\n </a>\n );\n }\n return (\n <button class={dropdownItem} role=\"menuitem\" type=\"button\" aria-current={aria} {...rest}>\n {content}\n </button>\n );\n};\n\n/** Hairline between menu groups. */\nexport const DropdownDivider: FC = () => {\n return <div class={dropdownDivider} role=\"separator\" />;\n};\n"],"mappings":";;;;;;;AAOA,MAAM,eAAe,GAAG;;;;;;AAOxB,MAAM,oBAAoB,GAAG;;IAEzB,aAAa;;;;AAKjB,MAAM,kBAAkB,GAAG;;;;;;;;;;;;;;;;;;;;;;AAuB3B,MAAM,uBAAuB,GAAG;;IAE5B,gBAAgB;;;;;;;;;;;AAYpB,MAAM,eAAe,GAAG;;;;;;;;;;;;;AAcxB,MAAM,kBAAkB,GAAG;;IAEvB,aAAa;;;;AAKjB,MAAM,oBAAoB,GAAG;;IAEzB,aAAa;;;;AAKjB,MAAM,uBAAuB,GAAG;;IAE5B,gBAAgB;;;;AAKpB,MAAM,cAAc;CAClB,OAAO;EAAE,OAAO;EAAc,KAAK;CAAgB;CACnD,OAAO;EAAE,OAAO;EAAmB,KAAK;CAAqB;AAC/D;AAEA,MAAM,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BxB,MAAM,kBAAkB,GAAG;;;;;;;AAQ3B,MAAa,YAYP,EACJ,OACA,QAAQ,SACR,WACA,WAAW,OACX,YAAY,SACZ,QAAQ,OACR,eACI;CACJ,OACE,qBAAC,WAAD;EAAS,OAAO,QAAQ,oBAAoB;EAAc,iBAAA;EAA1D,UAAA,CACE,qBAAC,WAAD;GAAS,OAAO,QAAQ,uBAAuB;GAAiB,cAAY;GAA5E,UAAA,CACG,OACA,WAAW,OACV,oBAAC,MAAD;IAAM,MAAM,cAAc,UAAU,eAAe;IAAgB,MAAK;GAAM,CAAA,CAEzE;EACT,CAAA,GAAA,oBAAC,OAAD;GAAK,OAAO,YAAY,UAAU,CAAC;GAAQ,MAAK;GAC7C;EACE,CAAA,CACE;;AAEb;;AAGA,MAAa,gBAMP,EAAE,MAAM,MAAM,UAAU,OAAO,UAAU,GAAG,WAAW;CAC3D,MAAM,UACJ,qBAAA,UAAA,EAAA,UAAA,CACG,OAAO,oBAAC,MAAD;EAAM,MAAM;EAAM,MAAK;CAAM,CAAA,IAAI,MACxC,QACD,EAAA,CAAA;CAEJ,MAAM,OAAO,UAAU,SAAS,KAAA;CAChC,IAAI,MACF,OACE,oBAAC,KAAD;EAAG,OAAO;EAAc,MAAK;EAAiB;EAAM,gBAAc;EAAM,GAAI;EACzE,UAAA;CACA,CAAA;CAGP,OACE,oBAAC,UAAD;EAAQ,OAAO;EAAc,MAAK;EAAW,MAAK;EAAS,gBAAc;EAAM,GAAI;EAChF,UAAA;CACK,CAAA;AAEZ;;AAGA,MAAa,wBAA4B;CACvC,OAAO,oBAAC,OAAD;EAAK,OAAO;EAAiB,MAAK;CAAa,CAAA;AACxD"}
|