@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,50 @@
1
+ # List-only page
2
+
3
+ A full-width list/table view: a collapsible sidebar, a top bar, an action row,
4
+ the list, and pagination. The default internal-dashboard "index" screen.
5
+
6
+ **When to use:** browsing or managing a collection of records where a row does
7
+ not need a side-by-side detail pane (rows link to their own detail page). If you
8
+ want an inline detail pane, use the list + detail template instead.
9
+
10
+ ## Shell slots
11
+
12
+ - `sidebar` → `app-sidebar` with the app's navigation.
13
+ - `topbar` → `page-header` with the collection title (and optional actions).
14
+ - default (`main`) → an `action-bar` above a `data-table`.
15
+ - `footer` → `pagination-nav`.
16
+
17
+ ## Markup
18
+
19
+ ```html
20
+ <app-shell style="height: 100vh">
21
+ <app-sidebar slot="sidebar"><!-- nav items --></app-sidebar>
22
+
23
+ <page-header slot="topbar" heading="Members"></page-header>
24
+
25
+ <div>
26
+ <action-bar>
27
+ <autocomplete-input slot="start" placeholder="Search…"></autocomplete-input>
28
+ <ui-button slot="end" variant="secondary">Filter</ui-button>
29
+ <ui-button slot="end" variant="primary">Create</ui-button>
30
+ </action-bar>
31
+ <data-table style="margin-top: 1rem; display: block;"></data-table>
32
+ </div>
33
+
34
+ <pagination-nav slot="footer" current-page="1" total-pages="6"></pagination-nav>
35
+ </app-shell>
36
+ ```
37
+
38
+ ## Notes
39
+
40
+ - Give `app-shell` a height (e.g. `100vh`) so the sidebar and main can size and
41
+ scroll independently.
42
+ - `pagination-nav` is controlled: set `current-page`/`total-pages` and re-render
43
+ in response to its `page-change` event, the same way `data-table` leaves the
44
+ rows to you.
45
+ - Put search and filters in the action bar's `start` slot and record actions
46
+ (create, delete) in `end`.
47
+
48
+ **Live demo:** `demo/layouts/list-only.html`
49
+
50
+ **Components:** app-shell, app-sidebar, page-header, action-bar, data-table, pagination-nav
@@ -0,0 +1,88 @@
1
+ # Record + metadata rail page
2
+
3
+ A record's read view with the main content on the left and a persistent
4
+ metadata/detail rail on the right — the Jira/Linear issue layout (description +
5
+ status, assignee, labels, priority).
6
+
7
+ **When to use:** viewing one rich record whose metadata (status, owner, labels,
8
+ progress) should stay visible alongside the content, and where that metadata is
9
+ mostly a static display of components rather than a transient selection.
10
+
11
+ ## Layout
12
+
13
+ The rail is a **two-column CSS grid inside the `main` slot**, not the shell's
14
+ `detail` slot:
15
+
16
+ - `sidebar` → `app-sidebar`.
17
+ - `topbar` → `page-header` with a linked `breadcrumb` and record `actions`
18
+ (primary Edit rightmost).
19
+ - default (`main`) → a grid: the body (description, activity) on the left, an
20
+ `<aside>` metadata rail on the right. It collapses to one column at the shared
21
+ 48rem breakpoint so the metadata **stacks below** the content on narrow
22
+ screens instead of hiding.
23
+
24
+ The metadata rail is composed from existing components: `frame-box` to group,
25
+ `status-pill` for status/priority/labels, `user-avatar` for people, and
26
+ `stat-meter` for progress. Activity uses `chat-message`.
27
+
28
+ ## Markup
29
+
30
+ ```html
31
+ <app-shell style="height: 100vh">
32
+ <app-sidebar slot="sidebar"><!-- nav items --></app-sidebar>
33
+
34
+ <page-header slot="topbar" heading="Fix flaky login redirect">
35
+ <nav slot="breadcrumb" aria-label="Breadcrumb"><a href="/board">Board</a> / PROJ-142</nav>
36
+ <ui-button slot="actions" variant="primary">Edit</ui-button>
37
+ </page-header>
38
+
39
+ <div class="ticket">
40
+ <div class="ticket-body">
41
+ <h3>Description</h3>
42
+ <p>…</p>
43
+ <frame-box label="Activity">
44
+ <chat-message role="user" author="Ada Lovelace">…</chat-message>
45
+ </frame-box>
46
+ </div>
47
+
48
+ <aside class="ticket-meta" aria-label="Issue details">
49
+ <frame-box label="Details">
50
+ <dl class="meta">
51
+ <div class="meta-row"><dt>Status</dt><dd><status-pill label="In Progress" color="info"></status-pill></dd></div>
52
+ <div class="meta-row"><dt>Assignee</dt><dd><user-avatar name="Ada Lovelace" size="24"></user-avatar> Ada Lovelace</dd></div>
53
+ <div class="meta-row"><dt>Priority</dt><dd><status-pill label="High" color="warning"></status-pill></dd></div>
54
+ </dl>
55
+ <stat-meter label="Progress" percent="60"></stat-meter>
56
+ </frame-box>
57
+ </aside>
58
+ </div>
59
+ </app-shell>
60
+ ```
61
+
62
+ ```css
63
+ .ticket {
64
+ display: grid;
65
+ grid-template-columns: minmax(0, 1fr) 18rem;
66
+ gap: 1.5rem;
67
+ align-items: start;
68
+ }
69
+ @media (max-width: 48rem) {
70
+ .ticket { grid-template-columns: minmax(0, 1fr); }
71
+ }
72
+ ```
73
+
74
+ ## Notes
75
+
76
+ - Use the two-column grid inside `main` (not the shell `detail` slot) when the
77
+ metadata is **persistent** and should stack on mobile. The shell's `detail`
78
+ slot is for a **transient master-detail selection** (list + selected item): on
79
+ mobile it becomes a dismissible overlay, which hides persistent record
80
+ metadata. Reach for it in the list + detail template, not here.
81
+ - Editable metadata is a drop-in swap: replace a `status-pill` with a
82
+ `form-select`, or the label pills with a `multi-select`.
83
+ - Keep the record actions in the header (primary Edit); there is no
84
+ `form-actions` footer on a read view.
85
+
86
+ **Live demo:** `demo/layouts/record-detail.html`
87
+
88
+ **Components:** app-shell, app-sidebar, page-header, frame-box, status-pill, user-avatar, stat-meter, chat-message
@@ -36,3 +36,24 @@ A thin stdio MCP server exposing two tools — `list_components` and
36
36
  `get_component_docs(tag)` — backed by the same `custom-elements.json` this
37
37
  package already generates and ships. No new data source was needed; the
38
38
  server is just a different transport over data that already existed.
39
+
40
+ ## Update (layout system): added `list_layouts` / `get_layout`
41
+
42
+ The dashboard layout system introduced cross-component knowledge — which
43
+ components compose into which page templates, the slot recipes, and the
44
+ button-placement rule — that no single component's generated doc captures. Two
45
+ thin tools, `list_layouts` and `get_layout(name)`, expose it.
46
+
47
+ This stays true to the server's founding principle: **no new data source.** The
48
+ tools read the authored `docs/layouts/*.md` recipes (which also ship in the npm
49
+ package and render on the docs site), not a bespoke structure invented for MCP.
50
+ The individual layout components still flow through `list_components` /
51
+ `get_component_docs` automatically.
52
+
53
+ **Is MCP the right fit here?** It's a reasonable, not slam-dunk, fit. The
54
+ justification is discoverability and targeted retrieval — an agent can list the
55
+ templates and pull just the one it needs, rather than loading everything. If the
56
+ guidance stayed tiny, `llms.txt` alone could carry it; the tools earn their keep
57
+ by making the "preferred layout" explicitly enumerable and by matching the
58
+ per-component tools consumers already call. The cost to watch is keeping the
59
+ recipes, the `demo/layouts/` demos, and the tool output in sync.
@@ -0,0 +1,47 @@
1
+ # `<page-header>`
2
+
3
+ Page title block for the top of a dashboard view: an optional breadcrumb
4
+ trail, the page heading, and a right-aligned cluster of page-level actions.
5
+ It only lays these out — the breadcrumb links and action buttons are entirely
6
+ the consumer's, so it stays framework- and router-agnostic.
7
+
8
+ ## Install
9
+
10
+ ```js
11
+ import "@f-ewald/components/page-header.js";
12
+ ```
13
+
14
+ ## Usage
15
+
16
+ ```html
17
+ <page-header heading="Team members">
18
+ <nav slot="breadcrumb" aria-label="Breadcrumb">Home / Settings / Members</nav>
19
+ <ui-button slot="actions" variant="primary">Invite</ui-button>
20
+ </page-header>
21
+ ```
22
+
23
+ ## Attributes / properties
24
+
25
+ | Property | Attribute | Type | Default | Description |
26
+ | --- | --- | --- | --- | --- |
27
+ | `heading` | `heading` | `string` | `""` | The page heading text. |
28
+
29
+ ## Events
30
+
31
+ _None._
32
+
33
+ ## Slots
34
+
35
+ _None._
36
+
37
+ ## CSS custom properties
38
+
39
+ | Custom property |
40
+ | --- |
41
+ | `--ui-font` |
42
+ | `--ui-font-size-lg` |
43
+ | `--ui-font-size-sm` |
44
+ | `--ui-font-weight-semibold` |
45
+ | `--ui-line-height-tight` |
46
+ | `--ui-text` |
47
+ | `--ui-text-muted` |
@@ -0,0 +1,54 @@
1
+ # `<pagination-nav>`
2
+
3
+ Minimal, controlled pager for list/table views: a previous/next control pair
4
+ around a "Page N of M" status. It owns no data — the consumer sets
5
+ `current-page` / `total-pages` and moves the page in response to the
6
+ `page-change` event (typically alongside its own data fetch), exactly like
7
+ `data-table` leaves the row data to the caller.
8
+
9
+ Previous is disabled on the first page and next on the last; neither fires an
10
+ event when there is nowhere to go.
11
+
12
+ ## Install
13
+
14
+ ```js
15
+ import "@f-ewald/components/pagination-nav.js";
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ```html
21
+ <pagination-nav current-page="1" total-pages="5"></pagination-nav>
22
+ ```
23
+
24
+ ## Attributes / properties
25
+
26
+ | Property | Attribute | Type | Default | Description |
27
+ | --- | --- | --- | --- | --- |
28
+ | `currentPage` | `current-page` | `number` | `1` | The 1-based current page. |
29
+ | `totalPages` | `total-pages` | `number` | `1` | The total number of pages (minimum 1). |
30
+
31
+ ## Events
32
+
33
+ | Event | Description |
34
+ | --- | --- |
35
+ | `page-change` | The user picked a new page (`detail: { page }`). |
36
+
37
+ ## Slots
38
+
39
+ _None._
40
+
41
+ ## CSS custom properties
42
+
43
+ | Custom property |
44
+ | --- |
45
+ | `--ui-border` |
46
+ | `--ui-focus-ring` |
47
+ | `--ui-font` |
48
+ | `--ui-font-size-sm` |
49
+ | `--ui-font-weight-medium` |
50
+ | `--ui-line-height-tight` |
51
+ | `--ui-radius-sm` |
52
+ | `--ui-surface-muted` |
53
+ | `--ui-text` |
54
+ | `--ui-text-muted` |
@@ -0,0 +1,44 @@
1
+ # `<timeline-container>`
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
+ ## Install
10
+
11
+ ```js
12
+ import "@f-ewald/components/timeline-container.js";
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```html
18
+ <timeline-container>
19
+ <timeline-entry datetime="2026-07-23T09:00:00Z">
20
+ <span slot="headline">Deployment started</span>
21
+ Release v1.4.0 is rolling out.
22
+ </timeline-entry>
23
+ <timeline-entry datetime="2026-07-23T08:45:00Z">
24
+ <span slot="headline">Review approved</span>
25
+ <status-pill label="In Review" color="info"></status-pill>
26
+ </timeline-entry>
27
+ </timeline-container>
28
+ ```
29
+
30
+ ## Attributes / properties
31
+
32
+ _None._
33
+
34
+ ## Events
35
+
36
+ _None._
37
+
38
+ ## Slots
39
+
40
+ _None._
41
+
42
+ ## CSS custom properties
43
+
44
+ _None._
@@ -0,0 +1,52 @@
1
+ # `<timeline-entry>`
2
+
3
+ One event on a `timeline-container`: a dot on the vertical line, an optional
4
+ headline, a relative timestamp ("3 hours ago"), and freely nested content.
5
+ The connecting line is drawn here — its segment above the dot is hidden on
6
+ the first entry and the segment below is hidden on the last, so the line caps
7
+ exactly at the first and last dots. Only meaningful inside a
8
+ `timeline-container`; demonstrated through it.
9
+
10
+ ## Install
11
+
12
+ ```js
13
+ import "@f-ewald/components/timeline-entry.js";
14
+ ```
15
+
16
+ ## Usage
17
+
18
+ ```html
19
+ <timeline-entry datetime="2026-07-23T09:00:00Z">
20
+ <span slot="headline">Deployment started</span>
21
+ Release v1.4.0 is rolling out to production.
22
+ </timeline-entry>
23
+ ```
24
+
25
+ ## Attributes / properties
26
+
27
+ | Property | Attribute | Type | Default | Description |
28
+ | --- | --- | --- | --- | --- |
29
+ | `datetime` | `datetime` | `string | null` | `null` | ISO 8601 or SQLite datetime string, rendered as a relative time. |
30
+
31
+ ## Events
32
+
33
+ _None._
34
+
35
+ ## Slots
36
+
37
+ _None._
38
+
39
+ ## CSS custom properties
40
+
41
+ | Custom property |
42
+ | --- |
43
+ | `--ui-border` |
44
+ | `--ui-font` |
45
+ | `--ui-font-size` |
46
+ | `--ui-font-size-sm` |
47
+ | `--ui-font-weight-semibold` |
48
+ | `--ui-line-height-normal` |
49
+ | `--ui-line-height-tight` |
50
+ | `--ui-primary` |
51
+ | `--ui-text` |
52
+ | `--ui-text-muted` |
package/llms.txt CHANGED
@@ -16,6 +16,31 @@ render correctly with zero external CSS. Override any `--ui-*` property on
16
16
  `:root` (or an ancestor) to retheme, or import the optional
17
17
  `@f-ewald/components/tokens.css` stylesheet as a starting point.
18
18
 
19
+ ## <action-bar>
20
+
21
+ Toolbar that sits directly above a list or table: a left cluster for
22
+ search and filters and a right cluster for record actions (create, delete,
23
+ bulk actions). It's a presentational layout container only — drop any
24
+ controls (`autocomplete-input`, `multi-select`, `ui-button`, …) into the
25
+ `start` and `end` slots; the bar owns none of their behavior and adds no
26
+ search field of its own. The two clusters wrap onto separate rows when the
27
+ bar is too narrow.
28
+
29
+ Import: `import "@f-ewald/components/action-bar.js";`
30
+
31
+ Properties: none
32
+ Events: none
33
+ CSS custom properties: none
34
+
35
+ Example:
36
+ ```html
37
+ <action-bar>
38
+ <autocomplete-input slot="start" placeholder="Search…"></autocomplete-input>
39
+ <ui-button slot="end" variant="secondary">Delete</ui-button>
40
+ <ui-button slot="end" variant="primary">Create</ui-button>
41
+ </action-bar>
42
+ ```
43
+
19
44
  ## <address-autocomplete>
20
45
 
21
46
  Form-associated text input with a suggestion dropdown. Works as a
@@ -61,6 +86,89 @@ Example:
61
86
  <animate-confetti duration="6000"></animate-confetti>
62
87
  ```
63
88
 
89
+ ## <app-shell>
90
+
91
+ The dashboard page shell: a slot-based CSS-grid backbone that arranges a
92
+ full-height sidebar, a top bar, the main content, an optional right-hand
93
+ detail column, and an optional footer. It owns the responsive behavior so
94
+ consumers don't re-implement it — above the shared 48rem breakpoint the
95
+ sidebar collapses to an icon rail and the detail region is an inline column;
96
+ at or below it the sidebar becomes an off-canvas drawer and the detail region
97
+ an overlay, both dismissed by a scrim or Escape.
98
+
99
+ Widths are tunable per instance via `--component-sidebar-width` (16rem),
100
+ `--component-sidebar-rail-width` (3.5rem), and `--component-topbar-height`
101
+ (3rem); the detail column reuses the 20rem/25rem panel widths. The main
102
+ content area is white by default — override it with
103
+ `--component-main-background`. Give the shell a height (e.g. `height: 100vh`)
104
+ so the sidebar and main can size and scroll.
105
+
106
+ The built-in top-bar button toggles the sidebar, and so does pressing `[`
107
+ anywhere on the page (ignored while typing in a text field or with a
108
+ modifier held). Hovering or keyboard-focusing the toggle reveals a tooltip
109
+ naming the action and its `[` shortcut — the shortcut is not shown as
110
+ permanent chrome.
111
+
112
+ Import: `import "@f-ewald/components/app-shell.js";`
113
+
114
+ Properties: `sidebarCollapsed` (attribute `sidebar-collapsed`) : boolean, default false; `detailOpen` (attribute `detail-open`) : boolean, default false; `detailWidth` (attribute `detail-width`) : "compact" | "comfortable", default "compact"
115
+ Events: `sidebar-toggle`, `detail-close`
116
+ CSS custom properties: `--ui-border`, `--ui-focus-ring`, `--ui-font-size-sm`, `--ui-line-height-tight`, `--ui-on-accent`, `--ui-overlay`, `--ui-radius-sm`, `--ui-shadow`, `--ui-shadow-lg`, `--ui-surface`, `--ui-surface-muted`, `--ui-text-muted`, `--ui-tooltip`
117
+
118
+ Example:
119
+ ```html
120
+ <app-shell detail-open style="height: 100vh">
121
+ <app-sidebar slot="sidebar">
122
+ <a href="/dashboard" aria-current="page" aria-label="Dashboard">
123
+ <!-- icon --><span style="display: var(--app-sidebar-label, inline)">Dashboard</span>
124
+ </a>
125
+ </app-sidebar>
126
+ <page-header slot="topbar" heading="Members"></page-header>
127
+ <action-bar>
128
+ <autocomplete-input slot="start" placeholder="Search…"></autocomplete-input>
129
+ <ui-button slot="end" variant="primary">Create</ui-button>
130
+ </action-bar>
131
+ <data-table></data-table>
132
+ <div slot="detail">Selected record…</div>
133
+ <pagination-nav slot="footer" total-pages="5"></pagination-nav>
134
+ </app-shell>
135
+ ```
136
+
137
+ ## <app-sidebar>
138
+
139
+ Collapsible navigation sidebar for the `app-shell` sidebar slot. It is
140
+ deliberately presentational and router-agnostic: the consumer supplies the
141
+ nav items as plain `<a>`/`<button>` elements (each an icon followed by a
142
+ label), optional `<p>` group headings, and marks the active item with
143
+ `aria-current="page"`. The sidebar styles them, tracks hover/active/focus,
144
+ and — in collapsed "rail" mode — centers the icons and hides the labels.
145
+
146
+ Rail mode is driven by the `collapsed` attribute (the parent `app-shell`
147
+ toggles it). Labels are hidden by the inherited `--app-sidebar-label` custom
148
+ property, so each label element should read it, e.g.
149
+ `<span style="display: var(--app-sidebar-label, inline)">Reports</span>`;
150
+ keep an `aria-label` on the item so its accessible name survives collapse.
151
+ The sidebar fills the width and height of its container — `app-shell` owns
152
+ the actual rail/expanded width.
153
+
154
+ Import: `import "@f-ewald/components/app-sidebar.js";`
155
+
156
+ Properties: `collapsed` (attribute `collapsed`) : boolean, default false
157
+ Events: none
158
+ CSS custom properties: `--ui-border`, `--ui-focus-ring`, `--ui-font`, `--ui-font-size-sm`, `--ui-font-size-xs`, `--ui-font-weight-medium`, `--ui-font-weight-semibold`, `--ui-line-height-tight`, `--ui-primary`, `--ui-radius-sm`, `--ui-surface`, `--ui-surface-muted`, `--ui-text`, `--ui-text-muted`, `--ui-tracking-wide`
159
+
160
+ Example:
161
+ ```html
162
+ <app-sidebar>
163
+ <a href="/dashboard" aria-current="page" aria-label="Dashboard">
164
+ <!-- icon --><span style="display: var(--app-sidebar-label, inline)">Dashboard</span>
165
+ </a>
166
+ <a href="/members" aria-label="Members">
167
+ <!-- icon --><span style="display: var(--app-sidebar-label, inline)">Members</span>
168
+ </a>
169
+ </app-sidebar>
170
+ ```
171
+
64
172
  ## <autocomplete-input>
65
173
 
66
174
  Generic form-associated text input with a suggestion dropdown, for any
@@ -390,6 +498,33 @@ Example:
390
498
  <editable-text multiline placeholder="Add a description…" label="Description"></editable-text>
391
499
  ```
392
500
 
501
+ ## <form-actions>
502
+
503
+ Form footer button bar with a fixed action order for internal apps: the
504
+ primary (submit) button is always rightmost, the secondary (cancel) button
505
+ sits to its immediate left, and an optional tertiary/destructive action is
506
+ pinned to the far left. The order is enforced by the component regardless of
507
+ the source order the buttons are authored in, so every form in a product
508
+ reads the same way.
509
+
510
+ Purely presentational: it lays out whatever controls (usually `ui-button`)
511
+ are slotted and never intercepts their clicks, `type="submit"`, or events.
512
+
513
+ Import: `import "@f-ewald/components/form-actions.js";`
514
+
515
+ Properties: none
516
+ Events: none
517
+ CSS custom properties: none
518
+
519
+ Example:
520
+ ```html
521
+ <form-actions>
522
+ <ui-button slot="start" variant="danger">Delete</ui-button>
523
+ <ui-button slot="secondary" variant="secondary">Cancel</ui-button>
524
+ <ui-button slot="primary" type="submit" variant="primary">Save</ui-button>
525
+ </form-actions>
526
+ ```
527
+
393
528
  ## <form-select>
394
529
 
395
530
  A styled dropdown select: a trigger button showing the current option's
@@ -780,6 +915,49 @@ Example:
780
915
  </script>
781
916
  ```
782
917
 
918
+ ## <page-header>
919
+
920
+ Page title block for the top of a dashboard view: an optional breadcrumb
921
+ trail, the page heading, and a right-aligned cluster of page-level actions.
922
+ It only lays these out — the breadcrumb links and action buttons are entirely
923
+ the consumer's, so it stays framework- and router-agnostic.
924
+
925
+ Import: `import "@f-ewald/components/page-header.js";`
926
+
927
+ Properties: `heading` (attribute `heading`) : string, default ""
928
+ Events: none
929
+ CSS custom properties: `--ui-font`, `--ui-font-size-lg`, `--ui-font-size-sm`, `--ui-font-weight-semibold`, `--ui-line-height-tight`, `--ui-text`, `--ui-text-muted`
930
+
931
+ Example:
932
+ ```html
933
+ <page-header heading="Team members">
934
+ <nav slot="breadcrumb" aria-label="Breadcrumb">Home / Settings / Members</nav>
935
+ <ui-button slot="actions" variant="primary">Invite</ui-button>
936
+ </page-header>
937
+ ```
938
+
939
+ ## <pagination-nav>
940
+
941
+ Minimal, controlled pager for list/table views: a previous/next control pair
942
+ around a "Page N of M" status. It owns no data — the consumer sets
943
+ `current-page` / `total-pages` and moves the page in response to the
944
+ `page-change` event (typically alongside its own data fetch), exactly like
945
+ `data-table` leaves the row data to the caller.
946
+
947
+ Previous is disabled on the first page and next on the last; neither fires an
948
+ event when there is nowhere to go.
949
+
950
+ Import: `import "@f-ewald/components/pagination-nav.js";`
951
+
952
+ Properties: `currentPage` (attribute `current-page`) : number, default 1; `totalPages` (attribute `total-pages`) : number, default 1
953
+ Events: `page-change`
954
+ CSS custom properties: `--ui-border`, `--ui-focus-ring`, `--ui-font`, `--ui-font-size-sm`, `--ui-font-weight-medium`, `--ui-line-height-tight`, `--ui-radius-sm`, `--ui-surface-muted`, `--ui-text`, `--ui-text-muted`
955
+
956
+ Example:
957
+ ```html
958
+ <pagination-nav current-page="1" total-pages="5"></pagination-nav>
959
+ ```
960
+
783
961
  ## <percent-bar-chart>
784
962
 
785
963
  Horizontal bar chart for labeled percentage rows, using D3's linear scale.
@@ -1095,6 +1273,57 @@ Example:
1095
1273
  </script>
1096
1274
  ```
1097
1275
 
1276
+ ## <timeline-container>
1277
+
1278
+ Vertical timeline: a single connecting line runs down the left edge and each
1279
+ slotted `timeline-entry` places a dot on it. This is a thin layout and
1280
+ semantics wrapper — the entries draw the line segments and dots themselves,
1281
+ so the container adds no gap between them (a gap would break the line).
1282
+ Exposed to assistive technology as a list of events.
1283
+
1284
+ Import: `import "@f-ewald/components/timeline-container.js";`
1285
+
1286
+ Properties: none
1287
+ Events: none
1288
+ CSS custom properties: none
1289
+
1290
+ Example:
1291
+ ```html
1292
+ <timeline-container>
1293
+ <timeline-entry datetime="2026-07-23T09:00:00Z">
1294
+ <span slot="headline">Deployment started</span>
1295
+ Release v1.4.0 is rolling out.
1296
+ </timeline-entry>
1297
+ <timeline-entry datetime="2026-07-23T08:45:00Z">
1298
+ <span slot="headline">Review approved</span>
1299
+ <status-pill label="In Review" color="info"></status-pill>
1300
+ </timeline-entry>
1301
+ </timeline-container>
1302
+ ```
1303
+
1304
+ ## <timeline-entry>
1305
+
1306
+ One event on a `timeline-container`: a dot on the vertical line, an optional
1307
+ headline, a relative timestamp ("3 hours ago"), and freely nested content.
1308
+ The connecting line is drawn here — its segment above the dot is hidden on
1309
+ the first entry and the segment below is hidden on the last, so the line caps
1310
+ exactly at the first and last dots. Only meaningful inside a
1311
+ `timeline-container`; demonstrated through it.
1312
+
1313
+ Import: `import "@f-ewald/components/timeline-entry.js";`
1314
+
1315
+ Properties: `datetime` (attribute `datetime`) : string | null, default null
1316
+ Events: none
1317
+ CSS custom properties: `--ui-border`, `--ui-font`, `--ui-font-size`, `--ui-font-size-sm`, `--ui-font-weight-semibold`, `--ui-line-height-normal`, `--ui-line-height-tight`, `--ui-primary`, `--ui-text`, `--ui-text-muted`
1318
+
1319
+ Example:
1320
+ ```html
1321
+ <timeline-entry datetime="2026-07-23T09:00:00Z">
1322
+ <span slot="headline">Deployment started</span>
1323
+ Release v1.4.0 is rolling out to production.
1324
+ </timeline-entry>
1325
+ ```
1326
+
1098
1327
  ## <toast-notification>
1099
1328
 
1100
1329
  Fixed-position stack of dismissible notifications, anchored top-right
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@f-ewald/components",
3
3
  "private": false,
4
- "version": "1.2.1",
4
+ "version": "1.3.0",
5
5
  "description": "A collection of universally usable web components for various tasks.",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",