@super-ic/product-shell 0.0.0-oidc-bootstrap.0 → 0.1.1
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/README.md +155 -1
- package/css/member-shell.css +338 -0
- package/dist/index.d.ts +108 -0
- package/dist/index.js +544 -0
- package/dist/recipes/MemberShell.d.ts +37 -0
- package/dist/recipes/MemberShell.js +38 -0
- package/package.json +30 -7
package/README.md
CHANGED
|
@@ -1,3 +1,157 @@
|
|
|
1
1
|
# @super-ic/product-shell
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Provider-neutral React application shell primitives for SuperIC product surfaces: the app
|
|
4
|
+
frame, navigation rails, page and state regions, the top bar, the segmented search pill, and
|
|
5
|
+
the settings switch row.
|
|
6
|
+
|
|
7
|
+
Every component in this package renders from fixture props alone. Nothing here opens a
|
|
8
|
+
network connection, reads a cookie, initialises an SDK, or decides authorisation.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
npm install @super-ic/product-shell @super-ic/foundation react react-dom
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`react`, `react-dom` and `@super-ic/foundation` are peer dependencies. The declared React
|
|
17
|
+
range is `>=19.0.0`. Installing this package on its own emits a peer warning naming
|
|
18
|
+
`@super-ic/foundation`; that warning is the intended signal, not a defect.
|
|
19
|
+
|
|
20
|
+
## Required setup
|
|
21
|
+
|
|
22
|
+
The components style themselves from CSS custom properties that only `@super-ic/foundation`
|
|
23
|
+
defines. Import the token sheet once, at the root of the consuming application, before any
|
|
24
|
+
component from this package renders:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import "@super-ic/foundation/tokens.css";
|
|
28
|
+
import "@super-ic/product-shell/member-shell.css";
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Without that import every surface falls back to the browser default colours: the frame has no
|
|
32
|
+
background, the state regions have no surface, and the switch has no track.
|
|
33
|
+
|
|
34
|
+
The 19 custom properties this package reads are `--accent`, `--background`, `--border`,
|
|
35
|
+
`--control-border`, `--control-elevation`, `--control-elevation-surface`,
|
|
36
|
+
`--control-highlight`, `--elev-card`, `--elev-pill`, `--foreground`, `--muted`,
|
|
37
|
+
`--muted-foreground`, `--primary`, `--primary-foreground`, `--radius`, `--radius-card`,
|
|
38
|
+
`--ring`, `--surface` and `--surface-2`.
|
|
39
|
+
|
|
40
|
+
No Tailwind `@source` line is needed. Existing primitives carry scoped inline CSS. The
|
|
41
|
+
`MemberShell` recipe uses the public ordinary CSS entry shown above, scoped by `data-slot`
|
|
42
|
+
attributes and available in the installed package.
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
import "@super-ic/foundation/tokens.css";
|
|
48
|
+
import "@super-ic/product-shell/member-shell.css";
|
|
49
|
+
|
|
50
|
+
import {
|
|
51
|
+
AppFrame,
|
|
52
|
+
AppRail,
|
|
53
|
+
AppTopBar,
|
|
54
|
+
PageRegion,
|
|
55
|
+
StateRegion,
|
|
56
|
+
} from "@super-ic/product-shell";
|
|
57
|
+
|
|
58
|
+
export function Screen() {
|
|
59
|
+
return (
|
|
60
|
+
<AppFrame
|
|
61
|
+
header={<AppTopBar brand="Example" modes={[{ id: "browse", label: "Browse" }]} activeModeId="browse" />}
|
|
62
|
+
rail={<AppRail items={[{ id: "home", label: "Home", href: "/", active: true }]} />}
|
|
63
|
+
>
|
|
64
|
+
<PageRegion title="Home" description="What is available near you today.">
|
|
65
|
+
<StateRegion
|
|
66
|
+
state="empty"
|
|
67
|
+
title="Nothing here yet"
|
|
68
|
+
description="New items appear as neighbours post them."
|
|
69
|
+
action={{ label: "Refresh", onAction: () => location.reload() }}
|
|
70
|
+
/>
|
|
71
|
+
</PageRegion>
|
|
72
|
+
</AppFrame>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
For a composed member application, use one navigation array and select the current item by
|
|
78
|
+
id. `MemberShell` accepts zero through five unique destinations. A non-empty navigation
|
|
79
|
+
requires a matching `currentNavigationId`, or explicit `null` for a secondary route
|
|
80
|
+
such as Account. An omitted ID still rejects a populated navigation. A secondary
|
|
81
|
+
route keeps all primary links available without falsely marking Home current; its
|
|
82
|
+
utility link may carry `aria-current="page"`. Empty navigation omits its landmarks.
|
|
83
|
+
|
|
84
|
+
The frame defaults to `100dvh`. Within a parent that has an explicitly resolved
|
|
85
|
+
height, set `--member-shell-block-size: 100%` to fill the available region. A review
|
|
86
|
+
banner belongs in the parent layout and must not push navigation below the viewport.
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
import "@super-ic/foundation/tokens.css";
|
|
90
|
+
import "@super-ic/product-shell/member-shell.css";
|
|
91
|
+
import { MemberShell } from "@super-ic/product-shell";
|
|
92
|
+
|
|
93
|
+
<MemberShell
|
|
94
|
+
brand="Fixture Share"
|
|
95
|
+
navigation={[{ id: "home", label: "Home", href: "/home" }]}
|
|
96
|
+
currentNavigationId="home"
|
|
97
|
+
pageTitle="Member home"
|
|
98
|
+
>
|
|
99
|
+
{content}
|
|
100
|
+
</MemberShell>;
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The product adapter owns route transitions, route-heading focus and scroll restoration. An
|
|
104
|
+
optional `onNavigation` callback receives the unmodified React anchor event and item, so an
|
|
105
|
+
SPA adapter can preserve modifier clicks and other browser link behavior.
|
|
106
|
+
|
|
107
|
+
## Exports
|
|
108
|
+
|
|
109
|
+
| Export | Kind | Notes |
|
|
110
|
+
| --- | --- | --- |
|
|
111
|
+
| `MemberShell` | recipe | Container-responsive `100dvh` member shell with one main, one h1, skip link and zero-to-five destination invariant. Import `@super-ic/product-shell/member-shell.css`. |
|
|
112
|
+
| `AppFrame` | component | Grid shell. Stamps `data-superic-provider="none"` and injects the frame stylesheet. |
|
|
113
|
+
| `AppRail` | component | Desktop navigation `<nav>`; sets `aria-current="page"` on the active item. |
|
|
114
|
+
| `MobileBottomNavigation` | component | Small-viewport counterpart to `AppRail`. |
|
|
115
|
+
| `PageRegion` | component | The `<main>` landmark, its heading, and its description. |
|
|
116
|
+
| `StateRegion` | component | Blocking and recovery states, each with a live region. |
|
|
117
|
+
| `AppTopBar` | component | Brand plus a single-select mode group using `aria-pressed`. |
|
|
118
|
+
| `SegmentedSearchPill` | component | `role="search"` form with per-segment labels and one submit control. |
|
|
119
|
+
| `SettingsSwitchRow` | component | `role="switch"` row wired to its label and help text. |
|
|
120
|
+
| `StateRegionStates` | const | `waiting`, `empty`, `error`, `offline`, `permission`, `recovery`, `stale`, `success`, plus matrix aliases `loading`, `initial-empty`, `validation-error`, `permission-denied`, `removed`, `retryable-error`, `conflict`, `capability-disabled`, `safe-stop`, `support-entry`. |
|
|
121
|
+
| `ProductMemberRoles`, `ProductMemberCapabilities` | const | Display-only role and capability vocabularies. |
|
|
122
|
+
| `assertProductMemberDisplayProjection` | function | Throws unless `authority === "display-only"`. |
|
|
123
|
+
| `createProviderNeutralFixture`, `assertProviderNeutralFixture` | function | Throws unless `mode === "fixture"` and `provider === "none"`. |
|
|
124
|
+
| `PRODUCT_SHELL_CONTRACT_VERSION` | const | Tracks this package's own version. |
|
|
125
|
+
|
|
126
|
+
## Contracts worth knowing before you build on this
|
|
127
|
+
|
|
128
|
+
**`ProductMember` is a projection, not an authority.** It carries role labels and capability
|
|
129
|
+
labels for rendering only. `assertProductMemberDisplayProjection` throws if you hand it a
|
|
130
|
+
member whose `authority` is anything other than `"display-only"`. Never branch a permission
|
|
131
|
+
decision on it; ask your own server.
|
|
132
|
+
|
|
133
|
+
**Fixtures may not reach a provider.** `assertProviderNeutralFixture` throws unless the
|
|
134
|
+
fixture declares `mode: "fixture"` and `provider: "none"`. `AppFrame`, `AppTopBar`,
|
|
135
|
+
`SegmentedSearchPill` and `SettingsSwitchRow` all stamp `data-superic-provider="none"` on
|
|
136
|
+
their root so a consumer test can assert the same thing from the DOM.
|
|
137
|
+
|
|
138
|
+
**`PageRegion` labels itself.** When you pass `title` and no `labelledBy`, the heading id is
|
|
139
|
+
generated per instance with `useId`, so two `PageRegion`s on one page never collide on a
|
|
140
|
+
duplicate `id`. Pass `labelledBy` when the heading lives outside the region and you own its
|
|
141
|
+
id; `PageRegion` then uses your id verbatim and does not generate one.
|
|
142
|
+
|
|
143
|
+
**`StateRegion` picks its own ARIA role.** `error` and `permission` render
|
|
144
|
+
`role="alert"` with `aria-live="assertive"`; the other six render `role="status"` with
|
|
145
|
+
`aria-live="polite"`. Do not override this from the outside. The mapping is the contract.
|
|
146
|
+
|
|
147
|
+
**`AppTopBar` allows exactly one pressed mode.** `activeModeId` selects it, and only that one
|
|
148
|
+
button gets `aria-pressed="true"`.
|
|
149
|
+
|
|
150
|
+
**`SegmentedSearchPill` shows a segment's `value` or its `placeholder`, never both.** A
|
|
151
|
+
segment with a `value` renders the value as text; a segment without one renders the
|
|
152
|
+
placeholder.
|
|
153
|
+
|
|
154
|
+
## Versioning
|
|
155
|
+
|
|
156
|
+
`PRODUCT_SHELL_CONTRACT_VERSION` is the package version, not a separate contract line. Assert
|
|
157
|
+
against it if you need to fail closed on a shell upgrade.
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
[data-slot="member-shell"] {
|
|
2
|
+
--member-shell-gutter: 1rem;
|
|
3
|
+
container-name: superic-member-shell;
|
|
4
|
+
container-type: inline-size;
|
|
5
|
+
display: grid;
|
|
6
|
+
grid-template-rows: auto minmax(0, 1fr) auto;
|
|
7
|
+
block-size: var(--member-shell-block-size, 100dvh);
|
|
8
|
+
min-block-size: 0;
|
|
9
|
+
min-inline-size: 0;
|
|
10
|
+
overflow: hidden;
|
|
11
|
+
color: var(--foreground);
|
|
12
|
+
background: var(--background);
|
|
13
|
+
font-family: var(--type-body-medium-family);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
[data-slot="member-shell"] *,
|
|
17
|
+
[data-slot="member-shell"] *::before,
|
|
18
|
+
[data-slot="member-shell"] *::after {
|
|
19
|
+
box-sizing: border-box;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
[data-slot="member-shell-skip-link"] {
|
|
23
|
+
position: fixed;
|
|
24
|
+
inset-block-start: 0.5rem;
|
|
25
|
+
inset-inline-start: 0.5rem;
|
|
26
|
+
z-index: 50;
|
|
27
|
+
inline-size: 1px;
|
|
28
|
+
block-size: 1px;
|
|
29
|
+
min-block-size: 0;
|
|
30
|
+
clip-path: inset(50%);
|
|
31
|
+
overflow: hidden;
|
|
32
|
+
white-space: nowrap;
|
|
33
|
+
border-radius: var(--radius);
|
|
34
|
+
background: var(--foreground);
|
|
35
|
+
color: var(--background);
|
|
36
|
+
padding: 0;
|
|
37
|
+
font-weight: 600;
|
|
38
|
+
text-decoration: none;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
[data-slot="member-shell-skip-link"]:focus-visible {
|
|
42
|
+
inline-size: auto;
|
|
43
|
+
block-size: auto;
|
|
44
|
+
min-block-size: 2.75rem;
|
|
45
|
+
clip-path: none;
|
|
46
|
+
overflow: visible;
|
|
47
|
+
white-space: normal;
|
|
48
|
+
padding: 0.75rem 1rem;
|
|
49
|
+
outline: 2px solid var(--ring);
|
|
50
|
+
outline-offset: 2px;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
[data-slot="member-shell-header"] {
|
|
54
|
+
z-index: 2;
|
|
55
|
+
max-block-size: 25dvh;
|
|
56
|
+
overflow: auto;
|
|
57
|
+
border-block-end: 1px solid var(--border);
|
|
58
|
+
background: var(--surface);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
[data-slot="member-shell-header-inner"] {
|
|
62
|
+
display: flex;
|
|
63
|
+
flex-wrap: wrap;
|
|
64
|
+
align-items: center;
|
|
65
|
+
justify-content: space-between;
|
|
66
|
+
gap: 0.75rem 1rem;
|
|
67
|
+
inline-size: 100%;
|
|
68
|
+
max-inline-size: 72rem;
|
|
69
|
+
min-block-size: 4rem;
|
|
70
|
+
margin-inline: auto;
|
|
71
|
+
padding: 0.5rem var(--member-shell-gutter);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
[data-slot="member-shell-brand"],
|
|
75
|
+
[data-slot="member-shell-utility-actions"] {
|
|
76
|
+
display: flex;
|
|
77
|
+
align-items: center;
|
|
78
|
+
gap: 0.5rem;
|
|
79
|
+
min-inline-size: 0;
|
|
80
|
+
max-inline-size: 100%;
|
|
81
|
+
overflow-wrap: anywhere;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
[data-slot="member-shell-brand"] { flex: 0 1 auto; }
|
|
85
|
+
[data-slot="member-shell-brand"] > * { max-inline-size: 100%; }
|
|
86
|
+
|
|
87
|
+
[data-slot="member-shell-utility-actions"] {
|
|
88
|
+
margin-inline-start: auto;
|
|
89
|
+
flex-wrap: wrap;
|
|
90
|
+
justify-content: flex-end;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
[data-slot="member-shell-utility-actions"] button {
|
|
94
|
+
block-size: auto;
|
|
95
|
+
min-block-size: var(--control-height-lg);
|
|
96
|
+
max-inline-size: 100%;
|
|
97
|
+
white-space: normal;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
[data-slot="member-shell-body"] {
|
|
101
|
+
display: grid;
|
|
102
|
+
grid-template-columns: minmax(0, 1fr);
|
|
103
|
+
min-block-size: 0;
|
|
104
|
+
min-inline-size: 0;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
[data-slot="member-shell-rail"] {
|
|
108
|
+
display: none;
|
|
109
|
+
min-block-size: 0;
|
|
110
|
+
overflow: auto;
|
|
111
|
+
border-inline-end: 1px solid var(--border);
|
|
112
|
+
background: var(--surface);
|
|
113
|
+
padding: 1rem;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
[data-slot="member-shell-main"] {
|
|
117
|
+
min-block-size: 0;
|
|
118
|
+
min-inline-size: 0;
|
|
119
|
+
overflow: auto;
|
|
120
|
+
scroll-padding-block: 1rem;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
[data-slot="member-shell-main"]:focus {
|
|
124
|
+
outline: none;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
[data-slot="member-shell-content"] {
|
|
128
|
+
display: grid;
|
|
129
|
+
align-content: start;
|
|
130
|
+
gap: 2rem;
|
|
131
|
+
inline-size: 100%;
|
|
132
|
+
max-inline-size: 72rem;
|
|
133
|
+
margin-inline: auto;
|
|
134
|
+
padding: 1.5rem var(--member-shell-gutter) 2rem;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
[data-slot="member-shell-page-header"] {
|
|
138
|
+
display: flex;
|
|
139
|
+
flex-wrap: wrap;
|
|
140
|
+
align-items: flex-start;
|
|
141
|
+
justify-content: space-between;
|
|
142
|
+
gap: 1rem 1.5rem;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
[data-slot="member-shell-page-heading"] {
|
|
146
|
+
display: grid;
|
|
147
|
+
gap: 0.5rem;
|
|
148
|
+
min-inline-size: 0;
|
|
149
|
+
max-inline-size: 70ch;
|
|
150
|
+
overflow-wrap: anywhere;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
[data-slot="member-shell-page-heading"] h1 {
|
|
154
|
+
margin: 0;
|
|
155
|
+
color: var(--foreground);
|
|
156
|
+
font-family: var(--type-h3-family);
|
|
157
|
+
font-size: 1.5rem;
|
|
158
|
+
font-weight: 600;
|
|
159
|
+
line-height: 1.2;
|
|
160
|
+
letter-spacing: -0.01em;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
[data-slot="member-shell-page-description"] {
|
|
164
|
+
color: var(--muted-foreground);
|
|
165
|
+
line-height: 1.5;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
[data-slot="member-shell-page-description"] > :first-child {
|
|
169
|
+
margin-block-start: 0;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
[data-slot="member-shell-page-description"] > :last-child {
|
|
173
|
+
margin-block-end: 0;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
[data-slot="member-shell-page-actions"] {
|
|
177
|
+
display: flex;
|
|
178
|
+
flex-wrap: wrap;
|
|
179
|
+
align-items: center;
|
|
180
|
+
gap: 0.5rem;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
[data-slot="member-shell-navigation-list"] {
|
|
184
|
+
display: grid;
|
|
185
|
+
gap: 0.25rem;
|
|
186
|
+
margin: 0;
|
|
187
|
+
padding: 0;
|
|
188
|
+
list-style: none;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
[data-slot="member-shell-navigation-list"] a {
|
|
192
|
+
display: flex;
|
|
193
|
+
align-items: center;
|
|
194
|
+
justify-content: flex-start;
|
|
195
|
+
gap: 0.5rem;
|
|
196
|
+
min-block-size: 2.75rem;
|
|
197
|
+
border-radius: var(--radius);
|
|
198
|
+
color: var(--foreground);
|
|
199
|
+
padding: 0.625rem 0.75rem;
|
|
200
|
+
line-height: 1.25;
|
|
201
|
+
text-decoration: none;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
[data-slot="member-shell-navigation-list"] a:hover {
|
|
205
|
+
background: var(--surface-2);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
[data-slot="member-shell-navigation-list"] a[aria-current="page"] {
|
|
209
|
+
background: var(--muted);
|
|
210
|
+
font-weight: 600;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
[data-slot="member-shell-navigation-list"] a:focus-visible,
|
|
214
|
+
[data-slot="member-shell"] button:focus-visible,
|
|
215
|
+
[data-slot="member-shell"] [role="button"]:focus-visible {
|
|
216
|
+
outline: 2px solid var(--ring);
|
|
217
|
+
outline-offset: 2px;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
[data-slot="member-shell-navigation-icon"] {
|
|
221
|
+
display: grid;
|
|
222
|
+
flex: 0 0 auto;
|
|
223
|
+
place-items: center;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
[data-slot="member-shell-compact-navigation-region"] {
|
|
227
|
+
max-block-size: 35dvh;
|
|
228
|
+
min-inline-size: 0;
|
|
229
|
+
overflow: auto;
|
|
230
|
+
overscroll-behavior: contain;
|
|
231
|
+
border-block-start: 1px solid var(--border);
|
|
232
|
+
background: var(--surface);
|
|
233
|
+
padding: 0.375rem 0.5rem calc(0.375rem + env(safe-area-inset-bottom, 0px));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
[data-slot="member-shell-compact-navigation"] [data-slot="member-shell-navigation-list"] {
|
|
237
|
+
grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
|
|
238
|
+
gap: 0;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
[data-slot="member-shell-compact-navigation"] [data-slot="member-shell-navigation-list"] > li {
|
|
242
|
+
min-inline-size: 0;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
[data-slot="member-shell-compact-navigation"] [data-slot="member-shell-navigation-list"] a {
|
|
246
|
+
flex-direction: column;
|
|
247
|
+
justify-content: center;
|
|
248
|
+
gap: 0.25rem;
|
|
249
|
+
min-inline-size: 0;
|
|
250
|
+
padding-inline: 0;
|
|
251
|
+
font-family: var(--type-caption-family);
|
|
252
|
+
font-size: var(--type-caption-size);
|
|
253
|
+
font-weight: var(--type-caption-weight);
|
|
254
|
+
line-height: var(--type-caption-line-height);
|
|
255
|
+
letter-spacing: var(--type-caption-tracking);
|
|
256
|
+
text-align: center;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
[data-slot="member-shell-compact-navigation"] [data-slot="member-shell-navigation-list"] a > span:last-child {
|
|
260
|
+
min-inline-size: 0;
|
|
261
|
+
max-inline-size: 100%;
|
|
262
|
+
overflow-wrap: anywhere;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
@container superic-member-shell (min-width: 64rem) {
|
|
266
|
+
[data-slot="member-shell-header-inner"],
|
|
267
|
+
[data-slot="member-shell-content"] {
|
|
268
|
+
--member-shell-gutter: 1.5rem;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
[data-slot="member-shell-body"] {
|
|
272
|
+
grid-template-columns: 15rem minmax(0, 1fr);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
[data-slot="member-shell"][data-has-navigation="false"] [data-slot="member-shell-body"] {
|
|
276
|
+
grid-template-columns: minmax(0, 1fr);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
[data-slot="member-shell-rail"] {
|
|
280
|
+
display: block;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
[data-slot="member-shell-compact-navigation-region"] {
|
|
284
|
+
display: none;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
@media (forced-colors: active) {
|
|
289
|
+
[data-slot="member-shell-navigation-list"] a[aria-current="page"] {
|
|
290
|
+
outline: 2px solid CanvasText;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
@media (prefers-reduced-motion: reduce) {
|
|
295
|
+
[data-slot="member-shell"] *,
|
|
296
|
+
[data-slot="member-shell"] *::before,
|
|
297
|
+
[data-slot="member-shell"] *::after {
|
|
298
|
+
scroll-behavior: auto;
|
|
299
|
+
transition-duration: 0ms !important;
|
|
300
|
+
animation-duration: 0ms !important;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/* Top navigation is the current member recipe; the rail is an explicit legacy option. */
|
|
305
|
+
[data-slot="member-shell-top-navigation"] { display: none; }
|
|
306
|
+
[data-slot="member-shell"][data-navigation-placement="top"] [data-slot="member-shell-page-heading"] h1 {
|
|
307
|
+
font-size: var(--type-h1-size);
|
|
308
|
+
font-family: var(--type-h1-family);
|
|
309
|
+
line-height: var(--type-h1-line-height);
|
|
310
|
+
letter-spacing: -0.035em;
|
|
311
|
+
}
|
|
312
|
+
[data-slot="member-shell"][data-navigation-placement="top"] [data-slot="member-shell-content"] { padding-block-start: 2.5rem; gap: 2.25rem; }
|
|
313
|
+
@container superic-member-shell (min-width: 48rem) {
|
|
314
|
+
[data-slot="member-shell"][data-navigation-placement="top"] [data-slot="member-shell-body"] { grid-template-columns: minmax(0,1fr); }
|
|
315
|
+
[data-slot="member-shell"][data-navigation-placement="top"] [data-slot="member-shell-header-inner"] { min-block-size: 5rem; gap: 2rem; }
|
|
316
|
+
[data-slot="member-shell-top-navigation"] { display: block; margin-inline: auto; }
|
|
317
|
+
[data-slot="member-shell-top-navigation"] [data-slot="member-shell-navigation-list"] { display: flex; flex-wrap: wrap; align-items: center; gap: 1.5rem; }
|
|
318
|
+
[data-slot="member-shell-top-navigation"] [data-slot="member-shell-navigation-list"] a { position: relative; min-block-size: 5rem; border-radius: 0; padding-inline: 0.25rem; color: var(--muted-foreground); background: transparent; }
|
|
319
|
+
[data-slot="member-shell-top-navigation"] [data-slot="member-shell-navigation-list"] a:hover { color: var(--foreground); }
|
|
320
|
+
[data-slot="member-shell-top-navigation"] [data-slot="member-shell-navigation-list"] a[aria-current="page"] { color: var(--foreground); }
|
|
321
|
+
[data-slot="member-shell-top-navigation"] [data-slot="member-shell-navigation-list"] a[aria-current="page"]::after { content: ""; position: absolute; inset-inline: 0; inset-block-end: 0; block-size: 3px; background: var(--foreground); }
|
|
322
|
+
[data-slot="member-shell-top-navigation"] [data-slot="member-shell-navigation-icon"] { display: none; }
|
|
323
|
+
[data-slot="member-shell"][data-navigation-placement="top"] [data-slot="member-shell-compact-navigation-region"] { display: none; }
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
[data-slot="member-shell-page-identity"] { display: flex; align-items: center; gap: 1.25rem; min-inline-size: 0; flex: 1 1 16rem; }
|
|
327
|
+
[data-slot="member-shell"][data-content-layout="workspace"] [data-slot="member-shell-main"] { container: member-main / size; }
|
|
328
|
+
[data-slot="member-shell"][data-content-layout="workspace"] [data-slot="member-shell-content"] { block-size:100%; min-block-size:0; grid-template-rows:auto minmax(0,1fr); align-content:stretch; gap:1rem; padding-block:1rem; }
|
|
329
|
+
[data-slot="member-shell"][data-content-layout="workspace"] [data-slot="member-shell-page-heading"] h1 { font-family:var(--type-h3-family); font-size:var(--type-h3-size); line-height:1.25; letter-spacing:-.025em; }
|
|
330
|
+
@container member-main (max-height: 34rem) {
|
|
331
|
+
[data-slot="member-shell-content"] { block-size:auto !important; min-block-size:100%; grid-template-rows:auto auto !important; }
|
|
332
|
+
}
|
|
333
|
+
[data-slot="member-shell-page-media"] { inline-size: 6rem; block-size: 5rem; border-radius: var(--radius-xl); overflow: hidden; flex: 0 0 auto; background: var(--surface-2); }
|
|
334
|
+
[data-slot="member-shell-page-media"] > img { inline-size: 100%; block-size: 100%; object-fit: cover; }
|
|
335
|
+
@container superic-member-shell (max-width: 30rem) {
|
|
336
|
+
[data-slot="member-shell-page-identity"] { gap: 1rem; }
|
|
337
|
+
[data-slot="member-shell-page-media"] { inline-size: 4.5rem; block-size: 4.5rem; }
|
|
338
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { CSSProperties, ReactNode } from "react";
|
|
2
|
+
import type { AppNavigationItem } from "./recipes/MemberShell.js";
|
|
3
|
+
export { MEMBER_SHELL_HEADING_ID, MEMBER_SHELL_MAIN_ID, MemberShell, } from "./recipes/MemberShell.js";
|
|
4
|
+
export type { AppNavigationItem, MemberShellNavigationHandler, MemberShellProps, } from "./recipes/MemberShell.js";
|
|
5
|
+
export declare const PRODUCT_SHELL_CONTRACT_VERSION = "0.1.0";
|
|
6
|
+
export declare const ProductMemberRoles: readonly ["member", "requester", "lender", "provider", "organizer", "founder", "steward", "courier"];
|
|
7
|
+
export declare const ProductMemberCapabilities: readonly ["browse_public", "create_ask", "create_offer", "manage_commitment", "manage_plan", "review_community_case", "perform_simulated_courier_task"];
|
|
8
|
+
export type ProductMemberRole = (typeof ProductMemberRoles)[number];
|
|
9
|
+
export type ProductMemberCapability = (typeof ProductMemberCapabilities)[number];
|
|
10
|
+
export type ProductMember = Readonly<{
|
|
11
|
+
id: string;
|
|
12
|
+
displayName: string;
|
|
13
|
+
roleLabels: readonly ProductMemberRole[];
|
|
14
|
+
capabilities: readonly ProductMemberCapability[];
|
|
15
|
+
brandTenantId: string;
|
|
16
|
+
productTenantId: string;
|
|
17
|
+
communityId: string;
|
|
18
|
+
effectiveMembershipId: string | null;
|
|
19
|
+
authority: "display-only";
|
|
20
|
+
}>;
|
|
21
|
+
export declare function assertProductMemberDisplayProjection(member: ProductMember): ProductMember;
|
|
22
|
+
export type ProviderNeutralFixture<T> = Readonly<{
|
|
23
|
+
fixtureId: string;
|
|
24
|
+
mode: "fixture";
|
|
25
|
+
provider: "none";
|
|
26
|
+
data: T;
|
|
27
|
+
}>;
|
|
28
|
+
export declare function createProviderNeutralFixture<T>(fixtureId: string, data: T): ProviderNeutralFixture<T>;
|
|
29
|
+
export declare function assertProviderNeutralFixture<T>(fixture: ProviderNeutralFixture<T>): ProviderNeutralFixture<T>;
|
|
30
|
+
export type AppFrameProps = Readonly<{
|
|
31
|
+
children: ReactNode;
|
|
32
|
+
header?: ReactNode;
|
|
33
|
+
rail?: ReactNode;
|
|
34
|
+
mobileNavigation?: ReactNode;
|
|
35
|
+
className?: string;
|
|
36
|
+
}>;
|
|
37
|
+
export declare function AppFrame({ children, header, rail, mobileNavigation, className }: AppFrameProps): import("react").JSX.Element;
|
|
38
|
+
export type AppRailProps = Readonly<{
|
|
39
|
+
items: readonly AppNavigationItem[];
|
|
40
|
+
label?: string;
|
|
41
|
+
}>;
|
|
42
|
+
export declare function AppRail({ items, label }: AppRailProps): import("react").JSX.Element;
|
|
43
|
+
export type MobileBottomNavigationProps = Readonly<{
|
|
44
|
+
items: readonly AppNavigationItem[];
|
|
45
|
+
label?: string;
|
|
46
|
+
}>;
|
|
47
|
+
export declare function MobileBottomNavigation({ items, label, }: MobileBottomNavigationProps): import("react").JSX.Element;
|
|
48
|
+
export type PageRegionProps = Readonly<{
|
|
49
|
+
children: ReactNode;
|
|
50
|
+
title?: string;
|
|
51
|
+
description?: string;
|
|
52
|
+
labelledBy?: string;
|
|
53
|
+
}>;
|
|
54
|
+
export declare function PageRegion({ children, title, description, labelledBy }: PageRegionProps): import("react").JSX.Element;
|
|
55
|
+
export declare const StateRegionStates: readonly ["waiting", "empty", "error", "offline", "permission", "recovery", "stale", "success", "loading", "initial-empty", "validation-error", "permission-denied", "removed", "retryable-error", "conflict", "capability-disabled", "safe-stop", "support-entry"];
|
|
56
|
+
export type StateRegionState = (typeof StateRegionStates)[number];
|
|
57
|
+
export type StateRegionAction = Readonly<{
|
|
58
|
+
label: string;
|
|
59
|
+
href?: string;
|
|
60
|
+
onAction?: () => void;
|
|
61
|
+
}>;
|
|
62
|
+
export type StateRegionProps = Readonly<{
|
|
63
|
+
state: StateRegionState;
|
|
64
|
+
title: string;
|
|
65
|
+
description: string;
|
|
66
|
+
action?: StateRegionAction;
|
|
67
|
+
icon?: ReactNode;
|
|
68
|
+
children?: ReactNode;
|
|
69
|
+
style?: CSSProperties;
|
|
70
|
+
}>;
|
|
71
|
+
export declare function StateRegion({ state, title, description, action, icon, children, style, }: StateRegionProps): import("react").JSX.Element;
|
|
72
|
+
export type AppTopBarMode = Readonly<{
|
|
73
|
+
id: string;
|
|
74
|
+
label: string;
|
|
75
|
+
icon?: ReactNode;
|
|
76
|
+
}>;
|
|
77
|
+
export type AppTopBarProps = Readonly<{
|
|
78
|
+
brand: ReactNode;
|
|
79
|
+
modes: readonly AppTopBarMode[];
|
|
80
|
+
activeModeId: string;
|
|
81
|
+
onModeChange?: (modeId: string) => void;
|
|
82
|
+
trailing?: ReactNode;
|
|
83
|
+
label?: string;
|
|
84
|
+
modesLabel?: string;
|
|
85
|
+
}>;
|
|
86
|
+
export declare function AppTopBar({ brand, modes, activeModeId, onModeChange, trailing, label, modesLabel, }: AppTopBarProps): import("react").JSX.Element;
|
|
87
|
+
export type SearchPillSegment = Readonly<{
|
|
88
|
+
id: string;
|
|
89
|
+
label: string;
|
|
90
|
+
placeholder: string;
|
|
91
|
+
value?: string;
|
|
92
|
+
}>;
|
|
93
|
+
export type SegmentedSearchPillProps = Readonly<{
|
|
94
|
+
segments: readonly SearchPillSegment[];
|
|
95
|
+
onSubmit?: () => void;
|
|
96
|
+
submitLabel?: string;
|
|
97
|
+
submitIcon?: ReactNode;
|
|
98
|
+
label?: string;
|
|
99
|
+
}>;
|
|
100
|
+
export declare function SegmentedSearchPill({ segments, onSubmit, submitLabel, submitIcon, label, }: SegmentedSearchPillProps): import("react").JSX.Element;
|
|
101
|
+
export type SettingsSwitchRowProps = Readonly<{
|
|
102
|
+
label: string;
|
|
103
|
+
help?: string;
|
|
104
|
+
checked: boolean;
|
|
105
|
+
onCheckedChange?: (checked: boolean) => void;
|
|
106
|
+
disabled?: boolean;
|
|
107
|
+
}>;
|
|
108
|
+
export declare function SettingsSwitchRow({ label, help, checked, onCheckedChange, disabled, }: SettingsSwitchRowProps): import("react").JSX.Element;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment, useId } from "react";
|
|
3
|
+
export { MEMBER_SHELL_HEADING_ID, MEMBER_SHELL_MAIN_ID, MemberShell, } from "./recipes/MemberShell.js";
|
|
4
|
+
export const PRODUCT_SHELL_CONTRACT_VERSION = "0.1.0";
|
|
5
|
+
export const ProductMemberRoles = [
|
|
6
|
+
"member",
|
|
7
|
+
"requester",
|
|
8
|
+
"lender",
|
|
9
|
+
"provider",
|
|
10
|
+
"organizer",
|
|
11
|
+
"founder",
|
|
12
|
+
"steward",
|
|
13
|
+
"courier",
|
|
14
|
+
];
|
|
15
|
+
export const ProductMemberCapabilities = [
|
|
16
|
+
"browse_public",
|
|
17
|
+
"create_ask",
|
|
18
|
+
"create_offer",
|
|
19
|
+
"manage_commitment",
|
|
20
|
+
"manage_plan",
|
|
21
|
+
"review_community_case",
|
|
22
|
+
"perform_simulated_courier_task",
|
|
23
|
+
];
|
|
24
|
+
export function assertProductMemberDisplayProjection(member) {
|
|
25
|
+
if (member.authority !== "display-only") {
|
|
26
|
+
throw new Error("ProductMember is a display projection and cannot grant authority");
|
|
27
|
+
}
|
|
28
|
+
return member;
|
|
29
|
+
}
|
|
30
|
+
export function createProviderNeutralFixture(fixtureId, data) {
|
|
31
|
+
return { fixtureId, mode: "fixture", provider: "none", data };
|
|
32
|
+
}
|
|
33
|
+
export function assertProviderNeutralFixture(fixture) {
|
|
34
|
+
if (fixture.mode !== "fixture" || fixture.provider !== "none") {
|
|
35
|
+
throw new Error("accepted fixtures cannot initialize a provider");
|
|
36
|
+
}
|
|
37
|
+
return fixture;
|
|
38
|
+
}
|
|
39
|
+
const appFrameCss = `
|
|
40
|
+
[data-slot="superic-app-frame"] {
|
|
41
|
+
display: grid;
|
|
42
|
+
min-block-size: 100%;
|
|
43
|
+
color: var(--foreground);
|
|
44
|
+
background: var(--background);
|
|
45
|
+
}
|
|
46
|
+
[data-slot="superic-app-frame-header"] {
|
|
47
|
+
position: sticky;
|
|
48
|
+
inset-block-start: 0;
|
|
49
|
+
z-index: 1;
|
|
50
|
+
border-block-end: 1px solid var(--border);
|
|
51
|
+
background: var(--surface);
|
|
52
|
+
}
|
|
53
|
+
[data-slot="superic-app-frame-body"] {
|
|
54
|
+
display: grid;
|
|
55
|
+
min-inline-size: 0;
|
|
56
|
+
}
|
|
57
|
+
[data-slot="superic-app-rail"] {
|
|
58
|
+
display: grid;
|
|
59
|
+
align-content: start;
|
|
60
|
+
gap: 0.25rem;
|
|
61
|
+
border-inline-end: 1px solid var(--border);
|
|
62
|
+
background: var(--surface);
|
|
63
|
+
padding: 1rem;
|
|
64
|
+
}
|
|
65
|
+
[data-slot="superic-app-rail"] a,
|
|
66
|
+
[data-slot="superic-mobile-bottom-navigation"] a {
|
|
67
|
+
display: flex;
|
|
68
|
+
min-block-size: 44px;
|
|
69
|
+
align-items: center;
|
|
70
|
+
gap: 0.5rem;
|
|
71
|
+
border-radius: var(--radius);
|
|
72
|
+
color: var(--foreground);
|
|
73
|
+
padding-inline: 0.75rem;
|
|
74
|
+
text-decoration: none;
|
|
75
|
+
}
|
|
76
|
+
[data-slot="superic-app-rail"] a[aria-current="page"],
|
|
77
|
+
[data-slot="superic-mobile-bottom-navigation"] a[aria-current="page"] {
|
|
78
|
+
background: var(--muted);
|
|
79
|
+
font-weight: 600;
|
|
80
|
+
}
|
|
81
|
+
[data-slot="superic-app-rail"] a:focus-visible,
|
|
82
|
+
[data-slot="superic-mobile-bottom-navigation"] a:focus-visible,
|
|
83
|
+
[data-slot="superic-state-region-action"]:focus-visible {
|
|
84
|
+
outline: 2px solid var(--ring);
|
|
85
|
+
outline-offset: 2px;
|
|
86
|
+
}
|
|
87
|
+
[data-slot="superic-page-region"] {
|
|
88
|
+
display: grid;
|
|
89
|
+
align-content: start;
|
|
90
|
+
gap: 1rem;
|
|
91
|
+
min-inline-size: 0;
|
|
92
|
+
padding: clamp(1rem, 3vw, 2rem);
|
|
93
|
+
}
|
|
94
|
+
[data-slot="superic-page-region-heading"] {
|
|
95
|
+
display: grid;
|
|
96
|
+
gap: 0.5rem;
|
|
97
|
+
max-inline-size: 70ch;
|
|
98
|
+
}
|
|
99
|
+
[data-slot="superic-state-region"] {
|
|
100
|
+
display: grid;
|
|
101
|
+
gap: 0.75rem;
|
|
102
|
+
max-inline-size: 48rem;
|
|
103
|
+
border-radius: var(--radius);
|
|
104
|
+
background: var(--surface);
|
|
105
|
+
box-shadow: var(--elev-card);
|
|
106
|
+
padding: clamp(1rem, 3vw, 1.5rem);
|
|
107
|
+
}
|
|
108
|
+
[data-slot="superic-state-region-icon"] {
|
|
109
|
+
display: grid;
|
|
110
|
+
inline-size: 2.75rem;
|
|
111
|
+
block-size: 2.75rem;
|
|
112
|
+
place-items: center;
|
|
113
|
+
border-radius: 999px;
|
|
114
|
+
background: var(--muted);
|
|
115
|
+
color: var(--primary);
|
|
116
|
+
}
|
|
117
|
+
[data-slot="superic-state-region-action"] {
|
|
118
|
+
inline-size: fit-content;
|
|
119
|
+
min-block-size: 44px;
|
|
120
|
+
border: 1px solid var(--border);
|
|
121
|
+
border-radius: var(--radius);
|
|
122
|
+
background: var(--primary);
|
|
123
|
+
color: var(--primary-foreground);
|
|
124
|
+
padding-inline: 1rem;
|
|
125
|
+
text-decoration: none;
|
|
126
|
+
}
|
|
127
|
+
[data-slot="superic-mobile-bottom-navigation"] {
|
|
128
|
+
display: grid;
|
|
129
|
+
grid-auto-flow: column;
|
|
130
|
+
grid-auto-columns: minmax(0, 1fr);
|
|
131
|
+
border-block-start: 1px solid var(--border);
|
|
132
|
+
background: var(--surface);
|
|
133
|
+
padding-block-end: env(safe-area-inset-bottom, 0px);
|
|
134
|
+
}
|
|
135
|
+
@media (max-width: 47.99rem) {
|
|
136
|
+
[data-slot="superic-app-rail"] { display: none; }
|
|
137
|
+
[data-slot="superic-app-frame-body"] { grid-template-columns: minmax(0, 1fr); }
|
|
138
|
+
}
|
|
139
|
+
@media (min-width: 48rem) {
|
|
140
|
+
[data-slot="superic-app-frame-body"] { grid-template-columns: minmax(13rem, 17rem) minmax(0, 1fr); }
|
|
141
|
+
[data-slot="superic-mobile-bottom-navigation"] { display: none; }
|
|
142
|
+
}
|
|
143
|
+
@media (forced-colors: active) {
|
|
144
|
+
[data-slot="superic-app-rail"] a[aria-current="page"],
|
|
145
|
+
[data-slot="superic-mobile-bottom-navigation"] a[aria-current="page"] {
|
|
146
|
+
outline: 2px solid CanvasText;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
@media (prefers-reduced-motion: reduce) {
|
|
150
|
+
[data-slot="superic-app-frame"] *,
|
|
151
|
+
[data-slot="superic-app-frame"] *::before,
|
|
152
|
+
[data-slot="superic-app-frame"] *::after {
|
|
153
|
+
scroll-behavior: auto;
|
|
154
|
+
transition-duration: 0ms;
|
|
155
|
+
animation-duration: 0ms;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
`;
|
|
159
|
+
function AppFrameStyles() {
|
|
160
|
+
return _jsx("style", { children: appFrameCss });
|
|
161
|
+
}
|
|
162
|
+
export function AppFrame({ children, header, rail, mobileNavigation, className }) {
|
|
163
|
+
return (_jsxs("div", { className: className, "data-slot": "superic-app-frame", "data-superic-provider": "none", children: [_jsx(AppFrameStyles, {}), header ? _jsx("header", { "data-slot": "superic-app-frame-header", children: header }) : null, _jsxs("div", { "data-slot": "superic-app-frame-body", children: [rail, children] }), mobileNavigation] }));
|
|
164
|
+
}
|
|
165
|
+
export function AppRail({ items, label = "Application navigation" }) {
|
|
166
|
+
return (_jsx("nav", { "aria-label": label, "data-slot": "superic-app-rail", children: items.map((item) => (_jsxs("a", { "aria-current": item.active ? "page" : undefined, href: item.href, children: [item.icon ? _jsx("span", { "aria-hidden": "true", children: item.icon }) : null, _jsx("span", { children: item.label })] }, item.id))) }));
|
|
167
|
+
}
|
|
168
|
+
export function MobileBottomNavigation({ items, label = "Mobile application navigation", }) {
|
|
169
|
+
return (_jsx("nav", { "aria-label": label, "data-slot": "superic-mobile-bottom-navigation", children: items.map((item) => (_jsxs("a", { "aria-current": item.active ? "page" : undefined, href: item.href, children: [item.icon ? _jsx("span", { "aria-hidden": "true", children: item.icon }) : null, _jsx("span", { children: item.label })] }, item.id))) }));
|
|
170
|
+
}
|
|
171
|
+
export function PageRegion({ children, title, description, labelledBy }) {
|
|
172
|
+
const generatedHeadingId = useId();
|
|
173
|
+
const headingId = labelledBy ?? (title ? `${generatedHeadingId}superic-page-region-title` : undefined);
|
|
174
|
+
return (_jsxs("main", { "aria-labelledby": headingId, "data-slot": "superic-page-region", children: [title ? (_jsxs("header", { "data-slot": "superic-page-region-heading", children: [_jsx("h1", { id: headingId, children: title }), description ? _jsx("p", { children: description }) : null] })) : null, children] }));
|
|
175
|
+
}
|
|
176
|
+
export const StateRegionStates = [
|
|
177
|
+
"waiting",
|
|
178
|
+
"empty",
|
|
179
|
+
"error",
|
|
180
|
+
"offline",
|
|
181
|
+
"permission",
|
|
182
|
+
"recovery",
|
|
183
|
+
"stale",
|
|
184
|
+
"success",
|
|
185
|
+
"loading",
|
|
186
|
+
"initial-empty",
|
|
187
|
+
"validation-error",
|
|
188
|
+
"permission-denied",
|
|
189
|
+
"removed",
|
|
190
|
+
"retryable-error",
|
|
191
|
+
"conflict",
|
|
192
|
+
"capability-disabled",
|
|
193
|
+
"safe-stop",
|
|
194
|
+
"support-entry",
|
|
195
|
+
];
|
|
196
|
+
const regionRoles = {
|
|
197
|
+
waiting: "status",
|
|
198
|
+
empty: "status",
|
|
199
|
+
error: "alert",
|
|
200
|
+
offline: "status",
|
|
201
|
+
permission: "alert",
|
|
202
|
+
recovery: "status",
|
|
203
|
+
stale: "status",
|
|
204
|
+
success: "status",
|
|
205
|
+
loading: "status",
|
|
206
|
+
"initial-empty": "status",
|
|
207
|
+
"validation-error": "alert",
|
|
208
|
+
"permission-denied": "alert",
|
|
209
|
+
removed: "status",
|
|
210
|
+
"retryable-error": "alert",
|
|
211
|
+
conflict: "alert",
|
|
212
|
+
"capability-disabled": "status",
|
|
213
|
+
"safe-stop": "alert",
|
|
214
|
+
"support-entry": "status",
|
|
215
|
+
};
|
|
216
|
+
export function StateRegion({ state, title, description, action, icon, children, style, }) {
|
|
217
|
+
const role = regionRoles[state];
|
|
218
|
+
const actionNode = action?.href
|
|
219
|
+
? _jsx("a", { "data-slot": "superic-state-region-action", href: action.href, children: action.label })
|
|
220
|
+
: action
|
|
221
|
+
? _jsx("button", { "data-slot": "superic-state-region-action", onClick: action.onAction, type: "button", children: action.label })
|
|
222
|
+
: null;
|
|
223
|
+
return (_jsxs("section", { "aria-live": role === "alert" ? "assertive" : "polite", "data-slot": "superic-state-region", "data-state": state, role: role, style: style, children: [icon ? _jsx("span", { "aria-hidden": "true", "data-slot": "superic-state-region-icon", children: icon }) : null, _jsxs("div", { children: [_jsx("h2", { children: title }), _jsx("p", { children: description })] }), children, actionNode] }));
|
|
224
|
+
}
|
|
225
|
+
const appTopBarCss = `
|
|
226
|
+
[data-slot="superic-app-top-bar"] {
|
|
227
|
+
display: grid;
|
|
228
|
+
grid-template-areas: "brand modes trailing";
|
|
229
|
+
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
|
|
230
|
+
align-items: center;
|
|
231
|
+
column-gap: 1rem;
|
|
232
|
+
row-gap: 0.5rem;
|
|
233
|
+
min-block-size: 4rem;
|
|
234
|
+
border-block-end: 1px solid var(--border);
|
|
235
|
+
background: var(--surface);
|
|
236
|
+
color: var(--foreground);
|
|
237
|
+
padding-block: 0.5rem;
|
|
238
|
+
padding-inline: clamp(1rem, 3vw, 2rem);
|
|
239
|
+
}
|
|
240
|
+
[data-slot="superic-app-top-bar-brand"] {
|
|
241
|
+
grid-area: brand;
|
|
242
|
+
display: flex;
|
|
243
|
+
align-items: center;
|
|
244
|
+
gap: 0.5rem;
|
|
245
|
+
min-inline-size: 0;
|
|
246
|
+
font-weight: 600;
|
|
247
|
+
}
|
|
248
|
+
[data-slot="superic-app-top-bar-modes"] {
|
|
249
|
+
grid-area: modes;
|
|
250
|
+
justify-self: center;
|
|
251
|
+
display: flex;
|
|
252
|
+
align-items: center;
|
|
253
|
+
gap: 0.5rem;
|
|
254
|
+
border: 1px solid var(--border);
|
|
255
|
+
border-radius: 999px;
|
|
256
|
+
background: var(--surface);
|
|
257
|
+
box-shadow: var(--control-elevation-surface);
|
|
258
|
+
padding: 0.25rem;
|
|
259
|
+
}
|
|
260
|
+
[data-slot="superic-app-top-bar-mode"] {
|
|
261
|
+
display: inline-flex;
|
|
262
|
+
align-items: center;
|
|
263
|
+
justify-content: center;
|
|
264
|
+
gap: 0.5rem;
|
|
265
|
+
min-block-size: 2.75rem;
|
|
266
|
+
border: 1px solid transparent;
|
|
267
|
+
border-radius: 999px;
|
|
268
|
+
background: transparent;
|
|
269
|
+
color: var(--muted-foreground);
|
|
270
|
+
padding-inline: 1rem;
|
|
271
|
+
font: inherit;
|
|
272
|
+
font-weight: 500;
|
|
273
|
+
cursor: pointer;
|
|
274
|
+
}
|
|
275
|
+
[data-slot="superic-app-top-bar-mode"]:hover {
|
|
276
|
+
color: var(--foreground);
|
|
277
|
+
background: var(--accent);
|
|
278
|
+
}
|
|
279
|
+
[data-slot="superic-app-top-bar-mode"][aria-pressed="true"] {
|
|
280
|
+
color: var(--primary-foreground);
|
|
281
|
+
border-color: var(--control-border);
|
|
282
|
+
background: var(--primary);
|
|
283
|
+
background-image: var(--control-highlight);
|
|
284
|
+
box-shadow: var(--control-elevation);
|
|
285
|
+
}
|
|
286
|
+
[data-slot="superic-app-top-bar-mode"]:focus-visible {
|
|
287
|
+
outline: 2px solid var(--ring);
|
|
288
|
+
outline-offset: 2px;
|
|
289
|
+
}
|
|
290
|
+
[data-slot="superic-app-top-bar-trailing"] {
|
|
291
|
+
grid-area: trailing;
|
|
292
|
+
justify-self: end;
|
|
293
|
+
display: flex;
|
|
294
|
+
align-items: center;
|
|
295
|
+
gap: 0.5rem;
|
|
296
|
+
min-inline-size: 0;
|
|
297
|
+
}
|
|
298
|
+
@media (max-width: 47.99rem) {
|
|
299
|
+
[data-slot="superic-app-top-bar"] {
|
|
300
|
+
grid-template-areas: "brand trailing" "modes modes";
|
|
301
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
@media (forced-colors: active) {
|
|
305
|
+
[data-slot="superic-app-top-bar-mode"][aria-pressed="true"] {
|
|
306
|
+
outline: 2px solid CanvasText;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
`;
|
|
310
|
+
function AppTopBarStyles() {
|
|
311
|
+
return _jsx("style", { children: appTopBarCss });
|
|
312
|
+
}
|
|
313
|
+
export function AppTopBar({ brand, modes, activeModeId, onModeChange, trailing, label = "Product top bar", modesLabel = "Mode", }) {
|
|
314
|
+
return (_jsxs("header", { "aria-label": label, "data-slot": "superic-app-top-bar", "data-superic-provider": "none", children: [_jsx(AppTopBarStyles, {}), _jsx("div", { "data-slot": "superic-app-top-bar-brand", children: brand }), _jsx("div", { "aria-label": modesLabel, "data-slot": "superic-app-top-bar-modes", role: "group", children: modes.map((mode) => {
|
|
315
|
+
const active = mode.id === activeModeId;
|
|
316
|
+
return (_jsxs("button", { "aria-pressed": active, "data-slot": "superic-app-top-bar-mode", onClick: onModeChange ? () => onModeChange(mode.id) : undefined, type: "button", children: [mode.icon ? _jsx("span", { "aria-hidden": "true", children: mode.icon }) : null, _jsx("span", { children: mode.label })] }, mode.id));
|
|
317
|
+
}) }), trailing ? _jsx("div", { "data-slot": "superic-app-top-bar-trailing", children: trailing }) : null] }));
|
|
318
|
+
}
|
|
319
|
+
const searchPillCss = `
|
|
320
|
+
[data-slot="superic-search-pill"] {
|
|
321
|
+
display: flex;
|
|
322
|
+
align-items: stretch;
|
|
323
|
+
inline-size: fit-content;
|
|
324
|
+
max-inline-size: 100%;
|
|
325
|
+
border: 1px solid var(--border);
|
|
326
|
+
border-radius: 999px;
|
|
327
|
+
background: var(--surface);
|
|
328
|
+
color: var(--foreground);
|
|
329
|
+
box-shadow: var(--elev-pill);
|
|
330
|
+
}
|
|
331
|
+
[data-slot="superic-search-pill-segment"] {
|
|
332
|
+
display: grid;
|
|
333
|
+
align-content: center;
|
|
334
|
+
gap: 0.25rem;
|
|
335
|
+
min-inline-size: 0;
|
|
336
|
+
min-block-size: 2.75rem;
|
|
337
|
+
border: 0;
|
|
338
|
+
border-radius: 999px;
|
|
339
|
+
background: transparent;
|
|
340
|
+
color: inherit;
|
|
341
|
+
text-align: start;
|
|
342
|
+
padding-block: 0.5rem;
|
|
343
|
+
padding-inline: 1.5rem;
|
|
344
|
+
font: inherit;
|
|
345
|
+
cursor: pointer;
|
|
346
|
+
}
|
|
347
|
+
[data-slot="superic-search-pill-segment"]:hover {
|
|
348
|
+
background: var(--accent);
|
|
349
|
+
}
|
|
350
|
+
[data-slot="superic-search-pill-segment"]:focus-visible,
|
|
351
|
+
[data-slot="superic-search-pill-submit"]:focus-visible {
|
|
352
|
+
outline: 2px solid var(--ring);
|
|
353
|
+
outline-offset: 2px;
|
|
354
|
+
}
|
|
355
|
+
[data-slot="superic-search-pill-segment-label"] {
|
|
356
|
+
font-size: 0.75rem;
|
|
357
|
+
font-weight: 600;
|
|
358
|
+
line-height: 1rem;
|
|
359
|
+
}
|
|
360
|
+
[data-slot="superic-search-pill-segment-value"] {
|
|
361
|
+
font-size: 0.875rem;
|
|
362
|
+
line-height: 1.25rem;
|
|
363
|
+
color: var(--muted-foreground);
|
|
364
|
+
overflow: hidden;
|
|
365
|
+
text-overflow: ellipsis;
|
|
366
|
+
white-space: nowrap;
|
|
367
|
+
}
|
|
368
|
+
[data-slot="superic-search-pill-segment-value"][data-filled="true"] {
|
|
369
|
+
color: var(--foreground);
|
|
370
|
+
}
|
|
371
|
+
[data-slot="superic-search-pill-divider"] {
|
|
372
|
+
align-self: center;
|
|
373
|
+
inline-size: 1px;
|
|
374
|
+
block-size: 2rem;
|
|
375
|
+
background: var(--border);
|
|
376
|
+
}
|
|
377
|
+
[data-slot="superic-search-pill-submit"] {
|
|
378
|
+
display: inline-flex;
|
|
379
|
+
align-items: center;
|
|
380
|
+
justify-content: center;
|
|
381
|
+
gap: 0.5rem;
|
|
382
|
+
align-self: center;
|
|
383
|
+
min-inline-size: 2.75rem;
|
|
384
|
+
min-block-size: 2.75rem;
|
|
385
|
+
margin-block: 0.5rem;
|
|
386
|
+
margin-inline: 0.25rem 0.5rem;
|
|
387
|
+
border: 1px solid var(--control-border);
|
|
388
|
+
border-radius: 999px;
|
|
389
|
+
background: var(--primary);
|
|
390
|
+
background-image: var(--control-highlight);
|
|
391
|
+
color: var(--primary-foreground);
|
|
392
|
+
box-shadow: var(--control-elevation);
|
|
393
|
+
padding-inline: 1rem;
|
|
394
|
+
font: inherit;
|
|
395
|
+
font-weight: 600;
|
|
396
|
+
cursor: pointer;
|
|
397
|
+
}
|
|
398
|
+
[data-slot="superic-search-pill-submit"][data-icon-only="true"] {
|
|
399
|
+
inline-size: 2.75rem;
|
|
400
|
+
padding-inline: 0;
|
|
401
|
+
}
|
|
402
|
+
[data-slot="superic-search-pill"] [data-slot="superic-visually-hidden"] {
|
|
403
|
+
position: absolute;
|
|
404
|
+
inline-size: 1px;
|
|
405
|
+
block-size: 1px;
|
|
406
|
+
margin: -1px;
|
|
407
|
+
padding: 0;
|
|
408
|
+
overflow: hidden;
|
|
409
|
+
clip-path: inset(50%);
|
|
410
|
+
white-space: nowrap;
|
|
411
|
+
border: 0;
|
|
412
|
+
}
|
|
413
|
+
@media (max-width: 47.99rem) {
|
|
414
|
+
[data-slot="superic-search-pill"] {
|
|
415
|
+
flex-direction: column;
|
|
416
|
+
align-items: stretch;
|
|
417
|
+
inline-size: 100%;
|
|
418
|
+
border-radius: var(--radius-card);
|
|
419
|
+
}
|
|
420
|
+
[data-slot="superic-search-pill-divider"] {
|
|
421
|
+
align-self: stretch;
|
|
422
|
+
inline-size: auto;
|
|
423
|
+
block-size: 1px;
|
|
424
|
+
margin-inline: 1.5rem;
|
|
425
|
+
}
|
|
426
|
+
[data-slot="superic-search-pill-submit"] {
|
|
427
|
+
align-self: stretch;
|
|
428
|
+
margin: 0.5rem;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
`;
|
|
432
|
+
function SegmentedSearchPillStyles() {
|
|
433
|
+
return _jsx("style", { children: searchPillCss });
|
|
434
|
+
}
|
|
435
|
+
export function SegmentedSearchPill({ segments, onSubmit, submitLabel = "Search", submitIcon, label = "Search", }) {
|
|
436
|
+
const handleSubmit = (event) => {
|
|
437
|
+
event.preventDefault();
|
|
438
|
+
onSubmit?.();
|
|
439
|
+
};
|
|
440
|
+
return (_jsxs("form", { "aria-label": label, "data-slot": "superic-search-pill", "data-superic-provider": "none", onSubmit: handleSubmit, role: "search", children: [_jsx(SegmentedSearchPillStyles, {}), segments.map((segment, index) => (_jsxs(Fragment, { children: [index > 0 ? _jsx("span", { "aria-hidden": "true", "data-slot": "superic-search-pill-divider" }) : null, _jsxs("button", { "data-slot": "superic-search-pill-segment", type: "button", children: [_jsx("span", { "data-slot": "superic-search-pill-segment-label", children: segment.label }), _jsx("span", { "data-filled": segment.value ? "true" : undefined, "data-slot": "superic-search-pill-segment-value", children: segment.value ?? segment.placeholder })] })] }, segment.id))), _jsxs("button", { "data-icon-only": submitIcon ? "true" : undefined, "data-slot": "superic-search-pill-submit", type: "submit", children: [submitIcon ? _jsx("span", { "aria-hidden": "true", children: submitIcon }) : null, _jsx("span", { "data-slot": submitIcon ? "superic-visually-hidden" : undefined, children: submitLabel })] })] }));
|
|
441
|
+
}
|
|
442
|
+
const settingsSwitchRowCss = `
|
|
443
|
+
[data-slot="superic-settings-switch-row"] {
|
|
444
|
+
display: flex;
|
|
445
|
+
align-items: center;
|
|
446
|
+
justify-content: space-between;
|
|
447
|
+
gap: 1rem;
|
|
448
|
+
border: 1px solid var(--border);
|
|
449
|
+
border-radius: var(--radius);
|
|
450
|
+
background: var(--surface);
|
|
451
|
+
color: var(--foreground);
|
|
452
|
+
box-shadow: var(--elev-card);
|
|
453
|
+
padding-block: 0.75rem;
|
|
454
|
+
padding-inline: 1rem;
|
|
455
|
+
}
|
|
456
|
+
[data-slot="superic-settings-switch-row"][data-disabled="true"] {
|
|
457
|
+
opacity: 0.6;
|
|
458
|
+
}
|
|
459
|
+
[data-slot="superic-settings-switch-row-copy"] {
|
|
460
|
+
display: grid;
|
|
461
|
+
gap: 0.25rem;
|
|
462
|
+
min-inline-size: 0;
|
|
463
|
+
}
|
|
464
|
+
[data-slot="superic-settings-switch-row-label"] {
|
|
465
|
+
font-weight: 600;
|
|
466
|
+
}
|
|
467
|
+
[data-slot="superic-settings-switch-row-help"] {
|
|
468
|
+
margin: 0;
|
|
469
|
+
font-size: 0.875rem;
|
|
470
|
+
line-height: 1.25rem;
|
|
471
|
+
color: var(--muted-foreground);
|
|
472
|
+
}
|
|
473
|
+
[data-slot="superic-settings-switch"] {
|
|
474
|
+
display: grid;
|
|
475
|
+
place-items: center;
|
|
476
|
+
flex: none;
|
|
477
|
+
min-inline-size: 2.75rem;
|
|
478
|
+
min-block-size: 2.75rem;
|
|
479
|
+
border: 0;
|
|
480
|
+
border-radius: 999px;
|
|
481
|
+
background: transparent;
|
|
482
|
+
padding: 0;
|
|
483
|
+
cursor: pointer;
|
|
484
|
+
}
|
|
485
|
+
[data-slot="superic-settings-switch"]:disabled {
|
|
486
|
+
cursor: not-allowed;
|
|
487
|
+
}
|
|
488
|
+
[data-slot="superic-settings-switch"]:focus-visible {
|
|
489
|
+
outline: 2px solid var(--ring);
|
|
490
|
+
outline-offset: 2px;
|
|
491
|
+
}
|
|
492
|
+
[data-slot="superic-settings-switch-track"] {
|
|
493
|
+
display: flex;
|
|
494
|
+
align-items: center;
|
|
495
|
+
inline-size: 2.75rem;
|
|
496
|
+
block-size: 1.75rem;
|
|
497
|
+
border: 1px solid var(--border);
|
|
498
|
+
border-radius: 999px;
|
|
499
|
+
background: var(--surface-2);
|
|
500
|
+
padding-inline: 0.125rem;
|
|
501
|
+
transition: background 150ms ease, border-color 150ms ease;
|
|
502
|
+
}
|
|
503
|
+
[data-slot="superic-settings-switch-thumb"] {
|
|
504
|
+
inline-size: 1.375rem;
|
|
505
|
+
block-size: 1.375rem;
|
|
506
|
+
border: 1px solid var(--border);
|
|
507
|
+
border-radius: 999px;
|
|
508
|
+
background: var(--surface);
|
|
509
|
+
box-shadow: var(--elev-card);
|
|
510
|
+
transition: transform 150ms ease;
|
|
511
|
+
}
|
|
512
|
+
[data-slot="superic-settings-switch"][aria-checked="true"] [data-slot="superic-settings-switch-track"] {
|
|
513
|
+
border-color: var(--control-border);
|
|
514
|
+
background: var(--primary);
|
|
515
|
+
}
|
|
516
|
+
[data-slot="superic-settings-switch"][aria-checked="true"] [data-slot="superic-settings-switch-thumb"] {
|
|
517
|
+
transform: translateX(1rem);
|
|
518
|
+
border-color: var(--control-border);
|
|
519
|
+
background: var(--primary-foreground);
|
|
520
|
+
}
|
|
521
|
+
@media (forced-colors: active) {
|
|
522
|
+
[data-slot="superic-settings-switch-track"] {
|
|
523
|
+
border: 1px solid CanvasText;
|
|
524
|
+
}
|
|
525
|
+
[data-slot="superic-settings-switch-thumb"] {
|
|
526
|
+
border: 1px solid CanvasText;
|
|
527
|
+
background: CanvasText;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
@media (prefers-reduced-motion: reduce) {
|
|
531
|
+
[data-slot="superic-settings-switch-track"],
|
|
532
|
+
[data-slot="superic-settings-switch-thumb"] {
|
|
533
|
+
transition-duration: 0ms;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
`;
|
|
537
|
+
function SettingsSwitchRowStyles() {
|
|
538
|
+
return _jsx("style", { children: settingsSwitchRowCss });
|
|
539
|
+
}
|
|
540
|
+
export function SettingsSwitchRow({ label, help, checked, onCheckedChange, disabled = false, }) {
|
|
541
|
+
const labelId = useId();
|
|
542
|
+
const helpId = useId();
|
|
543
|
+
return (_jsxs("div", { "data-disabled": disabled ? "true" : undefined, "data-slot": "superic-settings-switch-row", "data-superic-provider": "none", children: [_jsx(SettingsSwitchRowStyles, {}), _jsxs("div", { "data-slot": "superic-settings-switch-row-copy", children: [_jsx("span", { "data-slot": "superic-settings-switch-row-label", id: labelId, children: label }), help ? _jsx("p", { "data-slot": "superic-settings-switch-row-help", id: helpId, children: help }) : null] }), _jsx("button", { "aria-checked": checked, "aria-describedby": help ? helpId : undefined, "aria-labelledby": labelId, "data-slot": "superic-settings-switch", disabled: disabled, onClick: onCheckedChange ? () => onCheckedChange(!checked) : undefined, role: "switch", type: "button", children: _jsx("span", { "aria-hidden": "true", "data-slot": "superic-settings-switch-track", children: _jsx("span", { "data-slot": "superic-settings-switch-thumb" }) }) })] }));
|
|
544
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { MouseEventHandler, ReactNode } from "react";
|
|
2
|
+
export declare const MEMBER_SHELL_MAIN_ID = "superic-member-shell-main";
|
|
3
|
+
export declare const MEMBER_SHELL_HEADING_ID = "superic-member-shell-heading";
|
|
4
|
+
export type AppNavigationItem = Readonly<{
|
|
5
|
+
id: string;
|
|
6
|
+
label: string;
|
|
7
|
+
href: string;
|
|
8
|
+
icon?: ReactNode;
|
|
9
|
+
/** Legacy AppRail state. MemberShell derives current state from currentNavigationId. */
|
|
10
|
+
active?: boolean;
|
|
11
|
+
}>;
|
|
12
|
+
export type MemberShellNavigationHandler = (event: Parameters<MouseEventHandler<HTMLAnchorElement>>[0], item: AppNavigationItem) => void;
|
|
13
|
+
export type MemberShellProps = Readonly<{
|
|
14
|
+
brand: ReactNode;
|
|
15
|
+
navigation: readonly AppNavigationItem[];
|
|
16
|
+
pageTitle: string;
|
|
17
|
+
/** Use null for a secondary route, such as Account, with no current primary destination. */
|
|
18
|
+
currentNavigationId?: string | null;
|
|
19
|
+
/** Top navigation keeps neighborhood discovery and coordination in one frame. */
|
|
20
|
+
navigationPlacement?: "top" | "rail";
|
|
21
|
+
/** Workspace fills the remaining pane with one controlled child, such as MemberInbox. */
|
|
22
|
+
contentLayout?: "document" | "workspace";
|
|
23
|
+
pageDescription?: ReactNode;
|
|
24
|
+
/** Optional object or person media beside the page title, with consumer-owned alt text. */
|
|
25
|
+
pageMedia?: ReactNode;
|
|
26
|
+
pageActions?: ReactNode;
|
|
27
|
+
utilityActions?: ReactNode;
|
|
28
|
+
children: ReactNode;
|
|
29
|
+
onNavigation?: MemberShellNavigationHandler;
|
|
30
|
+
className?: string;
|
|
31
|
+
}>;
|
|
32
|
+
/**
|
|
33
|
+
* Responsive member application frame. Products retain route transitions,
|
|
34
|
+
* route-heading focus and scroll restoration; this component owns landmarks,
|
|
35
|
+
* reading order and available-space layout only.
|
|
36
|
+
*/
|
|
37
|
+
export declare function MemberShell({ brand, navigation, pageTitle, currentNavigationId, navigationPlacement, contentLayout, pageDescription, pageMedia, pageActions, utilityActions, children, onNavigation, className, }: MemberShellProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
export const MEMBER_SHELL_MAIN_ID = "superic-member-shell-main";
|
|
3
|
+
export const MEMBER_SHELL_HEADING_ID = "superic-member-shell-heading";
|
|
4
|
+
function assertMemberShellNavigation(navigation, currentNavigationId) {
|
|
5
|
+
if (navigation.length > 5) {
|
|
6
|
+
throw new Error("MemberShell invariant: navigation supports zero through five destinations.");
|
|
7
|
+
}
|
|
8
|
+
const ids = navigation.map((item) => item.id);
|
|
9
|
+
if (new Set(ids).size !== ids.length) {
|
|
10
|
+
throw new Error("MemberShell invariant: navigation destination ids must be unique.");
|
|
11
|
+
}
|
|
12
|
+
if (navigation.length === 0) {
|
|
13
|
+
if (currentNavigationId !== undefined && currentNavigationId !== null) {
|
|
14
|
+
throw new Error("MemberShell invariant: currentNavigationId must be omitted when navigation is empty.");
|
|
15
|
+
}
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (currentNavigationId === null)
|
|
19
|
+
return;
|
|
20
|
+
if (!currentNavigationId || !ids.includes(currentNavigationId)) {
|
|
21
|
+
throw new Error("MemberShell invariant: navigation requires one matching currentNavigationId, or explicit null for a secondary route.");
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function MemberNavigation({ compact, currentNavigationId, items, onNavigation, }) {
|
|
25
|
+
return (_jsx("nav", { "aria-label": compact ? "Primary navigation" : "Primary navigation", "data-slot": compact
|
|
26
|
+
? "member-shell-compact-navigation"
|
|
27
|
+
: "member-shell-rail-navigation", children: _jsx("ul", { "data-slot": "member-shell-navigation-list", children: items.map((item) => (_jsx("li", { children: _jsxs("a", { "aria-current": item.id === currentNavigationId ? "page" : undefined, href: item.href, onClick: onNavigation ? (event) => onNavigation(event, item) : undefined, children: [item.icon ? (_jsx("span", { "aria-hidden": "true", "data-slot": "member-shell-navigation-icon", children: item.icon })) : null, _jsx("span", { children: item.label })] }) }, item.id))) }) }));
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Responsive member application frame. Products retain route transitions,
|
|
31
|
+
* route-heading focus and scroll restoration; this component owns landmarks,
|
|
32
|
+
* reading order and available-space layout only.
|
|
33
|
+
*/
|
|
34
|
+
export function MemberShell({ brand, navigation, pageTitle, currentNavigationId, navigationPlacement = "top", contentLayout = "document", pageDescription, pageMedia, pageActions, utilityActions, children, onNavigation, className, }) {
|
|
35
|
+
assertMemberShellNavigation(navigation, currentNavigationId);
|
|
36
|
+
const hasNavigation = navigation.length > 0;
|
|
37
|
+
return (_jsxs("div", { className: className, "data-has-navigation": hasNavigation ? "true" : "false", "data-navigation-placement": navigationPlacement, "data-content-layout": contentLayout, "data-slot": "member-shell", "data-superic-provider": "none", children: [_jsxs("header", { "data-slot": "member-shell-header", children: [_jsx("a", { "data-slot": "member-shell-skip-link", href: `#${MEMBER_SHELL_MAIN_ID}`, children: "Skip to main content" }), _jsxs("div", { "data-slot": "member-shell-header-inner", children: [_jsx("div", { "data-slot": "member-shell-brand", children: brand }), hasNavigation && navigationPlacement === "top" ? (_jsx("div", { "data-slot": "member-shell-top-navigation", children: _jsx(MemberNavigation, { compact: false, currentNavigationId: currentNavigationId ?? null, items: navigation, onNavigation: onNavigation }) })) : null, utilityActions ? (_jsx("div", { "data-slot": "member-shell-utility-actions", children: utilityActions })) : null] })] }), _jsxs("div", { "data-slot": "member-shell-body", children: [hasNavigation && navigationPlacement === "rail" ? (_jsx("div", { "data-slot": "member-shell-rail", children: _jsx(MemberNavigation, { compact: false, currentNavigationId: currentNavigationId ?? null, items: navigation, onNavigation: onNavigation }) })) : null, _jsx("main", { "aria-labelledby": MEMBER_SHELL_HEADING_ID, "data-slot": "member-shell-main", id: MEMBER_SHELL_MAIN_ID, tabIndex: -1, children: _jsxs("div", { "data-slot": "member-shell-content", children: [_jsxs("header", { "data-slot": "member-shell-page-header", children: [_jsxs("div", { "data-slot": "member-shell-page-identity", children: [pageMedia ? (_jsx("div", { "data-slot": "member-shell-page-media", children: pageMedia })) : null, _jsxs("div", { "data-slot": "member-shell-page-heading", children: [_jsx("h1", { id: MEMBER_SHELL_HEADING_ID, children: pageTitle }), pageDescription ? (_jsx("div", { "data-slot": "member-shell-page-description", children: pageDescription })) : null] })] }), pageActions ? (_jsx("div", { "data-slot": "member-shell-page-actions", children: pageActions })) : null] }), children] }) })] }), hasNavigation ? (_jsx("div", { "data-slot": "member-shell-compact-navigation-region", children: _jsx(MemberNavigation, { compact: true, currentNavigationId: currentNavigationId ?? null, items: navigation, onNavigation: onNavigation }) })) : null] }));
|
|
38
|
+
}
|
package/package.json
CHANGED
|
@@ -1,19 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@super-ic/product-shell",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Provider-neutral React application shell primitives: app frame, rails, page and state regions, top bar, segmented search pill, and settings switch row.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": [
|
|
8
|
+
"./css/*.css"
|
|
9
|
+
],
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
6
12
|
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"css",
|
|
7
15
|
"README.md"
|
|
8
16
|
],
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"default": "./dist/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./member-shell.css": "./css/member-shell.css"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsc -b tsconfig.json --force",
|
|
26
|
+
"prepack": "node ../../scripts/prepack-check.mjs"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"@super-ic/foundation": "0.2.4",
|
|
30
|
+
"react": ">=19.0.0",
|
|
31
|
+
"react-dom": ">=19.0.0"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public",
|
|
35
|
+
"registry": "https://registry.npmjs.org/"
|
|
36
|
+
},
|
|
9
37
|
"repository": {
|
|
10
38
|
"type": "git",
|
|
11
39
|
"url": "git+https://github.com/superic-hq/superic-hq.git",
|
|
12
40
|
"directory": "ds/packages/product-shell"
|
|
13
|
-
},
|
|
14
|
-
"publishConfig": {
|
|
15
|
-
"access": "public",
|
|
16
|
-
"registry": "https://registry.npmjs.org/",
|
|
17
|
-
"tag": "oidc-bootstrap"
|
|
18
41
|
}
|
|
19
42
|
}
|