@pienter/ui 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,30 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.10.0 - 2026-09-15
6
+
7
+ ### Changed
8
+
9
+ - Index renders its title, description and actions through the new
10
+ `PageHeader`, so the `pui-index__header`, `pui-index__heading`,
11
+ `pui-index__title`, `pui-index__description` and `pui-index__actions`
12
+ classes are gone. They were never documented as public API; a consumer
13
+ style that targeted them moves to `pui-page-header` and its
14
+ `__heading`, `__title`, `__description` and `__actions` elements. Markup,
15
+ spacing and type are unchanged in both placements.
16
+
17
+ ### Added
18
+
19
+ - `PageHeader` at `@pienter/ui/components/PageHeader.vue`: the title,
20
+ description and actions band above a module index or a record page,
21
+ extracted from Index so detail and form pages no longer hand-roll the same
22
+ header. `title` and `description` are plain strings, `level` (`1` | `2` |
23
+ `3`, default `1`) picks the heading element and sizes levels 2 and 3 as
24
+ section headings, `title-id` sets the title's `id` for `aria-labelledby`,
25
+ and `#actions` fills the trailing row. It renders nothing when it has no
26
+ title, description or actions. Index uses it at level 1 in module placement
27
+ and level 2 in related placement.
28
+
5
29
  ## 0.9.0 - 2026-09-15
6
30
 
7
31
  ### Added
@@ -4,26 +4,16 @@
4
4
  :data-placement="placement"
5
5
  :data-state="loading ? 'loading' : undefined"
6
6
  >
7
- <header
8
- v-if="title || description || $slots.actions"
9
- class="pui-index__header"
7
+ <PageHeader
8
+ :title="title"
9
+ :description="description"
10
+ :level="placement === 'related' ? 2 : 1"
11
+ :title-id="titleId"
10
12
  >
11
- <div v-if="title || description" class="pui-index__heading">
12
- <component
13
- :is="placement === 'related' ? 'h2' : 'h1'"
14
- v-if="title"
15
- :id="titleId"
16
- class="pui-index__title"
17
- >{{ title }}</component
18
- >
19
- <p v-if="description" class="pui-index__description">
20
- {{ description }}
21
- </p>
22
- </div>
23
- <div v-if="$slots.actions" class="pui-index__actions">
13
+ <template v-if="$slots.actions" #actions>
24
14
  <slot name="actions" :reload="reload" :loading="loading" />
25
- </div>
26
- </header>
15
+ </template>
16
+ </PageHeader>
27
17
 
28
18
  <div class="pui-index__body">
29
19
  <div
@@ -172,6 +162,7 @@
172
162
  <script setup lang="ts" generic="T extends object">
173
163
  import { computed, watch } from 'vue';
174
164
  import DataTable from '../table/DataTable.vue';
165
+ import PageHeader from '../page-header/PageHeader.vue';
175
166
  import type { Column } from '../table/types.js';
176
167
  import TextInput from '../../form/text-input/TextInput.vue';
177
168
  import Select from '../../form/select/Select.vue';
@@ -6,37 +6,6 @@
6
6
  color: var(--text-clr-base);
7
7
  }
8
8
 
9
- .pui-index__header {
10
- display: grid;
11
- grid-template-columns: minmax(0, 1fr) auto;
12
- align-items: center;
13
- justify-content: space-between;
14
- gap: var(--space-s);
15
- padding-block-end: var(--space-s);
16
- border-block-end: var(--stroke-sm) solid var(--border-clr-subtle);
17
- }
18
-
19
- .pui-index__heading {
20
- display: grid;
21
- gap: var(--space-3xs);
22
- min-inline-size: 0;
23
- }
24
-
25
- .pui-index__title {
26
- margin: 0;
27
- font-family: var(--ff-display);
28
- font-size: var(--step-2);
29
- font-weight: var(--fw-display);
30
- letter-spacing: var(--ls-display);
31
- }
32
-
33
- .pui-index__description {
34
- margin: 0;
35
- font-size: var(--step--1);
36
- color: var(--text-clr-muted);
37
- }
38
-
39
- .pui-index__actions,
40
9
  .pui-index__selection {
41
10
  display: flex;
42
11
  flex-wrap: wrap;
@@ -123,16 +92,10 @@
123
92
  border: var(--stroke-sm) solid var(--border-clr-subtle);
124
93
  border-radius: var(--radius-md);
125
94
 
126
- & > .pui-index__header {
95
+ & > .pui-page-header {
127
96
  padding: var(--space-s);
128
97
  }
129
98
 
130
- & .pui-index__title {
131
- font-size: var(--step-1);
132
- font-weight: var(--fw-semibold);
133
- letter-spacing: normal;
134
- }
135
-
136
99
  & .pui-index__toolbar {
137
100
  padding: var(--space-s);
138
101
  border-block-end: var(--stroke-sm) solid var(--border-clr-subtle);
@@ -0,0 +1,49 @@
1
+ <template>
2
+ <header
3
+ v-if="title || description || $slots.actions"
4
+ class="pui-page-header"
5
+ >
6
+ <div v-if="title || description" class="pui-page-header__heading">
7
+ <component
8
+ :is="`h${level}`"
9
+ v-if="title"
10
+ :id="titleId"
11
+ class="pui-page-header__title"
12
+ >{{ title }}</component
13
+ >
14
+ <p v-if="description" class="pui-page-header__description">
15
+ {{ description }}
16
+ </p>
17
+ </div>
18
+ <div v-if="$slots.actions" class="pui-page-header__actions">
19
+ <slot name="actions" />
20
+ </div>
21
+ </header>
22
+ </template>
23
+
24
+ <script setup lang="ts">
25
+ withDefaults(
26
+ defineProps<{
27
+ title?: string;
28
+ description?: string;
29
+ /** Heading level of the title: `1` for a page, `2` or `3` for a band inside one. */
30
+ level?: 1 | 2 | 3;
31
+ /** `id` for the title element, so a region can reference it through `aria-labelledby`. */
32
+ titleId?: string;
33
+ }>(),
34
+ {
35
+ title: undefined,
36
+ description: undefined,
37
+ level: 1,
38
+ titleId: undefined,
39
+ },
40
+ );
41
+
42
+ defineSlots<{
43
+ actions?: () => unknown;
44
+ }>();
45
+ </script>
46
+
47
+ <style>
48
+ @import './page-header.css';
49
+ </style>
@@ -0,0 +1,44 @@
1
+ @layer components {
2
+ .pui-page-header {
3
+ display: grid;
4
+ grid-template-columns: minmax(0, 1fr) auto;
5
+ align-items: center;
6
+ justify-content: space-between;
7
+ gap: var(--space-s);
8
+ padding-block-end: var(--space-s);
9
+ border-block-end: var(--stroke-sm) solid var(--border-clr-subtle);
10
+ }
11
+
12
+ .pui-page-header__heading {
13
+ display: grid;
14
+ gap: var(--space-3xs);
15
+ min-inline-size: 0;
16
+ }
17
+
18
+ .pui-page-header__title {
19
+ margin: 0;
20
+ font-family: var(--ff-display);
21
+ font-size: var(--step-2);
22
+ font-weight: var(--fw-display);
23
+ letter-spacing: var(--ls-display);
24
+
25
+ &:is(h2, h3) {
26
+ font-size: var(--step-1);
27
+ font-weight: var(--fw-semibold);
28
+ letter-spacing: normal;
29
+ }
30
+ }
31
+
32
+ .pui-page-header__description {
33
+ margin: 0;
34
+ font-size: var(--step--1);
35
+ color: var(--text-clr-muted);
36
+ }
37
+
38
+ .pui-page-header__actions {
39
+ display: flex;
40
+ flex-wrap: wrap;
41
+ align-items: center;
42
+ gap: var(--space-xs);
43
+ }
44
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pienter/ui",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Shared Pienter UI components, styles, icons, and browser utilities.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -63,6 +63,7 @@
63
63
  "./components/Card.vue": "./components/layout/card/Card.vue",
64
64
  "./components/Collapsible.vue": "./components/layout/collapsible/Collapsible.vue",
65
65
  "./components/AppLayout.vue": "./components/layout/app-layout/AppLayout.vue",
66
+ "./components/PageHeader.vue": "./components/layout/page-header/PageHeader.vue",
66
67
  "./components/Separator.vue": "./components/layout/separator/Separator.vue",
67
68
  "./components/Table.vue": "./components/layout/table/Table.vue",
68
69
  "./components/TableRow.vue": "./components/layout/table/TableRow.vue",