@f-ewald/components 1.2.1 → 1.3.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.
Files changed (57) hide show
  1. package/README.md +15 -2
  2. package/custom-elements.json +1019 -93
  3. package/dist/action-bar.d.ts +24 -0
  4. package/dist/action-bar.d.ts.map +1 -0
  5. package/dist/action-bar.js +67 -0
  6. package/dist/action-bar.js.map +1 -0
  7. package/dist/app-shell.d.ts +86 -0
  8. package/dist/app-shell.d.ts.map +1 -0
  9. package/dist/app-shell.js +460 -0
  10. package/dist/app-shell.js.map +1 -0
  11. package/dist/app-sidebar.d.ts +44 -0
  12. package/dist/app-sidebar.d.ts.map +1 -0
  13. package/dist/app-sidebar.js +211 -0
  14. package/dist/app-sidebar.js.map +1 -0
  15. package/dist/form-actions.d.ts +27 -0
  16. package/dist/form-actions.d.ts.map +1 -0
  17. package/dist/form-actions.js +65 -0
  18. package/dist/form-actions.js.map +1 -0
  19. package/dist/index.d.ts +8 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +8 -0
  22. package/dist/index.js.map +1 -1
  23. package/dist/mcp-server.js +82 -5
  24. package/dist/mcp-server.js.map +1 -1
  25. package/dist/page-header.d.ts +27 -0
  26. package/dist/page-header.d.ts.map +1 -0
  27. package/dist/page-header.js +106 -0
  28. package/dist/page-header.js.map +1 -0
  29. package/dist/pagination-nav.d.ts +34 -0
  30. package/dist/pagination-nav.d.ts.map +1 -0
  31. package/dist/pagination-nav.js +144 -0
  32. package/dist/pagination-nav.js.map +1 -0
  33. package/dist/timeline-container.d.ts +22 -0
  34. package/dist/timeline-container.d.ts.map +1 -0
  35. package/dist/timeline-container.js +42 -0
  36. package/dist/timeline-container.js.map +1 -0
  37. package/dist/timeline-entry.d.ts +31 -0
  38. package/dist/timeline-entry.d.ts.map +1 -0
  39. package/dist/timeline-entry.js +170 -0
  40. package/dist/timeline-entry.js.map +1 -0
  41. package/docs/action-bar.md +41 -0
  42. package/docs/app-shell.md +85 -0
  43. package/docs/app-sidebar.md +69 -0
  44. package/docs/design-language.md +74 -1
  45. package/docs/form-actions.md +43 -0
  46. package/docs/layouts/detail-only.md +52 -0
  47. package/docs/layouts/form-page.md +52 -0
  48. package/docs/layouts/list-detail.md +52 -0
  49. package/docs/layouts/list-only.md +50 -0
  50. package/docs/layouts/record-detail.md +88 -0
  51. package/docs/mcp-evaluation.md +21 -0
  52. package/docs/page-header.md +47 -0
  53. package/docs/pagination-nav.md +54 -0
  54. package/docs/timeline-container.md +44 -0
  55. package/docs/timeline-entry.md +52 -0
  56. package/llms.txt +229 -0
  57. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-shell.js","sourceRoot":"","sources":["../src/app-shell.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, css, html, nothing, type PropertyValues } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { iconBars3 } from \"./icons.js\";\nimport \"./kbd-hint.js\";\nimport { tokens } from \"./tokens.js\";\n\n/** Fired when the built-in toggle changes the sidebar state. */\nexport interface SidebarToggleDetail {\n /** Desktop rail state. */\n collapsed: boolean;\n /** Mobile drawer open state. */\n mobileOpen: boolean;\n}\n\n/**\n * The dashboard page shell: a slot-based CSS-grid backbone that arranges a\n * full-height sidebar, a top bar, the main content, an optional right-hand\n * detail column, and an optional footer. It owns the responsive behavior so\n * consumers don't re-implement it — above the shared 48rem breakpoint the\n * sidebar collapses to an icon rail and the detail region is an inline column;\n * at or below it the sidebar becomes an off-canvas drawer and the detail region\n * an overlay, both dismissed by a scrim or Escape.\n *\n * Widths are tunable per instance via `--component-sidebar-width` (16rem),\n * `--component-sidebar-rail-width` (3.5rem), and `--component-topbar-height`\n * (3rem); the detail column reuses the 20rem/25rem panel widths. The main\n * content area is white by default — override it with\n * `--component-main-background`. Give the shell a height (e.g. `height: 100vh`)\n * so the sidebar and main can size and scroll.\n *\n * The built-in top-bar button toggles the sidebar, and so does pressing `[`\n * anywhere on the page (ignored while typing in a text field or with a\n * modifier held). Hovering or keyboard-focusing the toggle reveals a tooltip\n * naming the action and its `[` shortcut — the shortcut is not shown as\n * permanent chrome.\n *\n * @element app-shell\n * @slot - Main content area.\n * @slot sidebar - Full-height navigation (typically `app-sidebar`).\n * @slot topbar - Top bar content, right of the built-in toggle.\n * @slot detail - Optional right-hand detail; shown when `detail-open` is set.\n * @slot footer - Optional footer beneath the main content.\n * @fires sidebar-toggle - The built-in toggle changed the sidebar state.\n * @fires detail-close - The scrim or Escape dismissed the mobile detail overlay.\n */\n@customElement(\"app-shell\")\nexport class AppShell extends LitElement {\n /** Collapses the sidebar to an icon rail on desktop. */\n @property({ type: Boolean, reflect: true, attribute: \"sidebar-collapsed\" })\n sidebarCollapsed = false;\n /** Shows the right-hand detail region (inline column, or overlay on mobile). */\n @property({ type: Boolean, reflect: true, attribute: \"detail-open\" }) detailOpen = false;\n /** Detail width: `compact` (20rem) or `comfortable` (25rem). */\n @property({ attribute: \"detail-width\" }) detailWidth: \"compact\" | \"comfortable\" = \"compact\";\n\n /** Whether the viewport is at/below the 48rem breakpoint. */\n @state() private _mobile = false;\n /** Whether the mobile off-canvas nav drawer is open. */\n @state() private _mobileNavOpen = false;\n /** Whether the footer slot has assigned content. */\n @state() private _hasFooter = false;\n\n static override styles = [\n tokens,\n css`\n :host {\n display: block;\n block-size: 100%;\n }\n .shell {\n --_sidebar-w: var(--component-sidebar-width, 16rem);\n --_detail-w: 0px;\n display: grid;\n grid-template-columns: var(--_sidebar-w) minmax(0, 1fr) var(--_detail-w);\n grid-template-rows: auto minmax(0, 1fr) auto;\n grid-template-areas:\n \"sidebar topbar topbar\"\n \"sidebar main detail\"\n \"sidebar footer footer\";\n block-size: 100%;\n min-height: 0;\n background: var(--ui-surface, #ffffff);\n }\n :host([sidebar-collapsed]) .shell {\n --_sidebar-w: var(--component-sidebar-rail-width, 3.5rem);\n }\n :host([detail-open]) .shell {\n --_detail-w: 20rem;\n }\n :host([detail-open][detail-width=\"comfortable\"]) .shell {\n --_detail-w: 25rem;\n }\n .sidebar {\n grid-area: sidebar;\n min-height: 0;\n overflow: hidden;\n border-right: 1px solid var(--ui-border, #e2e8f0);\n background: var(--ui-surface, #ffffff);\n }\n .topbar {\n grid-area: topbar;\n display: flex;\n align-items: center;\n gap: 0.5rem;\n min-height: var(--component-topbar-height, 3rem);\n padding: 0.5rem 0.75rem;\n border-bottom: 1px solid var(--ui-border, #e2e8f0);\n background: var(--ui-surface, #ffffff);\n }\n .nav-toggle {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: 0 0 auto;\n width: 2rem;\n height: 2rem;\n padding: 0;\n background: none;\n border: none;\n cursor: pointer;\n color: var(--ui-text-muted, #64748b);\n border-radius: var(--ui-radius-sm, 0.25rem);\n }\n .nav-group {\n position: relative;\n display: inline-flex;\n align-items: center;\n gap: 0.25rem;\n flex: 0 0 auto;\n }\n .nav-tip {\n position: absolute;\n top: 100%;\n left: 0;\n margin-top: 0.25rem;\n z-index: var(--component-layer-z, 100);\n display: inline-flex;\n align-items: center;\n gap: 0.25rem;\n white-space: nowrap;\n padding: 0.25rem 0.5rem;\n border-radius: var(--ui-radius-sm, 0.25rem);\n background: var(--ui-tooltip, #0f172a);\n color: var(--ui-on-accent, #ffffff);\n box-shadow: var(--ui-shadow, 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1));\n font-size: var(--ui-font-size-sm, 0.75rem);\n line-height: var(--ui-line-height-tight, 1.25);\n opacity: 0;\n visibility: hidden;\n pointer-events: none;\n transition: opacity 120ms ease;\n }\n .nav-toggle:hover + .nav-tip,\n .nav-toggle:focus-visible + .nav-tip {\n opacity: 1;\n visibility: visible;\n }\n .nav-toggle:hover {\n background: var(--ui-surface-muted, #f8fafc);\n }\n .nav-toggle:focus-visible {\n outline: none;\n box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));\n }\n .topbar-content {\n flex: 1 1 auto;\n min-width: 0;\n }\n .main {\n grid-area: main;\n min-height: 0;\n min-width: 0;\n overflow: auto;\n padding: 1rem;\n background: var(--component-main-background, var(--ui-surface, #ffffff));\n }\n .detail {\n grid-area: detail;\n display: none;\n min-height: 0;\n overflow: auto;\n border-left: 1px solid var(--ui-border, #e2e8f0);\n background: var(--ui-surface, #ffffff);\n }\n :host([detail-open]) .detail {\n display: block;\n }\n .footer {\n grid-area: footer;\n padding: 0.5rem 0.75rem;\n border-top: 1px solid var(--ui-border, #e2e8f0);\n background: var(--ui-surface, #ffffff);\n }\n .footer.empty {\n display: none;\n }\n .scrim {\n display: none;\n }\n @media (max-width: 48rem) {\n .shell {\n grid-template-columns: minmax(0, 1fr);\n grid-template-rows: auto minmax(0, 1fr) auto;\n grid-template-areas:\n \"topbar\"\n \"main\"\n \"footer\";\n }\n .sidebar {\n position: fixed;\n top: 0;\n bottom: 0;\n left: 0;\n width: var(--component-sidebar-width, 16rem);\n z-index: var(--component-layer-z, 100);\n transform: translateX(-100%);\n transition: transform 250ms ease;\n }\n .shell.nav-open .sidebar {\n transform: translateX(0);\n box-shadow: var(\n --ui-shadow-lg,\n 0 20px 25px -5px rgb(0 0 0 / 0.1),\n 0 8px 10px -6px rgb(0 0 0 / 0.1)\n );\n }\n .detail {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: var(--_detail-w, 20rem);\n max-width: calc(100vw - 3rem);\n z-index: var(--component-layer-z, 100);\n transform: translateX(110%);\n transition: transform 250ms ease;\n }\n :host([detail-open]) .detail {\n transform: translateX(0);\n box-shadow: var(\n --ui-shadow-lg,\n 0 20px 25px -5px rgb(0 0 0 / 0.1),\n 0 8px 10px -6px rgb(0 0 0 / 0.1)\n );\n }\n .scrim.show {\n display: block;\n position: fixed;\n inset: 0;\n z-index: calc(var(--component-layer-z, 100) - 1);\n background: var(--ui-overlay, rgb(15 23 42 / 0.45));\n border: none;\n }\n }\n @media (prefers-reduced-motion: reduce) {\n .sidebar,\n .detail,\n .nav-tip {\n transition: none;\n }\n }\n @media (forced-colors: active) {\n .nav-toggle:focus-visible {\n outline: 2px solid CanvasText;\n outline-offset: 2px;\n box-shadow: none;\n }\n .nav-tip {\n border: 1px solid CanvasText;\n }\n }\n `,\n ];\n\n private _mediaQuery: MediaQueryList | null = null;\n private _previousFocus: HTMLElement | null = null;\n\n override connectedCallback(): void {\n super.connectedCallback();\n this._mediaQuery = window.matchMedia(\"(max-width: 48rem)\");\n this._mobile = this._mediaQuery.matches;\n this._mediaQuery.addEventListener(\"change\", this._onMediaChange);\n window.addEventListener(\"keydown\", this._onKeydown);\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n this._mediaQuery?.removeEventListener(\"change\", this._onMediaChange);\n this._mediaQuery = null;\n window.removeEventListener(\"keydown\", this._onKeydown);\n }\n\n /** Tracks the breakpoint and closes the mobile drawer when returning to desktop. */\n private _onMediaChange = (event: MediaQueryListEvent): void => {\n this._mobile = event.matches;\n if (!event.matches) this._mobileNavOpen = false;\n };\n\n /** Handles the `[` sidebar shortcut and Escape dismissal of mobile overlays. */\n private _onKeydown = (event: KeyboardEvent): void => {\n if (event.defaultPrevented) return;\n if (event.key === \"Escape\" && this._mobile) {\n if (this.detailOpen) {\n event.preventDefault();\n this._closeDetail();\n } else if (this._mobileNavOpen) {\n event.preventDefault();\n this._mobileNavOpen = false;\n }\n return;\n }\n if (\n event.key === \"[\" &&\n !event.ctrlKey &&\n !event.metaKey &&\n !event.altKey &&\n !this._isEditableTarget(event)\n ) {\n event.preventDefault();\n this._toggleSidebar();\n }\n };\n\n /** Whether the keydown originated in a text field, so the shortcut defers to typing. */\n private _isEditableTarget(event: KeyboardEvent): boolean {\n const target = event.composedPath()[0] as HTMLElement | undefined;\n if (!target) return false;\n if (target.isContentEditable) return true;\n const tag = target.tagName;\n return tag === \"INPUT\" || tag === \"TEXTAREA\" || tag === \"SELECT\";\n }\n\n /** Toggles the rail (desktop) or the off-canvas drawer (mobile) and announces it. */\n private _toggleSidebar(): void {\n if (this._mobile) {\n this._mobileNavOpen = !this._mobileNavOpen;\n if (this._mobileNavOpen) this._previousFocus = document.activeElement as HTMLElement | null;\n } else {\n this.sidebarCollapsed = !this.sidebarCollapsed;\n }\n this.dispatchEvent(\n new CustomEvent<SidebarToggleDetail>(\"sidebar-toggle\", {\n detail: { collapsed: this.sidebarCollapsed, mobileOpen: this._mobileNavOpen },\n bubbles: true,\n composed: true,\n }),\n );\n }\n\n /** Closes the detail overlay and notifies the consumer that owns `detail-open`. */\n private _closeDetail(): void {\n this.detailOpen = false;\n this.dispatchEvent(new CustomEvent(\"detail-close\", { bubbles: true, composed: true }));\n }\n\n /** Scrim click dismisses whichever overlay is open. */\n private _onScrim(): void {\n if (this.detailOpen) this._closeDetail();\n if (this._mobileNavOpen) this._mobileNavOpen = false;\n }\n\n protected override updated(changed: PropertyValues): void {\n if (changed.has(\"sidebarCollapsed\") || changed.has(\"_mobile\")) this._syncSidebar();\n if (!changed.has(\"_mobileNavOpen\")) return;\n if (this._mobileNavOpen) {\n this.shadowRoot?.querySelector<HTMLElement>(\".sidebar\")?.focus();\n return;\n }\n if (this._previousFocus?.isConnected) this._previousFocus.focus();\n this._previousFocus = null;\n }\n\n /**\n * Mirrors the shell's rail state onto the slotted sidebar so it hides labels\n * when collapsed on desktop, and always shows them in the mobile drawer.\n */\n private _syncSidebar(): void {\n const rail = this.sidebarCollapsed && !this._mobile;\n const slot = this.shadowRoot?.querySelector<HTMLSlotElement>('slot[name=\"sidebar\"]');\n for (const element of slot?.assignedElements() ?? []) {\n element.toggleAttribute(\"collapsed\", rail);\n }\n }\n\n override render() {\n const scrimActive = this._mobile && (this._mobileNavOpen || this.detailOpen);\n return html`\n <div class=\"shell ${this._mobileNavOpen ? \"nav-open\" : \"\"}\">\n <aside class=\"sidebar\" tabindex=\"-1\" aria-label=\"Primary\">\n <slot name=\"sidebar\" @slotchange=${this._syncSidebar}></slot>\n </aside>\n <header class=\"topbar\">\n <div class=\"nav-group\">\n <button\n class=\"nav-toggle\"\n type=\"button\"\n aria-label=\"Toggle navigation\"\n aria-keyshortcuts=\"[\"\n aria-describedby=\"nav-tip\"\n aria-expanded=${this._mobile ? String(this._mobileNavOpen) : String(!this.sidebarCollapsed)}\n @click=${this._toggleSidebar}\n >\n ${iconBars3(18)}\n </button>\n <span class=\"nav-tip\" id=\"nav-tip\" role=\"tooltip\">\n <span\n >${this._mobile\n ? \"Navigation\"\n : this.sidebarCollapsed\n ? \"Expand sidebar\"\n : \"Collapse sidebar\"}</span\n >\n ${this._mobile ? nothing : html`<kbd-hint keys=\"[\"></kbd-hint>`}\n </span>\n </div>\n <div class=\"topbar-content\"><slot name=\"topbar\"></slot></div>\n </header>\n <main class=\"main\"><slot></slot></main>\n <aside\n class=\"detail\"\n aria-label=\"Detail\"\n ?inert=${!this.detailOpen}\n aria-hidden=${String(!this.detailOpen)}\n >\n <slot name=\"detail\"></slot>\n </aside>\n <footer class=\"footer ${this._hasFooter ? \"\" : \"empty\"}\">\n <slot name=\"footer\" @slotchange=${this._onFooterSlotChange}></slot>\n </footer>\n <button\n class=\"scrim ${scrimActive ? \"show\" : \"\"}\"\n type=\"button\"\n tabindex=\"-1\"\n aria-label=\"Close\"\n @click=${this._onScrim}\n ?hidden=${!scrimActive}\n ></button>\n </div>\n `;\n }\n\n /** Collapses the footer row when nothing is slotted into it. */\n private _onFooterSlotChange(event: Event): void {\n this._hasFooter = (event.target as HTMLSlotElement).assignedNodes({ flatten: true }).length > 0;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"app-shell\": AppShell;\n }\n}\n"]}
@@ -0,0 +1,44 @@
1
+ import { LitElement } from "lit";
2
+ /**
3
+ * Collapsible navigation sidebar for the `app-shell` sidebar slot. It is
4
+ * deliberately presentational and router-agnostic: the consumer supplies the
5
+ * nav items as plain `<a>`/`<button>` elements (each an icon followed by a
6
+ * label), optional `<p>` group headings, and marks the active item with
7
+ * `aria-current="page"`. The sidebar styles them, tracks hover/active/focus,
8
+ * and — in collapsed "rail" mode — centers the icons and hides the labels.
9
+ *
10
+ * Rail mode is driven by the `collapsed` attribute (the parent `app-shell`
11
+ * toggles it). Labels are hidden by the inherited `--app-sidebar-label` custom
12
+ * property, so each label element should read it, e.g.
13
+ * `<span style="display: var(--app-sidebar-label, inline)">Reports</span>`;
14
+ * keep an `aria-label` on the item so its accessible name survives collapse.
15
+ * The sidebar fills the width and height of its container — `app-shell` owns
16
+ * the actual rail/expanded width.
17
+ *
18
+ * @element app-sidebar
19
+ * @slot - Navigation items: `<a>`/`<button>` links and optional `<p>` headings.
20
+ * @slot header - Optional brand pinned to the top: a small logo plus a name.
21
+ * Wrap the name in a `--app-sidebar-label` element so rail mode shows only the
22
+ * logo, centered in line with the nav icons.
23
+ * @slot footer - Optional account/settings area pinned to the bottom.
24
+ */
25
+ export declare class AppSidebar extends LitElement {
26
+ /** Collapses the sidebar to an icon rail (centers icons, hides labels). */
27
+ collapsed: boolean;
28
+ /** Whether the header slot has assigned content. */
29
+ private _hasHeader;
30
+ /** Whether the footer slot has assigned content. */
31
+ private _hasFooter;
32
+ static styles: import("lit").CSSResult[];
33
+ /** Collapses the header row when nothing is slotted into it. */
34
+ private _onHeaderSlotChange;
35
+ /** Collapses the footer row when nothing is slotted into it. */
36
+ private _onFooterSlotChange;
37
+ render(): import("lit-html").TemplateResult<1>;
38
+ }
39
+ declare global {
40
+ interface HTMLElementTagNameMap {
41
+ "app-sidebar": AppSidebar;
42
+ }
43
+ }
44
+ //# sourceMappingURL=app-sidebar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-sidebar.d.ts","sourceRoot":"","sources":["../src/app-sidebar.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAI5C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBACa,UAAW,SAAQ,UAAU;IACxC,2EAA2E;IAC/B,SAAS,UAAS;IAE9D,oDAAoD;IAC3C,OAAO,CAAC,UAAU,CAAS;IACpC,oDAAoD;IAC3C,OAAO,CAAC,UAAU,CAAS;IAEpC,OAAgB,MAAM,4BAkIpB;IAEF,gEAAgE;IAChE,OAAO,CAAC,mBAAmB;IAI3B,gEAAgE;IAChE,OAAO,CAAC,mBAAmB;IAIlB,MAAM,yCAcd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,aAAa,EAAE,UAAU,CAAC;KAC3B;CACF"}
@@ -0,0 +1,211 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { LitElement, css, html } from "lit";
8
+ import { customElement, property, state } from "lit/decorators.js";
9
+ import { tokens } from "./tokens.js";
10
+ /**
11
+ * Collapsible navigation sidebar for the `app-shell` sidebar slot. It is
12
+ * deliberately presentational and router-agnostic: the consumer supplies the
13
+ * nav items as plain `<a>`/`<button>` elements (each an icon followed by a
14
+ * label), optional `<p>` group headings, and marks the active item with
15
+ * `aria-current="page"`. The sidebar styles them, tracks hover/active/focus,
16
+ * and — in collapsed "rail" mode — centers the icons and hides the labels.
17
+ *
18
+ * Rail mode is driven by the `collapsed` attribute (the parent `app-shell`
19
+ * toggles it). Labels are hidden by the inherited `--app-sidebar-label` custom
20
+ * property, so each label element should read it, e.g.
21
+ * `<span style="display: var(--app-sidebar-label, inline)">Reports</span>`;
22
+ * keep an `aria-label` on the item so its accessible name survives collapse.
23
+ * The sidebar fills the width and height of its container — `app-shell` owns
24
+ * the actual rail/expanded width.
25
+ *
26
+ * @element app-sidebar
27
+ * @slot - Navigation items: `<a>`/`<button>` links and optional `<p>` headings.
28
+ * @slot header - Optional brand pinned to the top: a small logo plus a name.
29
+ * Wrap the name in a `--app-sidebar-label` element so rail mode shows only the
30
+ * logo, centered in line with the nav icons.
31
+ * @slot footer - Optional account/settings area pinned to the bottom.
32
+ */
33
+ let AppSidebar = class AppSidebar extends LitElement {
34
+ constructor() {
35
+ super(...arguments);
36
+ /** Collapses the sidebar to an icon rail (centers icons, hides labels). */
37
+ this.collapsed = false;
38
+ /** Whether the header slot has assigned content. */
39
+ this._hasHeader = false;
40
+ /** Whether the footer slot has assigned content. */
41
+ this._hasFooter = false;
42
+ }
43
+ static { this.styles = [
44
+ tokens,
45
+ css `
46
+ :host {
47
+ display: block;
48
+ height: 100%;
49
+ --app-sidebar-label: inline;
50
+ }
51
+ :host([collapsed]) {
52
+ --app-sidebar-label: none;
53
+ }
54
+ .sidebar {
55
+ display: flex;
56
+ flex-direction: column;
57
+ height: 100%;
58
+ min-height: 0;
59
+ background: var(--ui-surface, #ffffff);
60
+ color: var(--ui-text, #0f172a);
61
+ font-family: var(
62
+ --ui-font,
63
+ ui-sans-serif,
64
+ system-ui,
65
+ sans-serif,
66
+ "Apple Color Emoji",
67
+ "Segoe UI Emoji",
68
+ "Segoe UI Symbol",
69
+ "Noto Color Emoji"
70
+ );
71
+ }
72
+ .brand {
73
+ flex: 0 0 auto;
74
+ display: flex;
75
+ align-items: center;
76
+ padding: 0.75rem;
77
+ border-bottom: 1px solid var(--ui-border, #e2e8f0);
78
+ }
79
+ .foot {
80
+ flex: 0 0 auto;
81
+ padding: 0.75rem;
82
+ border-top: 1px solid var(--ui-border, #e2e8f0);
83
+ }
84
+ .brand.empty,
85
+ .foot.empty {
86
+ display: none;
87
+ }
88
+ .nav {
89
+ flex: 1 1 auto;
90
+ min-height: 0;
91
+ overflow-y: auto;
92
+ display: flex;
93
+ flex-direction: column;
94
+ gap: 0.25rem;
95
+ padding: 0.5rem;
96
+ }
97
+ ::slotted(a),
98
+ ::slotted(button) {
99
+ display: flex;
100
+ align-items: center;
101
+ gap: 0.5rem;
102
+ width: 100%;
103
+ box-sizing: border-box;
104
+ padding: 0.5rem;
105
+ border: none;
106
+ border-radius: var(--ui-radius-sm, 0.25rem);
107
+ background: none;
108
+ color: var(--ui-text, #0f172a);
109
+ text-align: left;
110
+ text-decoration: none;
111
+ white-space: nowrap;
112
+ cursor: pointer;
113
+ font-size: var(--ui-font-size-sm, 0.75rem);
114
+ font-weight: var(--ui-font-weight-medium, 500);
115
+ line-height: var(--ui-line-height-tight, 1.25);
116
+ font-family: var(
117
+ --ui-font,
118
+ ui-sans-serif,
119
+ system-ui,
120
+ sans-serif,
121
+ "Apple Color Emoji",
122
+ "Segoe UI Emoji",
123
+ "Segoe UI Symbol",
124
+ "Noto Color Emoji"
125
+ );
126
+ }
127
+ ::slotted(a:hover),
128
+ ::slotted(button:hover) {
129
+ background: var(--ui-surface-muted, #f8fafc);
130
+ }
131
+ ::slotted(a[aria-current]),
132
+ ::slotted(button[aria-current]) {
133
+ background: var(--ui-surface-muted, #f8fafc);
134
+ color: var(--ui-primary, #4f46e5);
135
+ font-weight: var(--ui-font-weight-semibold, 600);
136
+ }
137
+ ::slotted(a:focus-visible),
138
+ ::slotted(button:focus-visible) {
139
+ outline: none;
140
+ box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));
141
+ }
142
+ ::slotted(p) {
143
+ margin: 0;
144
+ padding: 0.5rem 0.5rem 0.25rem;
145
+ font-size: var(--ui-font-size-xs, 0.6875rem);
146
+ font-weight: var(--ui-font-weight-semibold, 600);
147
+ line-height: var(--ui-line-height-tight, 1.25);
148
+ text-transform: uppercase;
149
+ letter-spacing: var(--ui-tracking-wide, 0.04em);
150
+ color: var(--ui-text-muted, #64748b);
151
+ }
152
+ :host([collapsed]) ::slotted(a),
153
+ :host([collapsed]) ::slotted(button) {
154
+ justify-content: center;
155
+ }
156
+ :host([collapsed]) ::slotted(p) {
157
+ display: none;
158
+ }
159
+ :host([collapsed]) .brand {
160
+ justify-content: center;
161
+ padding-left: 0.5rem;
162
+ padding-right: 0.5rem;
163
+ }
164
+ @media (forced-colors: active) {
165
+ ::slotted(a:focus-visible),
166
+ ::slotted(button:focus-visible) {
167
+ outline: 2px solid CanvasText;
168
+ outline-offset: 2px;
169
+ box-shadow: none;
170
+ }
171
+ }
172
+ `,
173
+ ]; }
174
+ /** Collapses the header row when nothing is slotted into it. */
175
+ _onHeaderSlotChange(event) {
176
+ this._hasHeader = event.target.assignedNodes({ flatten: true }).length > 0;
177
+ }
178
+ /** Collapses the footer row when nothing is slotted into it. */
179
+ _onFooterSlotChange(event) {
180
+ this._hasFooter = event.target.assignedNodes({ flatten: true }).length > 0;
181
+ }
182
+ render() {
183
+ return html `
184
+ <div class="sidebar">
185
+ <div class="brand ${this._hasHeader ? "" : "empty"}">
186
+ <slot name="header" @slotchange=${this._onHeaderSlotChange}></slot>
187
+ </div>
188
+ <nav class="nav" aria-label="Sidebar">
189
+ <slot></slot>
190
+ </nav>
191
+ <div class="foot ${this._hasFooter ? "" : "empty"}">
192
+ <slot name="footer" @slotchange=${this._onFooterSlotChange}></slot>
193
+ </div>
194
+ </div>
195
+ `;
196
+ }
197
+ };
198
+ __decorate([
199
+ property({ type: Boolean, reflect: true })
200
+ ], AppSidebar.prototype, "collapsed", void 0);
201
+ __decorate([
202
+ state()
203
+ ], AppSidebar.prototype, "_hasHeader", void 0);
204
+ __decorate([
205
+ state()
206
+ ], AppSidebar.prototype, "_hasFooter", void 0);
207
+ AppSidebar = __decorate([
208
+ customElement("app-sidebar")
209
+ ], AppSidebar);
210
+ export { AppSidebar };
211
+ //# sourceMappingURL=app-sidebar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-sidebar.js","sourceRoot":"","sources":["../src/app-sidebar.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEI,IAAM,UAAU,GAAhB,MAAM,UAAW,SAAQ,UAAU;IAAnC;;QACL,2EAA2E;QAC/B,cAAS,GAAG,KAAK,CAAC;QAE9D,oDAAoD;QACnC,eAAU,GAAG,KAAK,CAAC;QACpC,oDAAoD;QACnC,eAAU,GAAG,KAAK,CAAC;IA+JtC,CAAC;aA7JiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA+HF;KACF,AAlIqB,CAkIpB;IAEF,gEAAgE;IACxD,mBAAmB,CAAC,KAAY;QACtC,IAAI,CAAC,UAAU,GAAI,KAAK,CAAC,MAA0B,CAAC,aAAa,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;IAClG,CAAC;IAED,gEAAgE;IACxD,mBAAmB,CAAC,KAAY;QACtC,IAAI,CAAC,UAAU,GAAI,KAAK,CAAC,MAA0B,CAAC,aAAa,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;IAClG,CAAC;IAEQ,MAAM;QACb,OAAO,IAAI,CAAA;;4BAEa,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO;4CACd,IAAI,CAAC,mBAAmB;;;;;2BAKzC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO;4CACb,IAAI,CAAC,mBAAmB;;;KAG/D,CAAC;IACJ,CAAC;CACF,CAAA;AApK6C;IAA3C,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;6CAAmB;AAG7C;IAAhB,KAAK,EAAE;8CAA4B;AAEnB;IAAhB,KAAK,EAAE;8CAA4B;AAPzB,UAAU;IADtB,aAAa,CAAC,aAAa,CAAC;GAChB,UAAU,CAsKtB","sourcesContent":["import { LitElement, css, html } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { tokens } from \"./tokens.js\";\n\n/**\n * Collapsible navigation sidebar for the `app-shell` sidebar slot. It is\n * deliberately presentational and router-agnostic: the consumer supplies the\n * nav items as plain `<a>`/`<button>` elements (each an icon followed by a\n * label), optional `<p>` group headings, and marks the active item with\n * `aria-current=\"page\"`. The sidebar styles them, tracks hover/active/focus,\n * and — in collapsed \"rail\" mode — centers the icons and hides the labels.\n *\n * Rail mode is driven by the `collapsed` attribute (the parent `app-shell`\n * toggles it). Labels are hidden by the inherited `--app-sidebar-label` custom\n * property, so each label element should read it, e.g.\n * `<span style=\"display: var(--app-sidebar-label, inline)\">Reports</span>`;\n * keep an `aria-label` on the item so its accessible name survives collapse.\n * The sidebar fills the width and height of its container — `app-shell` owns\n * the actual rail/expanded width.\n *\n * @element app-sidebar\n * @slot - Navigation items: `<a>`/`<button>` links and optional `<p>` headings.\n * @slot header - Optional brand pinned to the top: a small logo plus a name.\n * Wrap the name in a `--app-sidebar-label` element so rail mode shows only the\n * logo, centered in line with the nav icons.\n * @slot footer - Optional account/settings area pinned to the bottom.\n */\n@customElement(\"app-sidebar\")\nexport class AppSidebar extends LitElement {\n /** Collapses the sidebar to an icon rail (centers icons, hides labels). */\n @property({ type: Boolean, reflect: true }) collapsed = false;\n\n /** Whether the header slot has assigned content. */\n @state() private _hasHeader = false;\n /** Whether the footer slot has assigned content. */\n @state() private _hasFooter = false;\n\n static override styles = [\n tokens,\n css`\n :host {\n display: block;\n height: 100%;\n --app-sidebar-label: inline;\n }\n :host([collapsed]) {\n --app-sidebar-label: none;\n }\n .sidebar {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n background: var(--ui-surface, #ffffff);\n color: var(--ui-text, #0f172a);\n font-family: var(\n --ui-font,\n ui-sans-serif,\n system-ui,\n sans-serif,\n \"Apple Color Emoji\",\n \"Segoe UI Emoji\",\n \"Segoe UI Symbol\",\n \"Noto Color Emoji\"\n );\n }\n .brand {\n flex: 0 0 auto;\n display: flex;\n align-items: center;\n padding: 0.75rem;\n border-bottom: 1px solid var(--ui-border, #e2e8f0);\n }\n .foot {\n flex: 0 0 auto;\n padding: 0.75rem;\n border-top: 1px solid var(--ui-border, #e2e8f0);\n }\n .brand.empty,\n .foot.empty {\n display: none;\n }\n .nav {\n flex: 1 1 auto;\n min-height: 0;\n overflow-y: auto;\n display: flex;\n flex-direction: column;\n gap: 0.25rem;\n padding: 0.5rem;\n }\n ::slotted(a),\n ::slotted(button) {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n width: 100%;\n box-sizing: border-box;\n padding: 0.5rem;\n border: none;\n border-radius: var(--ui-radius-sm, 0.25rem);\n background: none;\n color: var(--ui-text, #0f172a);\n text-align: left;\n text-decoration: none;\n white-space: nowrap;\n cursor: pointer;\n font-size: var(--ui-font-size-sm, 0.75rem);\n font-weight: var(--ui-font-weight-medium, 500);\n line-height: var(--ui-line-height-tight, 1.25);\n font-family: var(\n --ui-font,\n ui-sans-serif,\n system-ui,\n sans-serif,\n \"Apple Color Emoji\",\n \"Segoe UI Emoji\",\n \"Segoe UI Symbol\",\n \"Noto Color Emoji\"\n );\n }\n ::slotted(a:hover),\n ::slotted(button:hover) {\n background: var(--ui-surface-muted, #f8fafc);\n }\n ::slotted(a[aria-current]),\n ::slotted(button[aria-current]) {\n background: var(--ui-surface-muted, #f8fafc);\n color: var(--ui-primary, #4f46e5);\n font-weight: var(--ui-font-weight-semibold, 600);\n }\n ::slotted(a:focus-visible),\n ::slotted(button:focus-visible) {\n outline: none;\n box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));\n }\n ::slotted(p) {\n margin: 0;\n padding: 0.5rem 0.5rem 0.25rem;\n font-size: var(--ui-font-size-xs, 0.6875rem);\n font-weight: var(--ui-font-weight-semibold, 600);\n line-height: var(--ui-line-height-tight, 1.25);\n text-transform: uppercase;\n letter-spacing: var(--ui-tracking-wide, 0.04em);\n color: var(--ui-text-muted, #64748b);\n }\n :host([collapsed]) ::slotted(a),\n :host([collapsed]) ::slotted(button) {\n justify-content: center;\n }\n :host([collapsed]) ::slotted(p) {\n display: none;\n }\n :host([collapsed]) .brand {\n justify-content: center;\n padding-left: 0.5rem;\n padding-right: 0.5rem;\n }\n @media (forced-colors: active) {\n ::slotted(a:focus-visible),\n ::slotted(button:focus-visible) {\n outline: 2px solid CanvasText;\n outline-offset: 2px;\n box-shadow: none;\n }\n }\n `,\n ];\n\n /** Collapses the header row when nothing is slotted into it. */\n private _onHeaderSlotChange(event: Event): void {\n this._hasHeader = (event.target as HTMLSlotElement).assignedNodes({ flatten: true }).length > 0;\n }\n\n /** Collapses the footer row when nothing is slotted into it. */\n private _onFooterSlotChange(event: Event): void {\n this._hasFooter = (event.target as HTMLSlotElement).assignedNodes({ flatten: true }).length > 0;\n }\n\n override render() {\n return html`\n <div class=\"sidebar\">\n <div class=\"brand ${this._hasHeader ? \"\" : \"empty\"}\">\n <slot name=\"header\" @slotchange=${this._onHeaderSlotChange}></slot>\n </div>\n <nav class=\"nav\" aria-label=\"Sidebar\">\n <slot></slot>\n </nav>\n <div class=\"foot ${this._hasFooter ? \"\" : \"empty\"}\">\n <slot name=\"footer\" @slotchange=${this._onFooterSlotChange}></slot>\n </div>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"app-sidebar\": AppSidebar;\n }\n}\n"]}
@@ -0,0 +1,27 @@
1
+ import { LitElement } from "lit";
2
+ /**
3
+ * Form footer button bar with a fixed action order for internal apps: the
4
+ * primary (submit) button is always rightmost, the secondary (cancel) button
5
+ * sits to its immediate left, and an optional tertiary/destructive action is
6
+ * pinned to the far left. The order is enforced by the component regardless of
7
+ * the source order the buttons are authored in, so every form in a product
8
+ * reads the same way.
9
+ *
10
+ * Purely presentational: it lays out whatever controls (usually `ui-button`)
11
+ * are slotted and never intercepts their clicks, `type="submit"`, or events.
12
+ *
13
+ * @element form-actions
14
+ * @slot start - Optional far-left action (e.g. a destructive "Delete").
15
+ * @slot secondary - The secondary/cancel action, placed left of primary.
16
+ * @slot primary - The primary/submit action, always rightmost.
17
+ */
18
+ export declare class FormActions extends LitElement {
19
+ static styles: import("lit").CSSResult[];
20
+ render(): import("lit-html").TemplateResult<1>;
21
+ }
22
+ declare global {
23
+ interface HTMLElementTagNameMap {
24
+ "form-actions": FormActions;
25
+ }
26
+ }
27
+ //# sourceMappingURL=form-actions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form-actions.d.ts","sourceRoot":"","sources":["../src/form-actions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAI5C;;;;;;;;;;;;;;;GAeG;AACH,qBACa,WAAY,SAAQ,UAAU;IACzC,OAAgB,MAAM,4BAuBpB;IAEO,MAAM,yCAQd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,cAAc,EAAE,WAAW,CAAC;KAC7B;CACF"}
@@ -0,0 +1,65 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { LitElement, css, html } from "lit";
8
+ import { customElement } from "lit/decorators.js";
9
+ import { tokens } from "./tokens.js";
10
+ /**
11
+ * Form footer button bar with a fixed action order for internal apps: the
12
+ * primary (submit) button is always rightmost, the secondary (cancel) button
13
+ * sits to its immediate left, and an optional tertiary/destructive action is
14
+ * pinned to the far left. The order is enforced by the component regardless of
15
+ * the source order the buttons are authored in, so every form in a product
16
+ * reads the same way.
17
+ *
18
+ * Purely presentational: it lays out whatever controls (usually `ui-button`)
19
+ * are slotted and never intercepts their clicks, `type="submit"`, or events.
20
+ *
21
+ * @element form-actions
22
+ * @slot start - Optional far-left action (e.g. a destructive "Delete").
23
+ * @slot secondary - The secondary/cancel action, placed left of primary.
24
+ * @slot primary - The primary/submit action, always rightmost.
25
+ */
26
+ let FormActions = class FormActions extends LitElement {
27
+ static { this.styles = [
28
+ tokens,
29
+ css `
30
+ :host {
31
+ display: block;
32
+ }
33
+ .actions {
34
+ display: flex;
35
+ flex-wrap: wrap;
36
+ align-items: center;
37
+ justify-content: flex-end;
38
+ gap: 0.5rem;
39
+ }
40
+ .start {
41
+ display: flex;
42
+ align-items: center;
43
+ gap: 0.5rem;
44
+ margin-right: auto;
45
+ }
46
+ ::slotted(*) {
47
+ margin: 0;
48
+ }
49
+ `,
50
+ ]; }
51
+ render() {
52
+ return html `
53
+ <div class="actions">
54
+ <span class="start"><slot name="start"></slot></span>
55
+ <slot name="secondary"></slot>
56
+ <slot name="primary"></slot>
57
+ </div>
58
+ `;
59
+ }
60
+ };
61
+ FormActions = __decorate([
62
+ customElement("form-actions")
63
+ ], FormActions);
64
+ export { FormActions };
65
+ //# sourceMappingURL=form-actions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form-actions.js","sourceRoot":"","sources":["../src/form-actions.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC;;;;;;;;;;;;;;;GAeG;AAEI,IAAM,WAAW,GAAjB,MAAM,WAAY,SAAQ,UAAU;aACzB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;KAoBF;KACF,AAvBqB,CAuBpB;IAEO,MAAM;QACb,OAAO,IAAI,CAAA;;;;;;KAMV,CAAC;IACJ,CAAC;CACF,CAAA;AAnCY,WAAW;IADvB,aAAa,CAAC,cAAc,CAAC;GACjB,WAAW,CAmCvB","sourcesContent":["import { LitElement, css, html } from \"lit\";\nimport { customElement } from \"lit/decorators.js\";\nimport { tokens } from \"./tokens.js\";\n\n/**\n * Form footer button bar with a fixed action order for internal apps: the\n * primary (submit) button is always rightmost, the secondary (cancel) button\n * sits to its immediate left, and an optional tertiary/destructive action is\n * pinned to the far left. The order is enforced by the component regardless of\n * the source order the buttons are authored in, so every form in a product\n * reads the same way.\n *\n * Purely presentational: it lays out whatever controls (usually `ui-button`)\n * are slotted and never intercepts their clicks, `type=\"submit\"`, or events.\n *\n * @element form-actions\n * @slot start - Optional far-left action (e.g. a destructive \"Delete\").\n * @slot secondary - The secondary/cancel action, placed left of primary.\n * @slot primary - The primary/submit action, always rightmost.\n */\n@customElement(\"form-actions\")\nexport class FormActions extends LitElement {\n static override styles = [\n tokens,\n css`\n :host {\n display: block;\n }\n .actions {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: flex-end;\n gap: 0.5rem;\n }\n .start {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n margin-right: auto;\n }\n ::slotted(*) {\n margin: 0;\n }\n `,\n ];\n\n override render() {\n return html`\n <div class=\"actions\">\n <span class=\"start\"><slot name=\"start\"></slot></span>\n <slot name=\"secondary\"></slot>\n <slot name=\"primary\"></slot>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"form-actions\": FormActions;\n }\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -36,6 +36,14 @@ export { DropdownButton, type DropdownOption } from "./dropdown-button.js";
36
36
  export { IconButton } from "./icon-button.js";
37
37
  export { KbdHint, type KbdPlatform } from "./kbd-hint.js";
38
38
  export { FrameBox } from "./frame-box.js";
39
+ export { AppShell, type SidebarToggleDetail } from "./app-shell.js";
40
+ export { AppSidebar } from "./app-sidebar.js";
41
+ export { ActionBar } from "./action-bar.js";
42
+ export { PageHeader } from "./page-header.js";
43
+ export { PaginationNav, type PageChangeDetail } from "./pagination-nav.js";
44
+ export { FormActions } from "./form-actions.js";
45
+ export { TimelineContainer } from "./timeline-container.js";
46
+ export { TimelineEntry } from "./timeline-entry.js";
39
47
  export { CalendarEntry } from "./calendar-entry.js";
40
48
  export { CalendarMonth } from "./calendar-month.js";
41
49
  export { CalendarYear } from "./calendar-year.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EACL,YAAY,EACZ,KAAK,wBAAwB,EAC7B,KAAK,qBAAqB,EAC1B,KAAK,6BAA6B,GACnC,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,WAAW,EACX,UAAU,EACV,KAAK,YAAY,GAClB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,KAAK,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAC9E,OAAO,EAAE,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACpF,OAAO,EAAE,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC/E,OAAO,EAAE,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3E,OAAO,EACL,mBAAmB,EACnB,KAAK,iBAAiB,GACvB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,iBAAiB,EACjB,KAAK,kBAAkB,GACxB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACpE,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACpE,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EACL,WAAW,EACX,KAAK,eAAe,EACpB,KAAK,kBAAkB,GACxB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EACL,WAAW,EACX,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,GACxB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,SAAS,EAAE,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3E,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EACL,WAAW,EACX,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,GAC1B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1E,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnE,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -36,6 +36,14 @@ export { DropdownButton } from "./dropdown-button.js";
36
36
  export { IconButton } from "./icon-button.js";
37
37
  export { KbdHint } from "./kbd-hint.js";
38
38
  export { FrameBox } from "./frame-box.js";
39
+ export { AppShell } from "./app-shell.js";
40
+ export { AppSidebar } from "./app-sidebar.js";
41
+ export { ActionBar } from "./action-bar.js";
42
+ export { PageHeader } from "./page-header.js";
43
+ export { PaginationNav } from "./pagination-nav.js";
44
+ export { FormActions } from "./form-actions.js";
45
+ export { TimelineContainer } from "./timeline-container.js";
46
+ export { TimelineEntry } from "./timeline-entry.js";
39
47
  export { CalendarEntry } from "./calendar-entry.js";
40
48
  export { CalendarMonth } from "./calendar-month.js";
41
49
  export { CalendarYear } from "./calendar-year.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["export { AnimateConfetti } from \"./animate-confetti.js\";\nexport { GalleryItem } from \"./gallery-item.js\";\nexport { GalleryItemVariant } from \"./gallery-item-variant.js\";\nexport {\n PhotoGallery,\n type PhotoGalleryChangeReason,\n type PhotoGalleryObjectFit,\n type PhotoGallerySlideChangeDetail,\n} from \"./photo-gallery.js\";\nexport { RevealButton } from \"./reveal-button.js\";\nexport { RomanNumeral } from \"./roman-numeral.js\";\nexport { ConfirmDialog } from \"./confirm-dialog.js\";\nexport {\n ToastNotification,\n notifySuccess,\n notifyError,\n notifyInfo,\n type ToastVariant,\n} from \"./toast-notification.js\";\nexport { SlidePanel } from \"./slide-panel.js\";\nexport { CopyLinkButton } from \"./copy-link-button.js\";\nexport { RelativeTime } from \"./relative-time.js\";\nexport { DistanceValue } from \"./distance-value.js\";\nexport { PriceHistoryChart, type PricePoint } from \"./price-history-chart.js\";\nexport { DistributionChart, type DistributionValue } from \"./distribution-chart.js\";\nexport { PercentBarChart, type PercentBarGroup } from \"./percent-bar-chart.js\";\nexport { WeightBarChart, type WeightBarItem } from \"./weight-bar-chart.js\";\nexport {\n AddressAutocomplete,\n type AddressSuggestion,\n} from \"./address-autocomplete.js\";\nexport {\n AutocompleteInput,\n type AutocompleteOption,\n} from \"./autocomplete-input.js\";\nexport { UserAvatar } from \"./user-avatar.js\";\nexport { MapPin } from \"./map-pin.js\";\nexport { MapCircle } from \"./map-circle.js\";\nexport { RadioCards, type RadioCardOption } from \"./radio-cards.js\";\nexport { RadioPills, type RadioPillOption } from \"./radio-pills.js\";\nexport { UiButton, type ButtonVariant } from \"./ui-button.js\";\nexport { StatMeter } from \"./stat-meter.js\";\nexport { StatusPill, type StatusPillColor } from \"./status-pill.js\";\nexport { EditableText } from \"./editable-text.js\";\nexport { LiveTimer } from \"./live-timer.js\";\nexport {\n ChatMessage,\n type ChatMessageRole,\n type ChatMessageVariant,\n} from \"./chat-message.js\";\nexport { FormSelect, type SelectOption } from \"./form-select.js\";\nexport {\n MultiSelect,\n type MultiSelectOption,\n type MultiSelectVariant,\n} from \"./multi-select.js\";\nexport { DataTable, type DataTableColumn } from \"./data-table.js\";\nexport { TileGrid } from \"./tile-grid.js\";\nexport { PopoverPanel } from \"./popover-panel.js\";\nexport { DropdownButton, type DropdownOption } from \"./dropdown-button.js\";\nexport { IconButton } from \"./icon-button.js\";\nexport { KbdHint, type KbdPlatform } from \"./kbd-hint.js\";\nexport { FrameBox } from \"./frame-box.js\";\nexport { CalendarEntry } from \"./calendar-entry.js\";\nexport { CalendarMonth } from \"./calendar-month.js\";\nexport { CalendarYear } from \"./calendar-year.js\";\nexport {\n KanbanBoard,\n type KanbanCardData,\n type KanbanColumnData,\n type KanbanCardMoveDetail,\n type KanbanCardOpenDetail,\n} from \"./kanban-board.js\";\nexport { KanbanColumn } from \"./kanban-column.js\";\nexport { KanbanCard } from \"./kanban-card.js\";\nexport { formatDuration, type DurationFormat } from \"./utils/duration.js\";\nexport { tokens, tokenValues, darkTokenValues } from \"./tokens.js\";\nexport * from \"./icons.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["export { AnimateConfetti } from \"./animate-confetti.js\";\nexport { GalleryItem } from \"./gallery-item.js\";\nexport { GalleryItemVariant } from \"./gallery-item-variant.js\";\nexport {\n PhotoGallery,\n type PhotoGalleryChangeReason,\n type PhotoGalleryObjectFit,\n type PhotoGallerySlideChangeDetail,\n} from \"./photo-gallery.js\";\nexport { RevealButton } from \"./reveal-button.js\";\nexport { RomanNumeral } from \"./roman-numeral.js\";\nexport { ConfirmDialog } from \"./confirm-dialog.js\";\nexport {\n ToastNotification,\n notifySuccess,\n notifyError,\n notifyInfo,\n type ToastVariant,\n} from \"./toast-notification.js\";\nexport { SlidePanel } from \"./slide-panel.js\";\nexport { CopyLinkButton } from \"./copy-link-button.js\";\nexport { RelativeTime } from \"./relative-time.js\";\nexport { DistanceValue } from \"./distance-value.js\";\nexport { PriceHistoryChart, type PricePoint } from \"./price-history-chart.js\";\nexport { DistributionChart, type DistributionValue } from \"./distribution-chart.js\";\nexport { PercentBarChart, type PercentBarGroup } from \"./percent-bar-chart.js\";\nexport { WeightBarChart, type WeightBarItem } from \"./weight-bar-chart.js\";\nexport {\n AddressAutocomplete,\n type AddressSuggestion,\n} from \"./address-autocomplete.js\";\nexport {\n AutocompleteInput,\n type AutocompleteOption,\n} from \"./autocomplete-input.js\";\nexport { UserAvatar } from \"./user-avatar.js\";\nexport { MapPin } from \"./map-pin.js\";\nexport { MapCircle } from \"./map-circle.js\";\nexport { RadioCards, type RadioCardOption } from \"./radio-cards.js\";\nexport { RadioPills, type RadioPillOption } from \"./radio-pills.js\";\nexport { UiButton, type ButtonVariant } from \"./ui-button.js\";\nexport { StatMeter } from \"./stat-meter.js\";\nexport { StatusPill, type StatusPillColor } from \"./status-pill.js\";\nexport { EditableText } from \"./editable-text.js\";\nexport { LiveTimer } from \"./live-timer.js\";\nexport {\n ChatMessage,\n type ChatMessageRole,\n type ChatMessageVariant,\n} from \"./chat-message.js\";\nexport { FormSelect, type SelectOption } from \"./form-select.js\";\nexport {\n MultiSelect,\n type MultiSelectOption,\n type MultiSelectVariant,\n} from \"./multi-select.js\";\nexport { DataTable, type DataTableColumn } from \"./data-table.js\";\nexport { TileGrid } from \"./tile-grid.js\";\nexport { PopoverPanel } from \"./popover-panel.js\";\nexport { DropdownButton, type DropdownOption } from \"./dropdown-button.js\";\nexport { IconButton } from \"./icon-button.js\";\nexport { KbdHint, type KbdPlatform } from \"./kbd-hint.js\";\nexport { FrameBox } from \"./frame-box.js\";\nexport { AppShell, type SidebarToggleDetail } from \"./app-shell.js\";\nexport { AppSidebar } from \"./app-sidebar.js\";\nexport { ActionBar } from \"./action-bar.js\";\nexport { PageHeader } from \"./page-header.js\";\nexport { PaginationNav, type PageChangeDetail } from \"./pagination-nav.js\";\nexport { FormActions } from \"./form-actions.js\";\nexport { TimelineContainer } from \"./timeline-container.js\";\nexport { TimelineEntry } from \"./timeline-entry.js\";\nexport { CalendarEntry } from \"./calendar-entry.js\";\nexport { CalendarMonth } from \"./calendar-month.js\";\nexport { CalendarYear } from \"./calendar-year.js\";\nexport {\n KanbanBoard,\n type KanbanCardData,\n type KanbanColumnData,\n type KanbanCardMoveDetail,\n type KanbanCardOpenDetail,\n} from \"./kanban-board.js\";\nexport { KanbanColumn } from \"./kanban-column.js\";\nexport { KanbanCard } from \"./kanban-card.js\";\nexport { formatDuration, type DurationFormat } from \"./utils/duration.js\";\nexport { tokens, tokenValues, darkTokenValues } from \"./tokens.js\";\nexport * from \"./icons.js\";\n"]}
@@ -3,15 +3,18 @@
3
3
  * Stdio MCP server exposing the `@f-ewald/components` catalog to AI coding
4
4
  * assistants (Claude Code, etc.) working in a consuming project.
5
5
  *
6
- * Two tools, backed by the same `custom-elements.json` and `docs/*.md` this
7
- * package already generates and ships via `npm run docs`:
6
+ * Tools, backed by the same `custom-elements.json` and `docs/*.md` this
7
+ * package already generates and ships via `npm run docs`, plus the authored
8
+ * `docs/layouts/*.md` recipes:
8
9
  * - `list_components`: every tag name + one-line description.
9
10
  * - `get_component_docs`: the full generated Markdown doc for one tag.
11
+ * - `list_layouts`: every dashboard page template + one-line summary.
12
+ * - `get_layout`: the full recipe for one page template.
10
13
  *
11
- * No new data source — this is just a different transport over data that
12
- * already exists. See `docs/mcp-evaluation.md` for the design rationale.
14
+ * No new data source — this is just a different transport over docs that
15
+ * already exist. See `docs/mcp-evaluation.md` for the design rationale.
13
16
  */
14
- import { readFile } from "node:fs/promises";
17
+ import { readdir, readFile } from "node:fs/promises";
15
18
  import { fileURLToPath } from "node:url";
16
19
  import path from "node:path";
17
20
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -23,6 +26,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
23
26
  // checkout or from an installed node_modules/@f-ewald/components.
24
27
  const packageRoot = path.join(__dirname, "..");
25
28
  const docsDir = path.join(packageRoot, "docs");
29
+ const layoutsDir = path.join(docsDir, "layouts");
26
30
  /** Reads the package's own version out of package.json, for server metadata. */
27
31
  async function readPackageVersion() {
28
32
  const text = await readFile(path.join(packageRoot, "package.json"), "utf8");
@@ -44,6 +48,45 @@ async function listComponents() {
44
48
  components.sort((a, b) => a.tag.localeCompare(b.tag));
45
49
  return components;
46
50
  }
51
+ /**
52
+ * Extracts {name, title, one-line summary} for every `docs/layouts/*.md`
53
+ * recipe, sorted by name. `name` is the filename without `.md`; `summary` is
54
+ * the recipe's first prose paragraph.
55
+ */
56
+ async function listLayouts() {
57
+ let files;
58
+ try {
59
+ files = await readdir(layoutsDir);
60
+ }
61
+ catch {
62
+ return [];
63
+ }
64
+ const layouts = [];
65
+ for (const file of files) {
66
+ if (!file.endsWith(".md"))
67
+ continue;
68
+ const name = file.slice(0, -3);
69
+ const lines = (await readFile(path.join(layoutsDir, file), "utf8")).split("\n");
70
+ const title = lines.find((line) => line.startsWith("# "))?.slice(2).trim() ?? name;
71
+ let seenTitle = false;
72
+ let summary = "";
73
+ for (const line of lines) {
74
+ const text = line.trim();
75
+ if (!seenTitle) {
76
+ if (text.startsWith("# "))
77
+ seenTitle = true;
78
+ continue;
79
+ }
80
+ if (text === "" || text.startsWith("#"))
81
+ continue;
82
+ summary = text;
83
+ break;
84
+ }
85
+ layouts.push({ name, title, summary });
86
+ }
87
+ layouts.sort((a, b) => a.name.localeCompare(b.name));
88
+ return layouts;
89
+ }
47
90
  const server = new McpServer({
48
91
  name: "f-ewald-components",
49
92
  version: await readPackageVersion(),
@@ -77,6 +120,40 @@ server.registerTool("get_component_docs", {
77
120
  };
78
121
  }
79
122
  });
123
+ server.registerTool("list_layouts", {
124
+ title: "List layouts",
125
+ description: "Lists the dashboard page templates (layout recipes) in " +
126
+ "@f-ewald/components — list-only, list+detail, detail-only, and form — " +
127
+ "with a one-line summary each. Call get_layout(name) for the full recipe " +
128
+ "(which components fill which app-shell slots, example markup, and notes). " +
129
+ "Use this to learn the preferred internal-dashboard layout before " +
130
+ "composing app-shell, app-sidebar, action-bar, pagination-nav, " +
131
+ "form-actions, and page-header.",
132
+ }, async () => ({
133
+ content: [{ type: "text", text: JSON.stringify(await listLayouts(), null, 2) }],
134
+ }));
135
+ server.registerTool("get_layout", {
136
+ title: "Get layout recipe",
137
+ description: "Returns the full Markdown recipe for one dashboard page template: which " +
138
+ "components go in which app-shell slots, copy-paste markup, and layout " +
139
+ "notes. Use list_layouts first to find valid names.",
140
+ inputSchema: { name: z.string().describe('Layout name, e.g. "list-detail".') },
141
+ }, async ({ name }) => {
142
+ if (/^[a-z0-9-]+$/.test(name)) {
143
+ try {
144
+ const text = await readFile(path.join(layoutsDir, `${name}.md`), "utf8");
145
+ return { content: [{ type: "text", text }] };
146
+ }
147
+ catch {
148
+ // Fall through to the not-found response with valid names.
149
+ }
150
+ }
151
+ const validNames = (await listLayouts()).map((layout) => layout.name).join(", ");
152
+ return {
153
+ content: [{ type: "text", text: `No layout named "${name}". Valid layouts: ${validNames}` }],
154
+ isError: true,
155
+ };
156
+ });
80
157
  const transport = new StdioServerTransport();
81
158
  await server.connect(transport);
82
159
  //# sourceMappingURL=mcp-server.js.map