@f-ewald/components 1.2.0 → 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 (63) hide show
  1. package/README.md +15 -2
  2. package/custom-elements.json +1020 -76
  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/icons.d.ts +1 -0
  20. package/dist/icons.d.ts.map +1 -1
  21. package/dist/icons.js +1 -0
  22. package/dist/icons.js.map +1 -1
  23. package/dist/index.d.ts +8 -0
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +8 -0
  26. package/dist/index.js.map +1 -1
  27. package/dist/mcp-server.js +82 -5
  28. package/dist/mcp-server.js.map +1 -1
  29. package/dist/page-header.d.ts +27 -0
  30. package/dist/page-header.d.ts.map +1 -0
  31. package/dist/page-header.js +106 -0
  32. package/dist/page-header.js.map +1 -0
  33. package/dist/pagination-nav.d.ts +34 -0
  34. package/dist/pagination-nav.d.ts.map +1 -0
  35. package/dist/pagination-nav.js +144 -0
  36. package/dist/pagination-nav.js.map +1 -0
  37. package/dist/popover-panel.js +1 -1
  38. package/dist/popover-panel.js.map +1 -1
  39. package/dist/timeline-container.d.ts +22 -0
  40. package/dist/timeline-container.d.ts.map +1 -0
  41. package/dist/timeline-container.js +42 -0
  42. package/dist/timeline-container.js.map +1 -0
  43. package/dist/timeline-entry.d.ts +31 -0
  44. package/dist/timeline-entry.d.ts.map +1 -0
  45. package/dist/timeline-entry.js +170 -0
  46. package/dist/timeline-entry.js.map +1 -0
  47. package/docs/action-bar.md +41 -0
  48. package/docs/app-shell.md +85 -0
  49. package/docs/app-sidebar.md +69 -0
  50. package/docs/design-language.md +74 -1
  51. package/docs/form-actions.md +43 -0
  52. package/docs/layouts/detail-only.md +52 -0
  53. package/docs/layouts/form-page.md +52 -0
  54. package/docs/layouts/list-detail.md +52 -0
  55. package/docs/layouts/list-only.md +50 -0
  56. package/docs/layouts/record-detail.md +88 -0
  57. package/docs/mcp-evaluation.md +21 -0
  58. package/docs/page-header.md +47 -0
  59. package/docs/pagination-nav.md +54 -0
  60. package/docs/timeline-container.md +44 -0
  61. package/docs/timeline-entry.md +52 -0
  62. package/llms.txt +229 -0
  63. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"popover-panel.js","sourceRoot":"","sources":["../src/popover-panel.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, css, html, nothing, type PropertyValues } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { iconX } from \"./icons.js\";\nimport { tokens } from \"./tokens.js\";\nimport {\n activateLayer,\n claimEscape,\n deactivateLayer,\n isTopLayer,\n} from \"./utils/layer-stack.js\";\n\n/**\n * Generic anchored popover shell: a floating card positioned relative to its\n * nearest `position: relative` ancestor (place it next to its trigger button\n * inside such a wrapper), as opposed to `slide-panel`'s fixed screen-edge\n * drawer. Closes on outside click or Escape. Header chrome and close button\n * match `slide-panel`'s API (`heading`, `panel-close`) so either can be\n * swapped in for the other with no consumer-side changes beyond the wrapper.\n *\n * Set `centered` to render as a screen-centered modal with a translucent\n * backdrop instead of the default anchored placement.\n *\n * @element popover-panel\n * @fires panel-close - User clicked the close (✕) button, pressed Escape, or clicked outside/the backdrop.\n * @slot title - Overrides the plain `heading` text with custom markup.\n * @slot actions - Extra header controls (e.g. an icon+label link) rendered between the title and the close button.\n * @slot - Popover body content.\n */\n@customElement(\"popover-panel\")\nexport class PopoverPanel extends LitElement {\n /** Whether the popover is currently visible. */\n @property({ type: Boolean, reflect: true }) open = false;\n /** Title text shown in the popover header. */\n @property() heading = \"\";\n /** Render as a screen-centered modal with a backdrop instead of anchored placement. */\n @property({ type: Boolean, reflect: true }) centered = false;\n\n static override styles = [\n tokens,\n css`\n :host {\n display: none;\n position: absolute;\n top: calc(100% + 0.5rem);\n right: 0;\n z-index: var(--component-layer-z, 20);\n }\n :host([open]) {\n display: block;\n }\n :host([centered][open]) {\n position: fixed;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--ui-overlay, rgb(15 23 42 / 0.45));\n z-index: var(--component-layer-z, 30);\n }\n :host([centered]) .panel {\n width: 25rem;\n }\n .panel {\n width: 20rem;\n max-width: calc(100vw - 2rem);\n background: var(--ui-surface, #ffffff);\n border: 1px solid var(--ui-border, #e2e8f0);\n border-radius: var(--ui-radius, 0.5rem);\n box-shadow: var(--ui-shadow-lg, 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1));\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0.75rem;\n border-bottom: 1px solid var(--ui-border, #e2e8f0);\n flex: 0 0 auto;\n }\n .panel-title {\n font-weight: var(--ui-font-weight-semibold, 600);\n font-size: var(--ui-font-size-lg, 1rem);\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 color: var(--ui-text, #0f172a);\n flex: 1 1 auto;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n padding-right: 0.5rem;\n }\n .panel-actions {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n flex: 0 0 auto;\n margin-right: 0.25rem;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n width: 2rem;\n height: 2rem;\n padding: 0;\n color: var(--ui-text-muted, #64748b);\n border-radius: var(--ui-radius-sm, 0.25rem);\n flex: 0 0 auto;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n }\n .close-btn:hover {\n background: var(--ui-surface-muted, #f8fafc);\n }\n .close-btn:focus-visible {\n outline: none;\n box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));\n }\n @media (forced-colors: active) {\n .close-btn:focus-visible {\n outline: 2px solid CanvasText;\n outline-offset: 2px;\n box-shadow: none;\n }\n }\n `,\n ];\n\n #modalActive = false;\n #previousFocus: HTMLElement | null = null;\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n window.removeEventListener(\"mousedown\", this.#onWindowMousedown, true);\n window.removeEventListener(\"keydown\", this.#onWindowKeydown);\n document.removeEventListener(\"focusin\", this.#onDocumentFocusIn, true);\n deactivateLayer(this);\n if (this.#previousFocus?.isConnected) this.#previousFocus.focus();\n this.#previousFocus = null;\n this.#modalActive = false;\n this.open = false;\n }\n\n protected override updated(changed: PropertyValues): void {\n if (changed.has(\"open\")) {\n if (this.open) {\n activateLayer(this);\n window.addEventListener(\"mousedown\", this.#onWindowMousedown, true);\n window.addEventListener(\"keydown\", this.#onWindowKeydown);\n } else {\n deactivateLayer(this);\n window.removeEventListener(\"mousedown\", this.#onWindowMousedown, true);\n window.removeEventListener(\"keydown\", this.#onWindowKeydown);\n }\n }\n\n const shouldBeModal = this.open && this.centered;\n if (shouldBeModal && !this.#modalActive) {\n this.#modalActive = true;\n this.#previousFocus = document.activeElement as HTMLElement | null;\n document.addEventListener(\"focusin\", this.#onDocumentFocusIn, true);\n this.#focusInitial();\n } else if (!shouldBeModal && this.#modalActive) {\n this.#modalActive = false;\n document.removeEventListener(\"focusin\", this.#onDocumentFocusIn, true);\n if (this.#previousFocus?.isConnected) this.#previousFocus.focus();\n this.#previousFocus = null;\n }\n }\n\n #onWindowMousedown = (e: MouseEvent): void => {\n if (!isTopLayer(this)) return;\n // In centered mode the host itself renders the backdrop, so a mousedown\n // landing directly on the host (not on .panel or its content) is a\n // backdrop click and must close like an outside click would.\n if (this.centered && e.target === this) {\n this.#close();\n return;\n }\n if (!e.composedPath().includes(this)) this.#close();\n };\n\n #onWindowKeydown = (e: KeyboardEvent): void => {\n if (!isTopLayer(this)) return;\n if (claimEscape(this, e)) {\n this.#close();\n return;\n }\n if (!this.centered || e.key !== \"Tab\") return;\n\n const focusable = this.#getFocusableElements();\n if (focusable.length === 0) {\n e.preventDefault();\n this.#focusInitial();\n return;\n }\n const active = (this.shadowRoot?.activeElement ?? document.activeElement) as HTMLElement | null;\n const index = focusable.indexOf(active as HTMLElement);\n if (e.shiftKey && index <= 0) {\n e.preventDefault();\n focusable.at(-1)?.focus();\n } else if (!e.shiftKey && index === focusable.length - 1) {\n e.preventDefault();\n focusable[0]?.focus();\n }\n };\n\n #onDocumentFocusIn = (event: FocusEvent): void => {\n if (!this.#modalActive || !isTopLayer(this) || event.composedPath().includes(this)) return;\n this.#focusInitial();\n };\n\n #getFocusableElements(): HTMLElement[] {\n const selector =\n \"a[href], button:not(:disabled), input:not(:disabled), select:not(:disabled), textarea:not(:disabled), [tabindex]:not([tabindex='-1'])\";\n const focusableInSlot = (slotSelector: string): HTMLElement[] => {\n const slot = this.shadowRoot?.querySelector<HTMLSlotElement>(slotSelector);\n return (slot?.assignedElements({ flatten: true }) ?? []).flatMap((element) => [\n ...(element instanceof HTMLElement && element.matches(selector) ? [element] : []),\n ...Array.from(element.querySelectorAll<HTMLElement>(selector)),\n ]);\n };\n const close = this.shadowRoot?.querySelector<HTMLElement>(\".close-btn\");\n return [\n ...focusableInSlot('slot[name=\"title\"]'),\n ...focusableInSlot('slot[name=\"actions\"]'),\n ...(close ? [close] : []),\n ...focusableInSlot(\"slot:not([name])\"),\n ].filter((element) => !element.hasAttribute(\"hidden\"));\n }\n\n #focusInitial(): void {\n const autofocus = this.querySelector<HTMLElement>(\"[autofocus]\");\n const close = this.shadowRoot?.querySelector<HTMLElement>(\".close-btn\");\n const panel = this.shadowRoot?.querySelector<HTMLElement>(\".panel\");\n (autofocus ?? close ?? this.#getFocusableElements()[0] ?? panel)?.focus();\n }\n\n #close(): void {\n this.dispatchEvent(new CustomEvent(\"panel-close\", { bubbles: true, composed: true }));\n }\n\n override render() {\n const explicitLabel = this.getAttribute(\"aria-label\");\n return html`\n <div\n class=\"panel\"\n role=\"dialog\"\n aria-modal=${this.centered ? \"true\" : nothing}\n aria-label=${explicitLabel ?? nothing}\n aria-labelledby=${explicitLabel ? nothing : \"popover-title\"}\n tabindex=\"-1\"\n >\n <div class=\"panel-header\">\n <span id=\"popover-title\" class=\"panel-title\">\n <slot name=\"title\">${this.heading || (this.centered ? \"Dialog\" : \"Popover\")}</slot>\n </span>\n <span class=\"panel-actions\"><slot name=\"actions\"></slot></span>\n <button class=\"close-btn\" aria-label=\"Close popover\" @click=${() => this.#close()}>\n <span aria-hidden=\"true\">${iconX(18)}</span>\n </button>\n </div>\n <slot></slot>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"popover-panel\": PopoverPanel;\n }\n}\n"]}
1
+ {"version":3,"file":"popover-panel.js","sourceRoot":"","sources":["../src/popover-panel.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, css, html, nothing, type PropertyValues } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { iconX } from \"./icons.js\";\nimport { tokens } from \"./tokens.js\";\nimport {\n activateLayer,\n claimEscape,\n deactivateLayer,\n isTopLayer,\n} from \"./utils/layer-stack.js\";\n\n/**\n * Generic anchored popover shell: a floating card positioned relative to its\n * nearest `position: relative` ancestor (place it next to its trigger button\n * inside such a wrapper), as opposed to `slide-panel`'s fixed screen-edge\n * drawer. Closes on outside click or Escape. Header chrome and close button\n * match `slide-panel`'s API (`heading`, `panel-close`) so either can be\n * swapped in for the other with no consumer-side changes beyond the wrapper.\n *\n * Set `centered` to render as a screen-centered modal with a translucent\n * backdrop instead of the default anchored placement.\n *\n * @element popover-panel\n * @fires panel-close - User clicked the close (✕) button, pressed Escape, or clicked outside/the backdrop.\n * @slot title - Overrides the plain `heading` text with custom markup.\n * @slot actions - Extra header controls (e.g. an icon+label link) rendered between the title and the close button.\n * @slot - Popover body content.\n */\n@customElement(\"popover-panel\")\nexport class PopoverPanel extends LitElement {\n /** Whether the popover is currently visible. */\n @property({ type: Boolean, reflect: true }) open = false;\n /** Title text shown in the popover header. */\n @property() heading = \"\";\n /** Render as a screen-centered modal with a backdrop instead of anchored placement. */\n @property({ type: Boolean, reflect: true }) centered = false;\n\n static override styles = [\n tokens,\n css`\n :host {\n display: none;\n position: absolute;\n top: calc(100% + 0.5rem);\n right: 0;\n z-index: var(--component-layer-z, 20);\n }\n :host([open]) {\n display: block;\n }\n :host([centered][open]) {\n position: fixed;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--ui-overlay, rgb(15 23 42 / 0.45));\n z-index: var(--component-layer-z, 30);\n }\n :host([centered]) .panel {\n width: 25rem;\n }\n .panel {\n width: 20rem;\n max-width: calc(100vw - 2rem);\n background: var(--ui-surface, #ffffff);\n border: 1px solid var(--ui-border, #e2e8f0);\n border-radius: var(--ui-radius, 0.5rem);\n box-shadow: var(--ui-shadow-lg, 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1));\n display: flex;\n flex-direction: column;\n }\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0.75rem;\n border-bottom: 1px solid var(--ui-border, #e2e8f0);\n border-radius: var(--ui-radius, 0.5rem) var(--ui-radius, 0.5rem) 0 0;\n flex: 0 0 auto;\n }\n .panel-title {\n font-weight: var(--ui-font-weight-semibold, 600);\n font-size: var(--ui-font-size-lg, 1rem);\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 color: var(--ui-text, #0f172a);\n flex: 1 1 auto;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n padding-right: 0.5rem;\n }\n .panel-actions {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n flex: 0 0 auto;\n margin-right: 0.25rem;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n width: 2rem;\n height: 2rem;\n padding: 0;\n color: var(--ui-text-muted, #64748b);\n border-radius: var(--ui-radius-sm, 0.25rem);\n flex: 0 0 auto;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n }\n .close-btn:hover {\n background: var(--ui-surface-muted, #f8fafc);\n }\n .close-btn:focus-visible {\n outline: none;\n box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));\n }\n @media (forced-colors: active) {\n .close-btn:focus-visible {\n outline: 2px solid CanvasText;\n outline-offset: 2px;\n box-shadow: none;\n }\n }\n `,\n ];\n\n #modalActive = false;\n #previousFocus: HTMLElement | null = null;\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n window.removeEventListener(\"mousedown\", this.#onWindowMousedown, true);\n window.removeEventListener(\"keydown\", this.#onWindowKeydown);\n document.removeEventListener(\"focusin\", this.#onDocumentFocusIn, true);\n deactivateLayer(this);\n if (this.#previousFocus?.isConnected) this.#previousFocus.focus();\n this.#previousFocus = null;\n this.#modalActive = false;\n this.open = false;\n }\n\n protected override updated(changed: PropertyValues): void {\n if (changed.has(\"open\")) {\n if (this.open) {\n activateLayer(this);\n window.addEventListener(\"mousedown\", this.#onWindowMousedown, true);\n window.addEventListener(\"keydown\", this.#onWindowKeydown);\n } else {\n deactivateLayer(this);\n window.removeEventListener(\"mousedown\", this.#onWindowMousedown, true);\n window.removeEventListener(\"keydown\", this.#onWindowKeydown);\n }\n }\n\n const shouldBeModal = this.open && this.centered;\n if (shouldBeModal && !this.#modalActive) {\n this.#modalActive = true;\n this.#previousFocus = document.activeElement as HTMLElement | null;\n document.addEventListener(\"focusin\", this.#onDocumentFocusIn, true);\n this.#focusInitial();\n } else if (!shouldBeModal && this.#modalActive) {\n this.#modalActive = false;\n document.removeEventListener(\"focusin\", this.#onDocumentFocusIn, true);\n if (this.#previousFocus?.isConnected) this.#previousFocus.focus();\n this.#previousFocus = null;\n }\n }\n\n #onWindowMousedown = (e: MouseEvent): void => {\n if (!isTopLayer(this)) return;\n // In centered mode the host itself renders the backdrop, so a mousedown\n // landing directly on the host (not on .panel or its content) is a\n // backdrop click and must close like an outside click would.\n if (this.centered && e.target === this) {\n this.#close();\n return;\n }\n if (!e.composedPath().includes(this)) this.#close();\n };\n\n #onWindowKeydown = (e: KeyboardEvent): void => {\n if (!isTopLayer(this)) return;\n if (claimEscape(this, e)) {\n this.#close();\n return;\n }\n if (!this.centered || e.key !== \"Tab\") return;\n\n const focusable = this.#getFocusableElements();\n if (focusable.length === 0) {\n e.preventDefault();\n this.#focusInitial();\n return;\n }\n const active = (this.shadowRoot?.activeElement ?? document.activeElement) as HTMLElement | null;\n const index = focusable.indexOf(active as HTMLElement);\n if (e.shiftKey && index <= 0) {\n e.preventDefault();\n focusable.at(-1)?.focus();\n } else if (!e.shiftKey && index === focusable.length - 1) {\n e.preventDefault();\n focusable[0]?.focus();\n }\n };\n\n #onDocumentFocusIn = (event: FocusEvent): void => {\n if (!this.#modalActive || !isTopLayer(this) || event.composedPath().includes(this)) return;\n this.#focusInitial();\n };\n\n #getFocusableElements(): HTMLElement[] {\n const selector =\n \"a[href], button:not(:disabled), input:not(:disabled), select:not(:disabled), textarea:not(:disabled), [tabindex]:not([tabindex='-1'])\";\n const focusableInSlot = (slotSelector: string): HTMLElement[] => {\n const slot = this.shadowRoot?.querySelector<HTMLSlotElement>(slotSelector);\n return (slot?.assignedElements({ flatten: true }) ?? []).flatMap((element) => [\n ...(element instanceof HTMLElement && element.matches(selector) ? [element] : []),\n ...Array.from(element.querySelectorAll<HTMLElement>(selector)),\n ]);\n };\n const close = this.shadowRoot?.querySelector<HTMLElement>(\".close-btn\");\n return [\n ...focusableInSlot('slot[name=\"title\"]'),\n ...focusableInSlot('slot[name=\"actions\"]'),\n ...(close ? [close] : []),\n ...focusableInSlot(\"slot:not([name])\"),\n ].filter((element) => !element.hasAttribute(\"hidden\"));\n }\n\n #focusInitial(): void {\n const autofocus = this.querySelector<HTMLElement>(\"[autofocus]\");\n const close = this.shadowRoot?.querySelector<HTMLElement>(\".close-btn\");\n const panel = this.shadowRoot?.querySelector<HTMLElement>(\".panel\");\n (autofocus ?? close ?? this.#getFocusableElements()[0] ?? panel)?.focus();\n }\n\n #close(): void {\n this.dispatchEvent(new CustomEvent(\"panel-close\", { bubbles: true, composed: true }));\n }\n\n override render() {\n const explicitLabel = this.getAttribute(\"aria-label\");\n return html`\n <div\n class=\"panel\"\n role=\"dialog\"\n aria-modal=${this.centered ? \"true\" : nothing}\n aria-label=${explicitLabel ?? nothing}\n aria-labelledby=${explicitLabel ? nothing : \"popover-title\"}\n tabindex=\"-1\"\n >\n <div class=\"panel-header\">\n <span id=\"popover-title\" class=\"panel-title\">\n <slot name=\"title\">${this.heading || (this.centered ? \"Dialog\" : \"Popover\")}</slot>\n </span>\n <span class=\"panel-actions\"><slot name=\"actions\"></slot></span>\n <button class=\"close-btn\" aria-label=\"Close popover\" @click=${() => this.#close()}>\n <span aria-hidden=\"true\">${iconX(18)}</span>\n </button>\n </div>\n <slot></slot>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"popover-panel\": PopoverPanel;\n }\n}\n"]}
@@ -0,0 +1,22 @@
1
+ import { LitElement } from "lit";
2
+ /**
3
+ * Vertical timeline: a single connecting line runs down the left edge and each
4
+ * slotted `timeline-entry` places a dot on it. This is a thin layout and
5
+ * semantics wrapper — the entries draw the line segments and dots themselves,
6
+ * so the container adds no gap between them (a gap would break the line).
7
+ * Exposed to assistive technology as a list of events.
8
+ *
9
+ * @element timeline-container
10
+ * @slot - `timeline-entry` elements, in chronological order.
11
+ */
12
+ export declare class TimelineContainer extends LitElement {
13
+ static styles: import("lit").CSSResult[];
14
+ connectedCallback(): void;
15
+ render(): import("lit-html").TemplateResult<1>;
16
+ }
17
+ declare global {
18
+ interface HTMLElementTagNameMap {
19
+ "timeline-container": TimelineContainer;
20
+ }
21
+ }
22
+ //# sourceMappingURL=timeline-container.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timeline-container.d.ts","sourceRoot":"","sources":["../src/timeline-container.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAI5C;;;;;;;;;GASG;AACH,qBACa,iBAAkB,SAAQ,UAAU;IAC/C,OAAgB,MAAM,4BAOpB;IAEO,iBAAiB,IAAI,IAAI,CAGjC;IAEQ,MAAM,yCAEd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,oBAAoB,EAAE,iBAAiB,CAAC;KACzC;CACF"}
@@ -0,0 +1,42 @@
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
+ * Vertical timeline: a single connecting line runs down the left edge and each
12
+ * slotted `timeline-entry` places a dot on it. This is a thin layout and
13
+ * semantics wrapper — the entries draw the line segments and dots themselves,
14
+ * so the container adds no gap between them (a gap would break the line).
15
+ * Exposed to assistive technology as a list of events.
16
+ *
17
+ * @element timeline-container
18
+ * @slot - `timeline-entry` elements, in chronological order.
19
+ */
20
+ let TimelineContainer = class TimelineContainer extends LitElement {
21
+ static { this.styles = [
22
+ tokens,
23
+ css `
24
+ :host {
25
+ display: block;
26
+ }
27
+ `,
28
+ ]; }
29
+ connectedCallback() {
30
+ super.connectedCallback();
31
+ if (!this.hasAttribute("role"))
32
+ this.setAttribute("role", "list");
33
+ }
34
+ render() {
35
+ return html `<slot></slot>`;
36
+ }
37
+ };
38
+ TimelineContainer = __decorate([
39
+ customElement("timeline-container")
40
+ ], TimelineContainer);
41
+ export { TimelineContainer };
42
+ //# sourceMappingURL=timeline-container.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timeline-container.js","sourceRoot":"","sources":["../src/timeline-container.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;;;;;;;;;GASG;AAEI,IAAM,iBAAiB,GAAvB,MAAM,iBAAkB,SAAQ,UAAU;aAC/B,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;KAIF;KACF,AAPqB,CAOpB;IAEO,iBAAiB;QACxB,KAAK,CAAC,iBAAiB,EAAE,CAAC;QAC1B,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC;YAAE,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpE,CAAC;IAEQ,MAAM;QACb,OAAO,IAAI,CAAA,eAAe,CAAC;IAC7B,CAAC;CACF,CAAA;AAlBY,iBAAiB;IAD7B,aAAa,CAAC,oBAAoB,CAAC;GACvB,iBAAiB,CAkB7B","sourcesContent":["import { LitElement, css, html } from \"lit\";\nimport { customElement } from \"lit/decorators.js\";\nimport { tokens } from \"./tokens.js\";\n\n/**\n * Vertical timeline: a single connecting line runs down the left edge and each\n * slotted `timeline-entry` places a dot on it. This is a thin layout and\n * semantics wrapper — the entries draw the line segments and dots themselves,\n * so the container adds no gap between them (a gap would break the line).\n * Exposed to assistive technology as a list of events.\n *\n * @element timeline-container\n * @slot - `timeline-entry` elements, in chronological order.\n */\n@customElement(\"timeline-container\")\nexport class TimelineContainer extends LitElement {\n static override styles = [\n tokens,\n css`\n :host {\n display: block;\n }\n `,\n ];\n\n override connectedCallback(): void {\n super.connectedCallback();\n if (!this.hasAttribute(\"role\")) this.setAttribute(\"role\", \"list\");\n }\n\n override render() {\n return html`<slot></slot>`;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"timeline-container\": TimelineContainer;\n }\n}\n"]}
@@ -0,0 +1,31 @@
1
+ import { LitElement } from "lit";
2
+ import "./relative-time.js";
3
+ /**
4
+ * One event on a `timeline-container`: a dot on the vertical line, an optional
5
+ * headline, a relative timestamp ("3 hours ago"), and freely nested content.
6
+ * The connecting line is drawn here — its segment above the dot is hidden on
7
+ * the first entry and the segment below is hidden on the last, so the line caps
8
+ * exactly at the first and last dots. Only meaningful inside a
9
+ * `timeline-container`; demonstrated through it.
10
+ *
11
+ * @element timeline-entry
12
+ * @slot headline - Optional headline/title for the event.
13
+ * @slot - The event content; nest any elements here.
14
+ */
15
+ export declare class TimelineEntry extends LitElement {
16
+ /** ISO 8601 or SQLite datetime string, rendered as a relative time. */
17
+ datetime: string | null;
18
+ /** Whether the headline slot currently has assigned content. */
19
+ private _hasHeadline;
20
+ static styles: import("lit").CSSResult[];
21
+ connectedCallback(): void;
22
+ /** Collapses the headline when nothing is slotted so the time sits alone. */
23
+ private _onHeadlineSlotChange;
24
+ render(): import("lit-html").TemplateResult<1>;
25
+ }
26
+ declare global {
27
+ interface HTMLElementTagNameMap {
28
+ "timeline-entry": TimelineEntry;
29
+ }
30
+ }
31
+ //# sourceMappingURL=timeline-entry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timeline-entry.d.ts","sourceRoot":"","sources":["../src/timeline-entry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsB,MAAM,KAAK,CAAC;AAErD,OAAO,oBAAoB,CAAC;AAG5B;;;;;;;;;;;GAWG;AACH,qBACa,aAAc,SAAQ,UAAU;IAC3C,uEAAuE;IAC3D,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE3C,gEAAgE;IACvD,OAAO,CAAC,YAAY,CAAS;IAEtC,OAAgB,MAAM,4BAgGpB;IAEO,iBAAiB,IAAI,IAAI,CAGjC;IAED,6EAA6E;IAC7E,OAAO,CAAC,qBAAqB;IAIpB,MAAM,yCAqBd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,gBAAgB,EAAE,aAAa,CAAC;KACjC;CACF"}
@@ -0,0 +1,170 @@
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, nothing } from "lit";
8
+ import { customElement, property, state } from "lit/decorators.js";
9
+ import "./relative-time.js";
10
+ import { tokens } from "./tokens.js";
11
+ /**
12
+ * One event on a `timeline-container`: a dot on the vertical line, an optional
13
+ * headline, a relative timestamp ("3 hours ago"), and freely nested content.
14
+ * The connecting line is drawn here — its segment above the dot is hidden on
15
+ * the first entry and the segment below is hidden on the last, so the line caps
16
+ * exactly at the first and last dots. Only meaningful inside a
17
+ * `timeline-container`; demonstrated through it.
18
+ *
19
+ * @element timeline-entry
20
+ * @slot headline - Optional headline/title for the event.
21
+ * @slot - The event content; nest any elements here.
22
+ */
23
+ let TimelineEntry = class TimelineEntry extends LitElement {
24
+ constructor() {
25
+ super(...arguments);
26
+ /** ISO 8601 or SQLite datetime string, rendered as a relative time. */
27
+ this.datetime = null;
28
+ /** Whether the headline slot currently has assigned content. */
29
+ this._hasHeadline = false;
30
+ }
31
+ static { this.styles = [
32
+ tokens,
33
+ css `
34
+ :host {
35
+ display: block;
36
+ font-family: var(
37
+ --ui-font,
38
+ ui-sans-serif,
39
+ system-ui,
40
+ sans-serif,
41
+ "Apple Color Emoji",
42
+ "Segoe UI Emoji",
43
+ "Segoe UI Symbol",
44
+ "Noto Color Emoji"
45
+ );
46
+ }
47
+ .entry {
48
+ display: flex;
49
+ gap: 0.75rem;
50
+ }
51
+ .rail {
52
+ position: relative;
53
+ flex: 0 0 auto;
54
+ width: 0.75rem;
55
+ }
56
+ .line {
57
+ position: absolute;
58
+ left: 50%;
59
+ width: 2px;
60
+ transform: translateX(-50%);
61
+ background: var(--ui-border, #e2e8f0);
62
+ }
63
+ .line-top {
64
+ top: 0;
65
+ height: 0.5rem;
66
+ }
67
+ .line-bottom {
68
+ top: 0.5rem;
69
+ bottom: 0;
70
+ }
71
+ .dot {
72
+ position: absolute;
73
+ top: 0.125rem;
74
+ left: 50%;
75
+ width: 0.75rem;
76
+ height: 0.75rem;
77
+ transform: translateX(-50%);
78
+ border-radius: 9999px;
79
+ /* Same primary-token gradient as map-circle/user-avatar, lighter on top. */
80
+ background: linear-gradient(
81
+ to bottom,
82
+ color-mix(in srgb, var(--ui-primary, #4f46e5) 70%, #ffffff) 0%,
83
+ color-mix(in srgb, var(--ui-primary, #4f46e5) 70%, #000000) 100%
84
+ );
85
+ }
86
+ :host(:first-child) .line-top {
87
+ display: none;
88
+ }
89
+ :host(:last-child) .line-bottom {
90
+ display: none;
91
+ }
92
+ .body {
93
+ flex: 1 1 auto;
94
+ min-width: 0;
95
+ padding-bottom: 1.5rem;
96
+ }
97
+ :host(:last-child) .body {
98
+ padding-bottom: 0;
99
+ }
100
+ .head {
101
+ display: flex;
102
+ align-items: baseline;
103
+ gap: 0.5rem;
104
+ flex-wrap: wrap;
105
+ }
106
+ .headline {
107
+ font-weight: var(--ui-font-weight-semibold, 600);
108
+ font-size: var(--ui-font-size, 0.875rem);
109
+ line-height: var(--ui-line-height-tight, 1.25);
110
+ color: var(--ui-text, #0f172a);
111
+ }
112
+ .headline.empty {
113
+ display: none;
114
+ }
115
+ .time {
116
+ font-size: var(--ui-font-size-sm, 0.75rem);
117
+ line-height: var(--ui-line-height-tight, 1.25);
118
+ color: var(--ui-text-muted, #64748b);
119
+ }
120
+ .content {
121
+ margin-top: 0.25rem;
122
+ font-size: var(--ui-font-size, 0.875rem);
123
+ line-height: var(--ui-line-height-normal, 1.5);
124
+ color: var(--ui-text, #0f172a);
125
+ }
126
+ `,
127
+ ]; }
128
+ connectedCallback() {
129
+ super.connectedCallback();
130
+ if (!this.hasAttribute("role"))
131
+ this.setAttribute("role", "listitem");
132
+ }
133
+ /** Collapses the headline when nothing is slotted so the time sits alone. */
134
+ _onHeadlineSlotChange(event) {
135
+ this._hasHeadline = event.target.assignedNodes({ flatten: true }).length > 0;
136
+ }
137
+ render() {
138
+ return html `
139
+ <div class="entry">
140
+ <div class="rail" aria-hidden="true">
141
+ <span class="line line-top"></span>
142
+ <span class="line line-bottom"></span>
143
+ <span class="dot"></span>
144
+ </div>
145
+ <div class="body">
146
+ <div class="head">
147
+ <span class="headline ${this._hasHeadline ? "" : "empty"}">
148
+ <slot name="headline" @slotchange=${this._onHeadlineSlotChange}></slot>
149
+ </span>
150
+ ${this.datetime
151
+ ? html `<relative-time class="time" datetime=${this.datetime}></relative-time>`
152
+ : nothing}
153
+ </div>
154
+ <div class="content"><slot></slot></div>
155
+ </div>
156
+ </div>
157
+ `;
158
+ }
159
+ };
160
+ __decorate([
161
+ property()
162
+ ], TimelineEntry.prototype, "datetime", void 0);
163
+ __decorate([
164
+ state()
165
+ ], TimelineEntry.prototype, "_hasHeadline", void 0);
166
+ TimelineEntry = __decorate([
167
+ customElement("timeline-entry")
168
+ ], TimelineEntry);
169
+ export { TimelineEntry };
170
+ //# sourceMappingURL=timeline-entry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timeline-entry.js","sourceRoot":"","sources":["../src/timeline-entry.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,oBAAoB,CAAC;AAC5B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC;;;;;;;;;;;GAWG;AAEI,IAAM,aAAa,GAAnB,MAAM,aAAc,SAAQ,UAAU;IAAtC;;QACL,uEAAuE;QAC3D,aAAQ,GAAkB,IAAI,CAAC;QAE3C,gEAAgE;QAC/C,iBAAY,GAAG,KAAK,CAAC;IAoIxC,CAAC;aAlIiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA6FF;KACF,AAhGqB,CAgGpB;IAEO,iBAAiB;QACxB,KAAK,CAAC,iBAAiB,EAAE,CAAC;QAC1B,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC;YAAE,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACxE,CAAC;IAED,6EAA6E;IACrE,qBAAqB,CAAC,KAAY;QACxC,IAAI,CAAC,YAAY,GAAI,KAAK,CAAC,MAA0B,CAAC,aAAa,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;IACpG,CAAC;IAEQ,MAAM;QACb,OAAO,IAAI,CAAA;;;;;;;;;oCASqB,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO;kDAClB,IAAI,CAAC,qBAAqB;;cAE9D,IAAI,CAAC,QAAQ;YACb,CAAC,CAAC,IAAI,CAAA,wCAAwC,IAAI,CAAC,QAAQ,mBAAmB;YAC9E,CAAC,CAAC,OAAO;;;;;KAKlB,CAAC;IACJ,CAAC;CACF,CAAA;AAvIa;IAAX,QAAQ,EAAE;+CAAgC;AAG1B;IAAhB,KAAK,EAAE;mDAA8B;AAL3B,aAAa;IADzB,aAAa,CAAC,gBAAgB,CAAC;GACnB,aAAa,CAyIzB","sourcesContent":["import { LitElement, css, html, nothing } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport \"./relative-time.js\";\nimport { tokens } from \"./tokens.js\";\n\n/**\n * One event on a `timeline-container`: a dot on the vertical line, an optional\n * headline, a relative timestamp (\"3 hours ago\"), and freely nested content.\n * The connecting line is drawn here — its segment above the dot is hidden on\n * the first entry and the segment below is hidden on the last, so the line caps\n * exactly at the first and last dots. Only meaningful inside a\n * `timeline-container`; demonstrated through it.\n *\n * @element timeline-entry\n * @slot headline - Optional headline/title for the event.\n * @slot - The event content; nest any elements here.\n */\n@customElement(\"timeline-entry\")\nexport class TimelineEntry extends LitElement {\n /** ISO 8601 or SQLite datetime string, rendered as a relative time. */\n @property() datetime: string | null = null;\n\n /** Whether the headline slot currently has assigned content. */\n @state() private _hasHeadline = false;\n\n static override styles = [\n tokens,\n css`\n :host {\n display: block;\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 .entry {\n display: flex;\n gap: 0.75rem;\n }\n .rail {\n position: relative;\n flex: 0 0 auto;\n width: 0.75rem;\n }\n .line {\n position: absolute;\n left: 50%;\n width: 2px;\n transform: translateX(-50%);\n background: var(--ui-border, #e2e8f0);\n }\n .line-top {\n top: 0;\n height: 0.5rem;\n }\n .line-bottom {\n top: 0.5rem;\n bottom: 0;\n }\n .dot {\n position: absolute;\n top: 0.125rem;\n left: 50%;\n width: 0.75rem;\n height: 0.75rem;\n transform: translateX(-50%);\n border-radius: 9999px;\n /* Same primary-token gradient as map-circle/user-avatar, lighter on top. */\n background: linear-gradient(\n to bottom,\n color-mix(in srgb, var(--ui-primary, #4f46e5) 70%, #ffffff) 0%,\n color-mix(in srgb, var(--ui-primary, #4f46e5) 70%, #000000) 100%\n );\n }\n :host(:first-child) .line-top {\n display: none;\n }\n :host(:last-child) .line-bottom {\n display: none;\n }\n .body {\n flex: 1 1 auto;\n min-width: 0;\n padding-bottom: 1.5rem;\n }\n :host(:last-child) .body {\n padding-bottom: 0;\n }\n .head {\n display: flex;\n align-items: baseline;\n gap: 0.5rem;\n flex-wrap: wrap;\n }\n .headline {\n font-weight: var(--ui-font-weight-semibold, 600);\n font-size: var(--ui-font-size, 0.875rem);\n line-height: var(--ui-line-height-tight, 1.25);\n color: var(--ui-text, #0f172a);\n }\n .headline.empty {\n display: none;\n }\n .time {\n font-size: var(--ui-font-size-sm, 0.75rem);\n line-height: var(--ui-line-height-tight, 1.25);\n color: var(--ui-text-muted, #64748b);\n }\n .content {\n margin-top: 0.25rem;\n font-size: var(--ui-font-size, 0.875rem);\n line-height: var(--ui-line-height-normal, 1.5);\n color: var(--ui-text, #0f172a);\n }\n `,\n ];\n\n override connectedCallback(): void {\n super.connectedCallback();\n if (!this.hasAttribute(\"role\")) this.setAttribute(\"role\", \"listitem\");\n }\n\n /** Collapses the headline when nothing is slotted so the time sits alone. */\n private _onHeadlineSlotChange(event: Event): void {\n this._hasHeadline = (event.target as HTMLSlotElement).assignedNodes({ flatten: true }).length > 0;\n }\n\n override render() {\n return html`\n <div class=\"entry\">\n <div class=\"rail\" aria-hidden=\"true\">\n <span class=\"line line-top\"></span>\n <span class=\"line line-bottom\"></span>\n <span class=\"dot\"></span>\n </div>\n <div class=\"body\">\n <div class=\"head\">\n <span class=\"headline ${this._hasHeadline ? \"\" : \"empty\"}\">\n <slot name=\"headline\" @slotchange=${this._onHeadlineSlotChange}></slot>\n </span>\n ${this.datetime\n ? html`<relative-time class=\"time\" datetime=${this.datetime}></relative-time>`\n : nothing}\n </div>\n <div class=\"content\"><slot></slot></div>\n </div>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"timeline-entry\": TimelineEntry;\n }\n}\n"]}
@@ -0,0 +1,41 @@
1
+ # `<action-bar>`
2
+
3
+ Toolbar that sits directly above a list or table: a left cluster for
4
+ search and filters and a right cluster for record actions (create, delete,
5
+ bulk actions). It's a presentational layout container only — drop any
6
+ controls (`autocomplete-input`, `multi-select`, `ui-button`, …) into the
7
+ `start` and `end` slots; the bar owns none of their behavior and adds no
8
+ search field of its own. The two clusters wrap onto separate rows when the
9
+ bar is too narrow.
10
+
11
+ ## Install
12
+
13
+ ```js
14
+ import "@f-ewald/components/action-bar.js";
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ```html
20
+ <action-bar>
21
+ <autocomplete-input slot="start" placeholder="Search…"></autocomplete-input>
22
+ <ui-button slot="end" variant="secondary">Delete</ui-button>
23
+ <ui-button slot="end" variant="primary">Create</ui-button>
24
+ </action-bar>
25
+ ```
26
+
27
+ ## Attributes / properties
28
+
29
+ _None._
30
+
31
+ ## Events
32
+
33
+ _None._
34
+
35
+ ## Slots
36
+
37
+ _None._
38
+
39
+ ## CSS custom properties
40
+
41
+ _None._
@@ -0,0 +1,85 @@
1
+ # `<app-shell>`
2
+
3
+ The dashboard page shell: a slot-based CSS-grid backbone that arranges a
4
+ full-height sidebar, a top bar, the main content, an optional right-hand
5
+ detail column, and an optional footer. It owns the responsive behavior so
6
+ consumers don't re-implement it — above the shared 48rem breakpoint the
7
+ sidebar collapses to an icon rail and the detail region is an inline column;
8
+ at or below it the sidebar becomes an off-canvas drawer and the detail region
9
+ an overlay, both dismissed by a scrim or Escape.
10
+
11
+ Widths are tunable per instance via `--component-sidebar-width` (16rem),
12
+ `--component-sidebar-rail-width` (3.5rem), and `--component-topbar-height`
13
+ (3rem); the detail column reuses the 20rem/25rem panel widths. The main
14
+ content area is white by default — override it with
15
+ `--component-main-background`. Give the shell a height (e.g. `height: 100vh`)
16
+ so the sidebar and main can size and scroll.
17
+
18
+ The built-in top-bar button toggles the sidebar, and so does pressing `[`
19
+ anywhere on the page (ignored while typing in a text field or with a
20
+ modifier held). Hovering or keyboard-focusing the toggle reveals a tooltip
21
+ naming the action and its `[` shortcut — the shortcut is not shown as
22
+ permanent chrome.
23
+
24
+ ## Install
25
+
26
+ ```js
27
+ import "@f-ewald/components/app-shell.js";
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ ```html
33
+ <app-shell detail-open style="height: 100vh">
34
+ <app-sidebar slot="sidebar">
35
+ <a href="/dashboard" aria-current="page" aria-label="Dashboard">
36
+ <!-- icon --><span style="display: var(--app-sidebar-label, inline)">Dashboard</span>
37
+ </a>
38
+ </app-sidebar>
39
+ <page-header slot="topbar" heading="Members"></page-header>
40
+ <action-bar>
41
+ <autocomplete-input slot="start" placeholder="Search…"></autocomplete-input>
42
+ <ui-button slot="end" variant="primary">Create</ui-button>
43
+ </action-bar>
44
+ <data-table></data-table>
45
+ <div slot="detail">Selected record…</div>
46
+ <pagination-nav slot="footer" total-pages="5"></pagination-nav>
47
+ </app-shell>
48
+ ```
49
+
50
+ ## Attributes / properties
51
+
52
+ | Property | Attribute | Type | Default | Description |
53
+ | --- | --- | --- | --- | --- |
54
+ | `sidebarCollapsed` | `sidebar-collapsed` | `boolean` | `false` | Collapses the sidebar to an icon rail on desktop. |
55
+ | `detailOpen` | `detail-open` | `boolean` | `false` | Shows the right-hand detail region (inline column, or overlay on mobile). |
56
+ | `detailWidth` | `detail-width` | `"compact" | "comfortable"` | `"compact"` | Detail width: `compact` (20rem) or `comfortable` (25rem). |
57
+
58
+ ## Events
59
+
60
+ | Event | Description |
61
+ | --- | --- |
62
+ | `sidebar-toggle` | The built-in toggle changed the sidebar state. |
63
+ | `detail-close` | The scrim or Escape dismissed the mobile detail overlay. |
64
+
65
+ ## Slots
66
+
67
+ _None._
68
+
69
+ ## CSS custom properties
70
+
71
+ | Custom property |
72
+ | --- |
73
+ | `--ui-border` |
74
+ | `--ui-focus-ring` |
75
+ | `--ui-font-size-sm` |
76
+ | `--ui-line-height-tight` |
77
+ | `--ui-on-accent` |
78
+ | `--ui-overlay` |
79
+ | `--ui-radius-sm` |
80
+ | `--ui-shadow` |
81
+ | `--ui-shadow-lg` |
82
+ | `--ui-surface` |
83
+ | `--ui-surface-muted` |
84
+ | `--ui-text-muted` |
85
+ | `--ui-tooltip` |
@@ -0,0 +1,69 @@
1
+ # `<app-sidebar>`
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
+ ## Install
19
+
20
+ ```js
21
+ import "@f-ewald/components/app-sidebar.js";
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ ```html
27
+ <app-sidebar>
28
+ <a href="/dashboard" aria-current="page" aria-label="Dashboard">
29
+ <!-- icon --><span style="display: var(--app-sidebar-label, inline)">Dashboard</span>
30
+ </a>
31
+ <a href="/members" aria-label="Members">
32
+ <!-- icon --><span style="display: var(--app-sidebar-label, inline)">Members</span>
33
+ </a>
34
+ </app-sidebar>
35
+ ```
36
+
37
+ ## Attributes / properties
38
+
39
+ | Property | Attribute | Type | Default | Description |
40
+ | --- | --- | --- | --- | --- |
41
+ | `collapsed` | `collapsed` | `boolean` | `false` | Collapses the sidebar to an icon rail (centers icons, hides labels). |
42
+
43
+ ## Events
44
+
45
+ _None._
46
+
47
+ ## Slots
48
+
49
+ _None._
50
+
51
+ ## CSS custom properties
52
+
53
+ | Custom property |
54
+ | --- |
55
+ | `--ui-border` |
56
+ | `--ui-focus-ring` |
57
+ | `--ui-font` |
58
+ | `--ui-font-size-sm` |
59
+ | `--ui-font-size-xs` |
60
+ | `--ui-font-weight-medium` |
61
+ | `--ui-font-weight-semibold` |
62
+ | `--ui-line-height-tight` |
63
+ | `--ui-primary` |
64
+ | `--ui-radius-sm` |
65
+ | `--ui-surface` |
66
+ | `--ui-surface-muted` |
67
+ | `--ui-text` |
68
+ | `--ui-text-muted` |
69
+ | `--ui-tracking-wide` |