@happyvertical/smrt-ui 0.51.38 → 0.52.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/AGENTS.md +14 -2
- package/README.md +257 -5
- package/dist/actions/__tests__/swipe-dismiss.test.js +69 -0
- package/dist/actions/swipe-dismiss.d.ts +42 -0
- package/dist/actions/swipe-dismiss.d.ts.map +1 -0
- package/dist/actions/swipe-dismiss.js +69 -0
- package/dist/components/calendar/Calendar.svelte +5 -0
- package/dist/components/calendar/Calendar.svelte.d.ts.map +1 -1
- package/dist/components/calendar/CalendarView.svelte +1250 -0
- package/dist/components/calendar/CalendarView.svelte.d.ts +66 -0
- package/dist/components/calendar/CalendarView.svelte.d.ts.map +1 -0
- package/dist/components/calendar/DayView.svelte +5 -0
- package/dist/components/calendar/DayView.svelte.d.ts.map +1 -1
- package/dist/components/calendar/__tests__/CalendarView.test.js +226 -0
- package/dist/components/calendar/__tests__/calendar-model.test.js +138 -0
- package/dist/components/calendar/calendar-model.d.ts +178 -0
- package/dist/components/calendar/calendar-model.d.ts.map +1 -0
- package/dist/components/calendar/calendar-model.js +362 -0
- package/dist/components/calendar/index.d.ts +12 -1
- package/dist/components/calendar/index.d.ts.map +1 -1
- package/dist/components/calendar/index.js +11 -1
- package/dist/components/data/CollectionList.svelte +23 -5
- package/dist/components/data/CollectionList.svelte.d.ts +8 -2
- package/dist/components/data/CollectionList.svelte.d.ts.map +1 -1
- package/dist/components/data/DataTable.svelte +118 -40
- package/dist/components/data/DataTable.svelte.d.ts.map +1 -1
- package/dist/components/data/DataTableController.d.ts +4 -0
- package/dist/components/data/DataTableController.d.ts.map +1 -1
- package/dist/components/data/DataTableController.js +11 -4
- package/dist/components/data/ListSortSelect.svelte +121 -0
- package/dist/components/data/ListSortSelect.svelte.d.ts +22 -0
- package/dist/components/data/ListSortSelect.svelte.d.ts.map +1 -0
- package/dist/components/data/SortableHeader.svelte +169 -0
- package/dist/components/data/SortableHeader.svelte.d.ts +30 -0
- package/dist/components/data/SortableHeader.svelte.d.ts.map +1 -0
- package/dist/components/data/__tests__/CollectionList.test.js +37 -0
- package/dist/components/data/__tests__/DataTable.test.js +76 -0
- package/dist/components/data/__tests__/DataTableController.test.js +35 -0
- package/dist/components/data/__tests__/SortableHeader.test.js +101 -0
- package/dist/components/data/__tests__/link-step-surface.test.js +172 -0
- package/dist/components/data/__tests__/list-sort-state.svelte.test.js +71 -0
- package/dist/components/data/__tests__/list-sort.test.js +91 -0
- package/dist/components/data/__tests__/surface-navigation.test.js +121 -0
- package/dist/components/data/data-surface.d.ts +1 -0
- package/dist/components/data/data-surface.d.ts.map +1 -1
- package/dist/components/data/data-surface.js +1 -0
- package/dist/components/data/index.d.ts +6 -0
- package/dist/components/data/index.d.ts.map +1 -1
- package/dist/components/data/index.js +6 -0
- package/dist/components/data/link-surface.d.ts +79 -0
- package/dist/components/data/link-surface.d.ts.map +1 -0
- package/dist/components/data/link-surface.js +155 -0
- package/dist/components/data/list-sort-state.svelte.d.ts +39 -0
- package/dist/components/data/list-sort-state.svelte.d.ts.map +1 -0
- package/dist/components/data/list-sort-state.svelte.js +47 -0
- package/dist/components/data/list-sort.d.ts +94 -0
- package/dist/components/data/list-sort.d.ts.map +1 -0
- package/dist/components/data/list-sort.js +182 -0
- package/dist/components/data/step-surface.d.ts +73 -0
- package/dist/components/data/step-surface.d.ts.map +1 -0
- package/dist/components/data/step-surface.js +177 -0
- package/dist/components/data/surface-navigation.d.ts +49 -0
- package/dist/components/data/surface-navigation.d.ts.map +1 -0
- package/dist/components/data/surface-navigation.js +100 -0
- package/dist/components/data/types.d.ts +19 -0
- package/dist/components/data/types.d.ts.map +1 -1
- package/dist/components/feedback/PhoneSheet.svelte +224 -0
- package/dist/components/feedback/PhoneSheet.svelte.d.ts +35 -0
- package/dist/components/feedback/PhoneSheet.svelte.d.ts.map +1 -0
- package/dist/components/feedback/WorkingStrip.svelte +426 -0
- package/dist/components/feedback/WorkingStrip.svelte.d.ts +37 -0
- package/dist/components/feedback/WorkingStrip.svelte.d.ts.map +1 -0
- package/dist/components/feedback/__tests__/PhoneSheet.test.js +101 -0
- package/dist/components/feedback/__tests__/WorkingStrip.test.js +172 -0
- package/dist/components/feedback/index.d.ts +4 -0
- package/dist/components/feedback/index.d.ts.map +1 -1
- package/dist/components/feedback/index.js +5 -0
- package/dist/components/feedback/working-status.d.ts +34 -0
- package/dist/components/feedback/working-status.d.ts.map +1 -0
- package/dist/components/feedback/working-status.js +5 -0
- package/dist/components/forms/Checkbox.svelte +26 -2
- package/dist/components/forms/Combobox.svelte +68 -12
- package/dist/components/forms/Combobox.svelte.d.ts +6 -0
- package/dist/components/forms/Combobox.svelte.d.ts.map +1 -1
- package/dist/components/forms/DictationButton.svelte +128 -0
- package/dist/components/forms/DictationButton.svelte.d.ts +19 -0
- package/dist/components/forms/DictationButton.svelte.d.ts.map +1 -0
- package/dist/components/forms/DictationStatus.svelte +164 -0
- package/dist/components/forms/DictationStatus.svelte.d.ts +11 -0
- package/dist/components/forms/DictationStatus.svelte.d.ts.map +1 -0
- package/dist/components/forms/Fieldset.svelte +5 -1
- package/dist/components/forms/Fieldset.svelte.d.ts +2 -0
- package/dist/components/forms/Fieldset.svelte.d.ts.map +1 -1
- package/dist/components/forms/FormActionBar.svelte +84 -0
- package/dist/components/forms/FormActionBar.svelte.d.ts +23 -0
- package/dist/components/forms/FormActionBar.svelte.d.ts.map +1 -0
- package/dist/components/forms/Input.svelte +9 -0
- package/dist/components/forms/Radio.svelte +15 -4
- package/dist/components/forms/SearchInput.svelte +207 -0
- package/dist/components/forms/SearchInput.svelte.d.ts +22 -0
- package/dist/components/forms/SearchInput.svelte.d.ts.map +1 -0
- package/dist/components/forms/Select.svelte +9 -0
- package/dist/components/forms/StagedControlReview.svelte +21 -12
- package/dist/components/forms/StagedControlReview.svelte.d.ts.map +1 -1
- package/dist/components/forms/Switch.svelte +11 -5
- package/dist/components/forms/TagsInput.svelte +8 -1
- package/dist/components/forms/Textarea.svelte +8 -0
- package/dist/components/forms/__tests__/Combobox.test.js +87 -0
- package/dist/components/forms/__tests__/ControlHitArea.test.js +73 -0
- package/dist/components/forms/__tests__/DictationButton.test.js +176 -0
- package/dist/components/forms/__tests__/Fieldset.test.js +37 -0
- package/dist/components/forms/__tests__/FormActionBar.test.js +22 -0
- package/dist/components/forms/__tests__/SearchInput.test.js +92 -0
- package/dist/components/forms/__tests__/StagedControlReview.test.js +37 -3
- package/dist/components/forms/__tests__/combobox-labels.fixture.svelte +19 -0
- package/dist/components/forms/__tests__/combobox-labels.fixture.svelte.d.ts +11 -0
- package/dist/components/forms/__tests__/combobox-labels.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/composite-registration.fixture.svelte +29 -0
- package/dist/components/forms/__tests__/composite-registration.fixture.svelte.d.ts +8 -0
- package/dist/components/forms/__tests__/composite-registration.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/control-interaction.test.js +19 -1
- package/dist/components/forms/__tests__/control-proposals.test.js +63 -0
- package/dist/components/forms/__tests__/dictation-recording.test.js +582 -0
- package/dist/components/forms/__tests__/dictation.test.js +486 -0
- package/dist/components/forms/__tests__/long-press.test.js +175 -0
- package/dist/components/forms/__tests__/place-picker.fixture.svelte +33 -0
- package/dist/components/forms/__tests__/place-picker.fixture.svelte.d.ts +12 -0
- package/dist/components/forms/__tests__/place-picker.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/audio-capture.d.ts +69 -0
- package/dist/components/forms/audio-capture.d.ts.map +1 -0
- package/dist/components/forms/audio-capture.js +193 -0
- package/dist/components/forms/control-interaction.d.ts +6 -0
- package/dist/components/forms/control-interaction.d.ts.map +1 -1
- package/dist/components/forms/control-interaction.js +12 -1
- package/dist/components/forms/control-proposals.d.ts +55 -0
- package/dist/components/forms/control-proposals.d.ts.map +1 -0
- package/dist/components/forms/control-proposals.js +188 -0
- package/dist/components/forms/dictation-transcribe.d.ts +36 -0
- package/dist/components/forms/dictation-transcribe.d.ts.map +1 -0
- package/dist/components/forms/dictation-transcribe.js +65 -0
- package/dist/components/forms/dictation.svelte.d.ts +200 -0
- package/dist/components/forms/dictation.svelte.d.ts.map +1 -0
- package/dist/components/forms/dictation.svelte.js +686 -0
- package/dist/components/forms/index.d.ts +14 -1
- package/dist/components/forms/index.d.ts.map +1 -1
- package/dist/components/forms/index.js +14 -1
- package/dist/components/forms/insert-text.d.ts +12 -0
- package/dist/components/forms/insert-text.d.ts.map +1 -0
- package/dist/components/forms/insert-text.js +38 -0
- package/dist/components/forms/long-press.d.ts +88 -0
- package/dist/components/forms/long-press.d.ts.map +1 -0
- package/dist/components/forms/long-press.js +213 -0
- package/dist/components/forms/ready-beep.d.ts +24 -0
- package/dist/components/forms/ready-beep.d.ts.map +1 -0
- package/dist/components/forms/ready-beep.js +82 -0
- package/dist/components/layout/PageHeader.svelte +151 -17
- package/dist/components/layout/PageHeader.svelte.d.ts +47 -7
- package/dist/components/layout/PageHeader.svelte.d.ts.map +1 -1
- package/dist/components/layout/__tests__/PageHeader.test.js +87 -1
- package/dist/components/layout/index.d.ts +1 -0
- package/dist/components/layout/index.d.ts.map +1 -1
- package/dist/components/layout/index.js +1 -0
- package/dist/components/layout/page-header-context.d.ts +25 -0
- package/dist/components/layout/page-header-context.d.ts.map +1 -0
- package/dist/components/layout/page-header-context.js +16 -0
- package/dist/components/nav/Tabs.svelte +241 -0
- package/dist/components/nav/Tabs.svelte.d.ts +15 -0
- package/dist/components/nav/Tabs.svelte.d.ts.map +1 -1
- package/dist/components/nav/__tests__/LinkTabs.test.js +117 -0
- package/dist/components/nav/index.d.ts +1 -0
- package/dist/components/nav/index.d.ts.map +1 -1
- package/dist/components/nav/index.js +1 -0
- package/dist/components/nav/tabs-overflow.d.ts +12 -0
- package/dist/components/nav/tabs-overflow.d.ts.map +1 -0
- package/dist/components/nav/tabs-overflow.js +17 -0
- package/dist/components/nav/types.d.ts +8 -0
- package/dist/components/nav/types.d.ts.map +1 -1
- package/dist/components/ui/Button.svelte +7 -0
- package/dist/components/ui/Card.svelte +14 -5
- package/dist/components/ui/Dropdown.svelte +55 -1
- package/dist/components/ui/Dropdown.svelte.d.ts +8 -0
- package/dist/components/ui/Dropdown.svelte.d.ts.map +1 -1
- package/dist/components/ui/IconToggle.svelte +139 -0
- package/dist/components/ui/IconToggle.svelte.d.ts +32 -0
- package/dist/components/ui/IconToggle.svelte.d.ts.map +1 -0
- package/dist/components/ui/IconToggleGroup.svelte +56 -0
- package/dist/components/ui/IconToggleGroup.svelte.d.ts +21 -0
- package/dist/components/ui/IconToggleGroup.svelte.d.ts.map +1 -0
- package/dist/components/ui/Pagination.svelte +1 -1
- package/dist/components/ui/Popover.svelte +17 -1
- package/dist/components/ui/Popover.svelte.d.ts +8 -0
- package/dist/components/ui/Popover.svelte.d.ts.map +1 -1
- package/dist/components/ui/__tests__/IconToggle.test.js +59 -0
- package/dist/components/ui/__tests__/IconToggleGroup.test.js +30 -0
- package/dist/components/ui/__tests__/Popover.test.js +44 -0
- package/dist/components/ui/__tests__/dropdown-popover-icon.test.js +125 -0
- package/dist/components/ui/__tests__/phone-touch-targets.test.js +39 -0
- package/dist/components/ui/index.d.ts +2 -0
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +2 -0
- package/dist/i18n/strings.ui.d.ts +41 -0
- package/dist/i18n/strings.ui.d.ts.map +1 -1
- package/dist/i18n/strings.ui.js +46 -0
- package/dist/svelte/__tests__/playground.test.js +1 -0
- package/dist/svelte/playground/CalendarPreview.svelte +140 -0
- package/dist/svelte/playground/CalendarPreview.svelte.d.ts +4 -0
- package/dist/svelte/playground/CalendarPreview.svelte.d.ts.map +1 -0
- package/dist/svelte/playground.d.ts.map +1 -1
- package/dist/svelte/playground.js +10 -0
- package/dist/types-generic.d.ts +2 -0
- package/dist/types-generic.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -1,41 +1,107 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
/**
|
|
3
|
-
* PageHeader -
|
|
4
|
-
*
|
|
3
|
+
* PageHeader - one header per page: an ancestors-only breadcrumb row, the
|
|
4
|
+
* page title (`<h1>`), and the page's own actions, right-aligned.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* Page-header contract (shared with AdminShell in smrt-svelte/workspace):
|
|
7
|
+
* - `crumbs` are the page's ancestors only, never the page itself; none on
|
|
8
|
+
* section homes. Inside a shell that sets `setPageHeaderContext()`, pages
|
|
9
|
+
* pass only `parents` (ancestors the nav doesn't know, e.g. the article
|
|
10
|
+
* above its video) and the shell adds the rest; the shell also hears the
|
|
11
|
+
* title (for its phone top bar). smrt-svelte's `shellPageTrailFor()`
|
|
12
|
+
* computes such a trail from a nav.
|
|
13
|
+
* - The crumb row carries `data-shell-breadcrumbs` and the title
|
|
14
|
+
* `data-shell-page-title`: on phones AdminShell hides the crumbs and
|
|
15
|
+
* visually hides the title, because its phone top bar shows the back arrow
|
|
16
|
+
* and the title instead.
|
|
17
|
+
* - No in-page back link: the crumbs (and the phone top bar) are the way
|
|
18
|
+
* back. `backHref` is kept only for pages outside such a shell.
|
|
19
|
+
* - An editable title (`titleField`, e.g. an article's headline) renders
|
|
20
|
+
* inside the `<h1>`, so the heading is still the page's title in the
|
|
21
|
+
* outline and is named by the field's value, while the field keeps its own
|
|
22
|
+
* label. Such a heading has no `data-shell-page-title`: it stays visible on
|
|
23
|
+
* phones, because it is the input. Pass the live value as `title` so the
|
|
24
|
+
* shell (the phone top bar) follows along as the person types.
|
|
8
25
|
*/
|
|
9
26
|
import type { Snippet } from 'svelte';
|
|
10
27
|
import { ripple } from '../../actions/ripple.js';
|
|
11
28
|
import { Icon } from '../display/index.js';
|
|
29
|
+
import {
|
|
30
|
+
getPageHeaderContext,
|
|
31
|
+
type PageHeaderCrumb,
|
|
32
|
+
} from './page-header-context.js';
|
|
12
33
|
|
|
13
34
|
export interface Props {
|
|
14
35
|
/** Page title */
|
|
15
36
|
title: string;
|
|
16
37
|
/** Optional subtitle/description */
|
|
17
38
|
subtitle?: string;
|
|
18
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* All ancestors of this page, outermost first (never the page itself).
|
|
41
|
+
* Overrides the shell's trail; usually pass `parents` instead.
|
|
42
|
+
*/
|
|
43
|
+
crumbs?: readonly PageHeaderCrumb[];
|
|
44
|
+
/** Ancestors past the shell's nav, outermost first. */
|
|
45
|
+
parents?: readonly PageHeaderCrumb[];
|
|
46
|
+
/** Shorter title for the shell (e.g. its phone top bar); default `title`. */
|
|
47
|
+
shortTitle?: string;
|
|
48
|
+
/** Accessible name of the breadcrumb nav. */
|
|
49
|
+
crumbsLabel?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Back navigation URL.
|
|
52
|
+
* @deprecated Use `crumbs`; inside AdminShell the phone top bar goes back.
|
|
53
|
+
*/
|
|
19
54
|
backHref?: string;
|
|
20
55
|
/** Back link label */
|
|
21
56
|
backLabel?: string;
|
|
22
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* An editable title rendered inside the `<h1>` in place of `title`'s text
|
|
59
|
+
* (a labelled text field styled as the heading). `title` still names the
|
|
60
|
+
* page for the shell; keep it in sync with the field's value.
|
|
61
|
+
*/
|
|
62
|
+
titleField?: Snippet;
|
|
63
|
+
/** Slot for action buttons (right-aligned beside the title) */
|
|
23
64
|
actions?: Snippet;
|
|
24
|
-
/**
|
|
65
|
+
/** Small line under the title (status, dates, counts) */
|
|
66
|
+
meta?: Snippet;
|
|
67
|
+
/** Slot for additional content below the title row (e.g. page tabs) */
|
|
25
68
|
children?: Snippet;
|
|
26
69
|
}
|
|
27
70
|
|
|
28
71
|
const {
|
|
29
72
|
title,
|
|
30
73
|
subtitle,
|
|
74
|
+
crumbs,
|
|
75
|
+
parents = [],
|
|
76
|
+
shortTitle,
|
|
77
|
+
crumbsLabel = 'Breadcrumb',
|
|
31
78
|
backHref,
|
|
32
79
|
backLabel = 'Back',
|
|
80
|
+
titleField,
|
|
33
81
|
actions,
|
|
82
|
+
meta,
|
|
34
83
|
children,
|
|
35
84
|
}: Props = $props();
|
|
85
|
+
|
|
86
|
+
const shell = getPageHeaderContext();
|
|
87
|
+
const trail = $derived<readonly PageHeaderCrumb[]>(
|
|
88
|
+
crumbs ?? (shell ? shell.crumbs(parents) : parents),
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
$effect(() => shell?.report?.({ title: shortTitle ?? title, parents }));
|
|
36
92
|
</script>
|
|
37
93
|
|
|
38
|
-
<header class="page-header">
|
|
94
|
+
<header class="page-header" data-page-header>
|
|
95
|
+
{#if trail.length > 0}
|
|
96
|
+
<nav class="page-crumbs" aria-label={crumbsLabel} data-shell-breadcrumbs>
|
|
97
|
+
<ol>
|
|
98
|
+
{#each trail as crumb, index (`${index}:${crumb.href}`)}
|
|
99
|
+
<li><a href={crumb.href}>{crumb.label}</a></li>
|
|
100
|
+
{/each}
|
|
101
|
+
</ol>
|
|
102
|
+
</nav>
|
|
103
|
+
{/if}
|
|
104
|
+
|
|
39
105
|
<div class="header-main">
|
|
40
106
|
<div class="header-content">
|
|
41
107
|
{#if backHref}
|
|
@@ -44,10 +110,19 @@ const {
|
|
|
44
110
|
<span>{backLabel}</span>
|
|
45
111
|
</a>
|
|
46
112
|
{/if}
|
|
47
|
-
|
|
113
|
+
{#if titleField}
|
|
114
|
+
<h1 class="page-title page-title-field" data-page-title-field>
|
|
115
|
+
{@render titleField()}
|
|
116
|
+
</h1>
|
|
117
|
+
{:else}
|
|
118
|
+
<h1 class="page-title" data-shell-page-title>{title}</h1>
|
|
119
|
+
{/if}
|
|
48
120
|
{#if subtitle}
|
|
49
121
|
<p class="page-subtitle">{subtitle}</p>
|
|
50
122
|
{/if}
|
|
123
|
+
{#if meta}
|
|
124
|
+
<div class="page-meta">{@render meta()}</div>
|
|
125
|
+
{/if}
|
|
51
126
|
</div>
|
|
52
127
|
|
|
53
128
|
{#if actions}
|
|
@@ -58,7 +133,7 @@ const {
|
|
|
58
133
|
</div>
|
|
59
134
|
|
|
60
135
|
{#if children}
|
|
61
|
-
<div class="header-extra">
|
|
136
|
+
<div class="header-extra" data-page-header-extra>
|
|
62
137
|
{@render children()}
|
|
63
138
|
</div>
|
|
64
139
|
{/if}
|
|
@@ -66,15 +141,60 @@ const {
|
|
|
66
141
|
|
|
67
142
|
<style>
|
|
68
143
|
.page-header {
|
|
69
|
-
margin-bottom:
|
|
70
|
-
|
|
144
|
+
margin-bottom: 1.5rem;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.page-crumbs {
|
|
148
|
+
margin-bottom: var(--smrt-spacing-1, 4px);
|
|
149
|
+
color: var(--smrt-color-on-surface-variant);
|
|
150
|
+
font: var(--smrt-typography-body-small-font);
|
|
151
|
+
font-size: 0.84rem;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.page-crumbs ol {
|
|
155
|
+
display: flex;
|
|
156
|
+
flex-wrap: wrap;
|
|
157
|
+
align-items: center;
|
|
158
|
+
gap: 0.35rem;
|
|
159
|
+
margin: 0;
|
|
160
|
+
padding: 0;
|
|
161
|
+
list-style: none;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.page-crumbs li {
|
|
165
|
+
display: flex;
|
|
166
|
+
align-items: center;
|
|
167
|
+
min-width: 0;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.page-crumbs li + li::before {
|
|
171
|
+
content: '/';
|
|
172
|
+
margin-inline-end: 0.35rem;
|
|
173
|
+
color: var(--smrt-color-outline);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.page-crumbs a {
|
|
177
|
+
color: inherit;
|
|
178
|
+
text-decoration: none;
|
|
179
|
+
overflow-wrap: anywhere;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.page-crumbs a:hover {
|
|
183
|
+
color: var(--smrt-color-primary);
|
|
184
|
+
text-decoration: underline;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.page-crumbs a:focus-visible {
|
|
188
|
+
outline: 2px solid var(--smrt-color-primary);
|
|
189
|
+
outline-offset: 2px;
|
|
190
|
+
border-radius: var(--smrt-radius-sm, 4px);
|
|
71
191
|
}
|
|
72
192
|
|
|
73
193
|
.header-main {
|
|
74
194
|
display: flex;
|
|
75
195
|
justify-content: space-between;
|
|
76
196
|
align-items: flex-start;
|
|
77
|
-
gap: 1.5rem;
|
|
197
|
+
gap: 0.75rem 1.5rem;
|
|
78
198
|
flex-wrap: wrap;
|
|
79
199
|
}
|
|
80
200
|
|
|
@@ -83,7 +203,7 @@ const {
|
|
|
83
203
|
flex-direction: column;
|
|
84
204
|
gap: var(--smrt-spacing-1, 4px);
|
|
85
205
|
flex: 1;
|
|
86
|
-
min-width: 200px;
|
|
206
|
+
min-width: min(100%, 200px);
|
|
87
207
|
}
|
|
88
208
|
|
|
89
209
|
.back-link {
|
|
@@ -109,13 +229,26 @@ const {
|
|
|
109
229
|
color: var(--smrt-color-on-surface);
|
|
110
230
|
margin: 0;
|
|
111
231
|
letter-spacing: -0.5px;
|
|
232
|
+
overflow-wrap: anywhere;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.page-title-field {
|
|
236
|
+
min-width: 0;
|
|
112
237
|
}
|
|
113
238
|
|
|
114
239
|
.page-subtitle {
|
|
115
240
|
font: var(--smrt-typography-body-medium-font);
|
|
116
241
|
color: var(--smrt-color-on-surface-variant);
|
|
117
242
|
margin: 0;
|
|
118
|
-
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.page-meta {
|
|
246
|
+
display: flex;
|
|
247
|
+
flex-wrap: wrap;
|
|
248
|
+
align-items: center;
|
|
249
|
+
gap: 0.5rem;
|
|
250
|
+
font: var(--smrt-typography-body-small-font);
|
|
251
|
+
color: var(--smrt-color-on-surface-variant);
|
|
119
252
|
}
|
|
120
253
|
|
|
121
254
|
.header-actions {
|
|
@@ -123,9 +256,10 @@ const {
|
|
|
123
256
|
gap: 0.75rem;
|
|
124
257
|
align-items: center;
|
|
125
258
|
flex-wrap: wrap;
|
|
259
|
+
margin-inline-start: auto;
|
|
126
260
|
}
|
|
127
261
|
|
|
128
262
|
.header-extra {
|
|
129
|
-
margin-top:
|
|
263
|
+
margin-top: 1rem;
|
|
130
264
|
}
|
|
131
|
-
</style>
|
|
265
|
+
</style>
|
|
@@ -1,23 +1,63 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* PageHeader -
|
|
3
|
-
*
|
|
2
|
+
* PageHeader - one header per page: an ancestors-only breadcrumb row, the
|
|
3
|
+
* page title (`<h1>`), and the page's own actions, right-aligned.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Page-header contract (shared with AdminShell in smrt-svelte/workspace):
|
|
6
|
+
* - `crumbs` are the page's ancestors only, never the page itself; none on
|
|
7
|
+
* section homes. Inside a shell that sets `setPageHeaderContext()`, pages
|
|
8
|
+
* pass only `parents` (ancestors the nav doesn't know, e.g. the article
|
|
9
|
+
* above its video) and the shell adds the rest; the shell also hears the
|
|
10
|
+
* title (for its phone top bar). smrt-svelte's `shellPageTrailFor()`
|
|
11
|
+
* computes such a trail from a nav.
|
|
12
|
+
* - The crumb row carries `data-shell-breadcrumbs` and the title
|
|
13
|
+
* `data-shell-page-title`: on phones AdminShell hides the crumbs and
|
|
14
|
+
* visually hides the title, because its phone top bar shows the back arrow
|
|
15
|
+
* and the title instead.
|
|
16
|
+
* - No in-page back link: the crumbs (and the phone top bar) are the way
|
|
17
|
+
* back. `backHref` is kept only for pages outside such a shell.
|
|
18
|
+
* - An editable title (`titleField`, e.g. an article's headline) renders
|
|
19
|
+
* inside the `<h1>`, so the heading is still the page's title in the
|
|
20
|
+
* outline and is named by the field's value, while the field keeps its own
|
|
21
|
+
* label. Such a heading has no `data-shell-page-title`: it stays visible on
|
|
22
|
+
* phones, because it is the input. Pass the live value as `title` so the
|
|
23
|
+
* shell (the phone top bar) follows along as the person types.
|
|
7
24
|
*/
|
|
8
25
|
import type { Snippet } from 'svelte';
|
|
26
|
+
import { type PageHeaderCrumb } from './page-header-context.js';
|
|
9
27
|
export interface Props {
|
|
10
28
|
/** Page title */
|
|
11
29
|
title: string;
|
|
12
30
|
/** Optional subtitle/description */
|
|
13
31
|
subtitle?: string;
|
|
14
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* All ancestors of this page, outermost first (never the page itself).
|
|
34
|
+
* Overrides the shell's trail; usually pass `parents` instead.
|
|
35
|
+
*/
|
|
36
|
+
crumbs?: readonly PageHeaderCrumb[];
|
|
37
|
+
/** Ancestors past the shell's nav, outermost first. */
|
|
38
|
+
parents?: readonly PageHeaderCrumb[];
|
|
39
|
+
/** Shorter title for the shell (e.g. its phone top bar); default `title`. */
|
|
40
|
+
shortTitle?: string;
|
|
41
|
+
/** Accessible name of the breadcrumb nav. */
|
|
42
|
+
crumbsLabel?: string;
|
|
43
|
+
/**
|
|
44
|
+
* Back navigation URL.
|
|
45
|
+
* @deprecated Use `crumbs`; inside AdminShell the phone top bar goes back.
|
|
46
|
+
*/
|
|
15
47
|
backHref?: string;
|
|
16
48
|
/** Back link label */
|
|
17
49
|
backLabel?: string;
|
|
18
|
-
/**
|
|
50
|
+
/**
|
|
51
|
+
* An editable title rendered inside the `<h1>` in place of `title`'s text
|
|
52
|
+
* (a labelled text field styled as the heading). `title` still names the
|
|
53
|
+
* page for the shell; keep it in sync with the field's value.
|
|
54
|
+
*/
|
|
55
|
+
titleField?: Snippet;
|
|
56
|
+
/** Slot for action buttons (right-aligned beside the title) */
|
|
19
57
|
actions?: Snippet;
|
|
20
|
-
/**
|
|
58
|
+
/** Small line under the title (status, dates, counts) */
|
|
59
|
+
meta?: Snippet;
|
|
60
|
+
/** Slot for additional content below the title row (e.g. page tabs) */
|
|
21
61
|
children?: Snippet;
|
|
22
62
|
}
|
|
23
63
|
declare const PageHeader: import("svelte").Component<Props, {}, "">;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PageHeader.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/layout/PageHeader.svelte.ts"],"names":[],"mappings":"AAGA
|
|
1
|
+
{"version":3,"file":"PageHeader.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/layout/PageHeader.svelte.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAGtC,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,0BAA0B,CAAC;AAGlC,MAAM,WAAW,KAAK;IACpB,iBAAiB;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,MAAM,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IACpC,uDAAuD;IACvD,OAAO,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IACrC,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6CAA6C;IAC7C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sBAAsB;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,+DAA+D;IAC/D,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,yDAAyD;IACzD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,uEAAuE;IACvE,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAoFD,QAAA,MAAM,UAAU,2CAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* title, optional subtitle, optional back link, and actions/children snippets.
|
|
6
6
|
*/
|
|
7
7
|
import { render, screen } from '@testing-library/svelte';
|
|
8
|
-
import { createRawSnippet } from 'svelte';
|
|
8
|
+
import { createRawSnippet, flushSync } from 'svelte';
|
|
9
9
|
import { describe, expect, it } from 'vitest';
|
|
10
10
|
import { expectNoA11yViolations } from '../../../test-support/a11y';
|
|
11
11
|
import PageHeader from '../PageHeader.svelte';
|
|
@@ -66,6 +66,92 @@ describe('PageHeader', () => {
|
|
|
66
66
|
render(PageHeader, { props: { title: 'Detail' } });
|
|
67
67
|
expect(screen.queryByRole('link')).not.toBeInTheDocument();
|
|
68
68
|
});
|
|
69
|
+
it('renders ancestors-only crumbs above the title', () => {
|
|
70
|
+
render(PageHeader, {
|
|
71
|
+
props: {
|
|
72
|
+
title: 'Fair opens Friday',
|
|
73
|
+
crumbs: [
|
|
74
|
+
{ label: 'Alpha Times', href: '/sites/alpha' },
|
|
75
|
+
{ label: 'Content', href: '/sites/alpha/articles' },
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
const nav = screen.getByRole('navigation', { name: 'Breadcrumb' });
|
|
80
|
+
expect(nav).toHaveAttribute('data-shell-breadcrumbs');
|
|
81
|
+
const links = nav.querySelectorAll('a');
|
|
82
|
+
expect([...links].map((a) => a.textContent)).toEqual([
|
|
83
|
+
'Alpha Times',
|
|
84
|
+
'Content',
|
|
85
|
+
]);
|
|
86
|
+
// The current page is the heading, not a crumb.
|
|
87
|
+
expect(nav).not.toHaveTextContent('Fair opens Friday');
|
|
88
|
+
expect(screen.getByRole('heading', { level: 1, name: 'Fair opens Friday' })).toHaveAttribute('data-shell-page-title');
|
|
89
|
+
});
|
|
90
|
+
it('renders no crumb row without ancestors', () => {
|
|
91
|
+
render(PageHeader, { props: { title: 'Content', crumbs: [] } });
|
|
92
|
+
expect(screen.queryByRole('navigation')).not.toBeInTheDocument();
|
|
93
|
+
});
|
|
94
|
+
it('renders the meta snippet under the title', () => {
|
|
95
|
+
render(PageHeader, {
|
|
96
|
+
props: { title: 'Detail', meta: snippet('<span>Draft</span>') },
|
|
97
|
+
});
|
|
98
|
+
expect(screen.getByText('Draft')).toBeInTheDocument();
|
|
99
|
+
});
|
|
100
|
+
it('takes its trail from the shell and reports its title', () => {
|
|
101
|
+
const reports = [];
|
|
102
|
+
const context = new Map([
|
|
103
|
+
[
|
|
104
|
+
Symbol.for('smrt-ui.page-header'),
|
|
105
|
+
{
|
|
106
|
+
crumbs: (parents) => [
|
|
107
|
+
{ label: 'Alpha Times', href: '/sites/alpha' },
|
|
108
|
+
...parents,
|
|
109
|
+
],
|
|
110
|
+
report: (page) => {
|
|
111
|
+
reports.push(page);
|
|
112
|
+
return undefined;
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
]);
|
|
117
|
+
render(PageHeader, {
|
|
118
|
+
props: {
|
|
119
|
+
title: 'Create video',
|
|
120
|
+
parents: [{ label: 'Videos', href: '/sites/alpha/articles/a1/videos' }],
|
|
121
|
+
},
|
|
122
|
+
context,
|
|
123
|
+
});
|
|
124
|
+
const nav = screen.getByRole('navigation', { name: 'Breadcrumb' });
|
|
125
|
+
expect([...nav.querySelectorAll('a')].map((a) => a.textContent)).toEqual([
|
|
126
|
+
'Alpha Times',
|
|
127
|
+
'Videos',
|
|
128
|
+
]);
|
|
129
|
+
flushSync();
|
|
130
|
+
expect(reports).toEqual([
|
|
131
|
+
{
|
|
132
|
+
title: 'Create video',
|
|
133
|
+
parents: [{ label: 'Videos', href: '/sites/alpha/articles/a1/videos' }],
|
|
134
|
+
},
|
|
135
|
+
]);
|
|
136
|
+
});
|
|
137
|
+
it('renders an editable title inside the h1 and keeps it visible on phones', async () => {
|
|
138
|
+
const { container } = render(PageHeader, {
|
|
139
|
+
props: {
|
|
140
|
+
title: 'Council passes budget',
|
|
141
|
+
titleField: snippet('<input name="title" aria-label="Title" value="Council passes budget" />'),
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
const heading = screen.getByRole('heading', { level: 1 });
|
|
145
|
+
const field = screen.getByRole('textbox', { name: 'Title' });
|
|
146
|
+
expect(heading).toContainElement(field);
|
|
147
|
+
// The heading is named by the field's value; the field keeps its label.
|
|
148
|
+
expect(heading).toHaveAccessibleName('Council passes budget');
|
|
149
|
+
// No data-shell-page-title: the shell must not hide the input on phones.
|
|
150
|
+
expect(heading).not.toHaveAttribute('data-shell-page-title');
|
|
151
|
+
expect(heading).toHaveAttribute('data-page-title-field');
|
|
152
|
+
expect(container.querySelectorAll('h1')).toHaveLength(1);
|
|
153
|
+
await expectNoA11yViolations(container);
|
|
154
|
+
});
|
|
69
155
|
it('is axe-clean with subtitle, back link, and actions', async () => {
|
|
70
156
|
const { container } = render(PageHeader, {
|
|
71
157
|
props: {
|
|
@@ -8,5 +8,6 @@ export { default as Grid } from './Grid.svelte';
|
|
|
8
8
|
export { default as Header } from './Header.svelte';
|
|
9
9
|
export { default as Masthead } from './Masthead.svelte';
|
|
10
10
|
export { default as PageHeader } from './PageHeader.svelte';
|
|
11
|
+
export { getPageHeaderContext, type PageHeaderContext, type PageHeaderCrumb, type PageHeaderReport, setPageHeaderContext, } from './page-header-context.js';
|
|
11
12
|
export { default as SummaryCard } from './SummaryCard.svelte';
|
|
12
13
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/layout/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/layout/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EACL,oBAAoB,EACpB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,oBAAoB,GACrB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -8,4 +8,5 @@ export { default as Grid } from './Grid.svelte';
|
|
|
8
8
|
export { default as Header } from './Header.svelte';
|
|
9
9
|
export { default as Masthead } from './Masthead.svelte';
|
|
10
10
|
export { default as PageHeader } from './PageHeader.svelte';
|
|
11
|
+
export { getPageHeaderContext, setPageHeaderContext, } from './page-header-context.js';
|
|
11
12
|
export { default as SummaryCard } from './SummaryCard.svelte';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** One ancestor of a page. */
|
|
2
|
+
export interface PageHeaderCrumb {
|
|
3
|
+
label: string;
|
|
4
|
+
href: string;
|
|
5
|
+
}
|
|
6
|
+
/** What a page reports to its shell. */
|
|
7
|
+
export interface PageHeaderReport {
|
|
8
|
+
title: string;
|
|
9
|
+
parents: readonly PageHeaderCrumb[];
|
|
10
|
+
}
|
|
11
|
+
export interface PageHeaderContext {
|
|
12
|
+
/**
|
|
13
|
+
* The page's ancestors (never the page itself), given the ancestors it
|
|
14
|
+
* knows beyond the shell's nav. Called inside a reactive derivation, so
|
|
15
|
+
* the shell may read reactive state (the current path, its nav).
|
|
16
|
+
*/
|
|
17
|
+
crumbs(parents: readonly PageHeaderCrumb[]): PageHeaderCrumb[];
|
|
18
|
+
/** Hear the page's title and parents; return a cleanup for when it goes. */
|
|
19
|
+
report?(page: PageHeaderReport): (() => void) | undefined;
|
|
20
|
+
}
|
|
21
|
+
/** Called by a shell layout; applies to every `PageHeader` below it. */
|
|
22
|
+
export declare function setPageHeaderContext(context: PageHeaderContext): void;
|
|
23
|
+
/** The enclosing shell's page-header context, or null. */
|
|
24
|
+
export declare function getPageHeaderContext(): PageHeaderContext | null;
|
|
25
|
+
//# sourceMappingURL=page-header-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-header-context.d.ts","sourceRoot":"","sources":["../../../src/components/layout/page-header-context.ts"],"names":[],"mappings":"AAQA,8BAA8B;AAC9B,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wCAAwC;AACxC,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,SAAS,eAAe,EAAE,CAAC;CACrC;AAED,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,MAAM,CAAC,OAAO,EAAE,SAAS,eAAe,EAAE,GAAG,eAAe,EAAE,CAAC;IAC/D,4EAA4E;IAC5E,MAAM,CAAC,CAAC,IAAI,EAAE,gBAAgB,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC;CAC3D;AAID,wEAAwE;AACxE,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAErE;AAED,0DAA0D;AAC1D,wBAAgB,oBAAoB,IAAI,iBAAiB,GAAG,IAAI,CAE/D"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a shell (an app's workspace layout) takes part in every `PageHeader`
|
|
3
|
+
* below it, without the pages or the packages that render them knowing the
|
|
4
|
+
* shell: it turns a page's own `parents` into the full ancestor trail (from
|
|
5
|
+
* its nav) and hears each page's title, e.g. for a phone top bar.
|
|
6
|
+
*/
|
|
7
|
+
import { getContext, setContext } from 'svelte';
|
|
8
|
+
const PAGE_HEADER_CONTEXT = Symbol.for('smrt-ui.page-header');
|
|
9
|
+
/** Called by a shell layout; applies to every `PageHeader` below it. */
|
|
10
|
+
export function setPageHeaderContext(context) {
|
|
11
|
+
setContext(PAGE_HEADER_CONTEXT, context);
|
|
12
|
+
}
|
|
13
|
+
/** The enclosing shell's page-header context, or null. */
|
|
14
|
+
export function getPageHeaderContext() {
|
|
15
|
+
return getContext(PAGE_HEADER_CONTEXT) ?? null;
|
|
16
|
+
}
|