@dloizides/ui-nav 1.8.0 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +49 -0
- package/README.md +14 -0
- package/dist/index.d.mts +39 -3
- package/dist/index.d.ts +39 -3
- package/dist/index.js +14 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +14 -7
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,54 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.10.0
|
|
4
|
+
|
|
5
|
+
- **Fix (`layout` was not authoritative — `layout="side"` rendered a top bar anyway).**
|
|
6
|
+
`wantsTopBar` carried an extra `(layout === 'side' && hasTopBar)` clause, so passing a
|
|
7
|
+
`topBar` **config** with `layout="side"` mounted the bar regardless — making `'side'` and
|
|
8
|
+
`'both'` render **pixel-identically** and `layout` unobservable. `layout` now decides which
|
|
9
|
+
REGIONS exist; `topBar` is only the CONTENT of the top region. A `'side'` app that also wants
|
|
10
|
+
a bar asks for `layout="both"`, which is what that combination always meant.
|
|
11
|
+
- Found by `apps/ui-showcase`, which renders every orientation side by side — a product app
|
|
12
|
+
picks ONE orientation and never sees the other, which is why this survived to 1.9.0.
|
|
13
|
+
- **Not breaking in this fleet:** no app passed `layout="side"` + `topBar`. The four `'side'`
|
|
14
|
+
portals (erevna, katalogos, zygos, agora) pass a `header` NODE, not a `topBar` config.
|
|
15
|
+
- The `hasHeader` clause is **deliberately kept**: `header` is the documented escape hatch by
|
|
16
|
+
which a `'side'` app supplies its own ready-made header (the structured `Topbar` with
|
|
17
|
+
language / notification / user slots). It is an explicit "render THIS node", not a config
|
|
18
|
+
whose presence is silently read as an orientation change.
|
|
19
|
+
- **a11y: `AccountState.accountHint`** — the account-name button's `accessibilityHint` was
|
|
20
|
+
hardcoded to `displayName`, byte-identical to its `accessibilityLabel`, so a screen reader
|
|
21
|
+
announced the name twice and never said what pressing it does — and the consumer had no way
|
|
22
|
+
to correct it. The sibling `upgrade?: TopbarAction` on the same interface has always carried
|
|
23
|
+
a `hint`; this closes that inconsistency. Falls back to `displayName`, so existing callers
|
|
24
|
+
are unaffected.
|
|
25
|
+
- **a11y: `AccountState.accountTestID`** — the account-name button's testID was hardcoded, with
|
|
26
|
+
no override, unlike `TopbarAction.testID?` / `NavItem.testID?` elsewhere in the package.
|
|
27
|
+
- **a11y: `Topbar.language.testID`** — the inline `language` prop type declared no `testID`, so
|
|
28
|
+
the repo's testID+label+hint standard was **structurally unsatisfiable** for that one button
|
|
29
|
+
while every other Topbar action (all `TopbarAction`) accepted one.
|
|
30
|
+
|
|
31
|
+
> Note for consumers: this react-native-web build does **not** forward `accessibilityHint` to
|
|
32
|
+
> the DOM at all — rendered buttons carry `aria-label` but no hint attribute. The hint is a
|
|
33
|
+
> NATIVE-only guarantee today. That is pre-existing and unchanged here, but it means DOM-level
|
|
34
|
+
> tests cannot distinguish a threaded hint from a dropped one; `topbarA11y.test.tsx` therefore
|
|
35
|
+
> pins the prop threading directly.
|
|
36
|
+
|
|
37
|
+
## 1.9.0
|
|
38
|
+
|
|
39
|
+
**`NavBar.contentMaxWidth` — a full-bleed bar with a centred content column.**
|
|
40
|
+
An optional content-column cap (px), forwarded via `NavShell.topBar.contentMaxWidth`.
|
|
41
|
+
When set, the OUTER `NavBar` container — which carries the bar background + bottom
|
|
42
|
+
border (from `barTheme` / theme surface + any `containerStyle`, e.g. the AML
|
|
43
|
+
radial-glow appbar) — stays FULL WIDTH (full-bleed), while the INNER row (brand +
|
|
44
|
+
links + right) is capped to `contentMaxWidth` and centred. This restores the v1-console
|
|
45
|
+
`.ui-appbar` (full-bleed) + `.ui-appbar-inner { max-width; margin: 0 auto }` shape:
|
|
46
|
+
previously an app had to put the cap on the container's `containerStyle`, which also
|
|
47
|
+
capped the dark background and left light gutters on wide viewports. Omit it and the
|
|
48
|
+
inner spans the full container (unchanged — byte-identical to before). The priority+
|
|
49
|
+
overflow "…" fit measures the capped inner's rendered width, so it keeps working
|
|
50
|
+
against the content column, not the full-bleed container. Purely additive.
|
|
51
|
+
|
|
3
52
|
## 1.8.0
|
|
4
53
|
|
|
5
54
|
The navigation shell becomes the ONE shell for every portal: a first-class
|
package/README.md
CHANGED
|
@@ -126,6 +126,7 @@ import { NavShell, type NavItem } from '@dloizides/ui-nav';
|
|
|
126
126
|
right: <><LanguageSwitcher /><UserChip /></>,
|
|
127
127
|
overflowLabel: FM('menu.more'),
|
|
128
128
|
barTheme: darkChromeTheme, // optional: dark bar over a light page
|
|
129
|
+
contentMaxWidth: 1180, // optional: full-bleed bar, inner capped + centred
|
|
129
130
|
}}
|
|
130
131
|
sideRail={{
|
|
131
132
|
items: sectionItems, // side sections (leaf + expandable)
|
|
@@ -169,6 +170,19 @@ are re-passed so labels keep localizing; every colour still flows through the th
|
|
|
169
170
|
(style-only). Custom appbar backgrounds (e.g. a radial-glow gradient) stay
|
|
170
171
|
expressible via `containerStyle`.
|
|
171
172
|
|
|
173
|
+
### Full-bleed bar with a centred content column (`NavBar.contentMaxWidth`)
|
|
174
|
+
|
|
175
|
+
Pass `contentMaxWidth` (px) — via `NavBar.contentMaxWidth` or
|
|
176
|
+
`NavShell.topBar.contentMaxWidth` — to make the bar **full-bleed** while its content
|
|
177
|
+
stays in a centred column. The OUTER container (which carries the bar background +
|
|
178
|
+
bottom border from `barTheme` / theme surface + any `containerStyle`, e.g. AML's
|
|
179
|
+
radial-glow appbar) spans the full viewport width; the INNER row (brand + links +
|
|
180
|
+
right) is capped to `contentMaxWidth` and centred — the v1-console `.ui-appbar`
|
|
181
|
+
(full-bleed) + `.ui-appbar-inner { max-width; margin: 0 auto }` shape. Omit it and
|
|
182
|
+
the inner spans the full container (unchanged). The priority+ overflow "…" fit
|
|
183
|
+
measures the capped inner's rendered width, so it keeps working against the content
|
|
184
|
+
column, not the full-bleed container.
|
|
185
|
+
|
|
172
186
|
### Cross-navigation pills (`PillNav`)
|
|
173
187
|
|
|
174
188
|
A row of fully-rounded pills from a `NavItem[]` — kefi's coral dashboard switcher.
|
package/dist/index.d.mts
CHANGED
|
@@ -120,16 +120,34 @@ interface AccountState {
|
|
|
120
120
|
plan?: AccountPlan;
|
|
121
121
|
onLogout: () => void;
|
|
122
122
|
onAccount?: () => void;
|
|
123
|
+
/**
|
|
124
|
+
* a11y hint for the account-name button (what pressing it DOES).
|
|
125
|
+
*
|
|
126
|
+
* Without this the hint fell back to `displayName` — byte-identical to the
|
|
127
|
+
* accessibility LABEL, so a screen reader announced the name twice and never
|
|
128
|
+
* said where the button goes. The sibling `upgrade?: TopbarAction` on this very
|
|
129
|
+
* interface has always carried a `hint`; this closes that inconsistency. The
|
|
130
|
+
* `displayName` fallback is retained so existing callers are unaffected.
|
|
131
|
+
*/
|
|
132
|
+
accountHint?: string;
|
|
133
|
+
/** testID for the account-name button. Defaults to `NAV_TEST_IDS.accountName`. */
|
|
134
|
+
accountTestID?: string;
|
|
123
135
|
upgrade?: TopbarAction;
|
|
124
136
|
}
|
|
125
137
|
interface TopbarProps {
|
|
126
138
|
/** Left slot — typically the tenant logo. */
|
|
127
139
|
left?: React.ReactNode;
|
|
128
|
-
/**
|
|
140
|
+
/**
|
|
141
|
+
* Optional language toggle. `testID` is optional but the repo standard wants
|
|
142
|
+
* one — before 1.10.0 this inline type declared none, so a testID was
|
|
143
|
+
* structurally unreachable for this button while every other Topbar action
|
|
144
|
+
* (which use `TopbarAction`) accepted one.
|
|
145
|
+
*/
|
|
129
146
|
language?: {
|
|
130
147
|
label: string;
|
|
131
148
|
hint: string;
|
|
132
149
|
onPress: () => void;
|
|
150
|
+
testID?: string;
|
|
133
151
|
};
|
|
134
152
|
/** Optional notification slot (e.g. a notification bell). */
|
|
135
153
|
notificationSlot?: React.ReactNode;
|
|
@@ -210,6 +228,16 @@ interface NavBarProps {
|
|
|
210
228
|
overflowHint?: string;
|
|
211
229
|
/** Extra container style overrides. */
|
|
212
230
|
containerStyle?: ViewStyle | ViewStyle[];
|
|
231
|
+
/**
|
|
232
|
+
* Optional content-column cap (px). When set, the OUTER container — which carries
|
|
233
|
+
* the background + bottom border (`barTheme`/theme surface + any `containerStyle`,
|
|
234
|
+
* e.g. the AML radial-glow appbar) — stays FULL WIDTH (full-bleed), while the INNER
|
|
235
|
+
* row (brand + links + right) is capped to this width and centred. Omit for the
|
|
236
|
+
* default where the inner spans the full container. The priority+ overflow "…" fit
|
|
237
|
+
* measures the (now capped) inner's rendered width via `onLayout`, so it keeps
|
|
238
|
+
* working against the capped column, not the full-bleed container.
|
|
239
|
+
*/
|
|
240
|
+
contentMaxWidth?: number;
|
|
213
241
|
/**
|
|
214
242
|
* Optional CHROME theme override — re-provisions the `@dloizides/ui-feedback`
|
|
215
243
|
* theme for the bar ONLY, so the top bar can be dark while the page below keeps
|
|
@@ -508,6 +536,13 @@ interface NavShellTopBar {
|
|
|
508
536
|
* `ViewStyle`, the AML radial-glow appbar). Style-only.
|
|
509
537
|
*/
|
|
510
538
|
containerStyle?: ViewStyle | ViewStyle[];
|
|
539
|
+
/**
|
|
540
|
+
* Optional content-column cap (px) for the top bar. When set, the bar's
|
|
541
|
+
* background + bottom border (from `containerStyle` / `barTheme`) stay FULL WIDTH
|
|
542
|
+
* (full-bleed) while the inner brand + links + right row is capped to this width
|
|
543
|
+
* and centred — the AML v1-console appbar shape. Forwarded to `NavBar.contentMaxWidth`.
|
|
544
|
+
*/
|
|
545
|
+
contentMaxWidth?: number;
|
|
511
546
|
/**
|
|
512
547
|
* Optional CHROME theme override for the top bar only (dark bar over a light
|
|
513
548
|
* page). Forwarded to `NavBar.barTheme`. Style-only — see `NavBar`.
|
|
@@ -574,8 +609,9 @@ interface NavShellProps extends ForwardedShellProps {
|
|
|
574
609
|
navigateHint?: (label: string) => string;
|
|
575
610
|
/**
|
|
576
611
|
* Top-bar config → a `NavBar` in the header slot. Required for `'top'` /
|
|
577
|
-
* `'both'`; ignored for `'side'
|
|
578
|
-
* a `
|
|
612
|
+
* `'both'`; **ignored for `'side'`** — `layout` decides which regions exist, so
|
|
613
|
+
* a `'side'` app that also wants a bar asks for `layout="both"`. A `'side'` app
|
|
614
|
+
* that wants its OWN header node uses the `header` escape hatch instead.
|
|
579
615
|
*/
|
|
580
616
|
topBar?: NavShellTopBar;
|
|
581
617
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -120,16 +120,34 @@ interface AccountState {
|
|
|
120
120
|
plan?: AccountPlan;
|
|
121
121
|
onLogout: () => void;
|
|
122
122
|
onAccount?: () => void;
|
|
123
|
+
/**
|
|
124
|
+
* a11y hint for the account-name button (what pressing it DOES).
|
|
125
|
+
*
|
|
126
|
+
* Without this the hint fell back to `displayName` — byte-identical to the
|
|
127
|
+
* accessibility LABEL, so a screen reader announced the name twice and never
|
|
128
|
+
* said where the button goes. The sibling `upgrade?: TopbarAction` on this very
|
|
129
|
+
* interface has always carried a `hint`; this closes that inconsistency. The
|
|
130
|
+
* `displayName` fallback is retained so existing callers are unaffected.
|
|
131
|
+
*/
|
|
132
|
+
accountHint?: string;
|
|
133
|
+
/** testID for the account-name button. Defaults to `NAV_TEST_IDS.accountName`. */
|
|
134
|
+
accountTestID?: string;
|
|
123
135
|
upgrade?: TopbarAction;
|
|
124
136
|
}
|
|
125
137
|
interface TopbarProps {
|
|
126
138
|
/** Left slot — typically the tenant logo. */
|
|
127
139
|
left?: React.ReactNode;
|
|
128
|
-
/**
|
|
140
|
+
/**
|
|
141
|
+
* Optional language toggle. `testID` is optional but the repo standard wants
|
|
142
|
+
* one — before 1.10.0 this inline type declared none, so a testID was
|
|
143
|
+
* structurally unreachable for this button while every other Topbar action
|
|
144
|
+
* (which use `TopbarAction`) accepted one.
|
|
145
|
+
*/
|
|
129
146
|
language?: {
|
|
130
147
|
label: string;
|
|
131
148
|
hint: string;
|
|
132
149
|
onPress: () => void;
|
|
150
|
+
testID?: string;
|
|
133
151
|
};
|
|
134
152
|
/** Optional notification slot (e.g. a notification bell). */
|
|
135
153
|
notificationSlot?: React.ReactNode;
|
|
@@ -210,6 +228,16 @@ interface NavBarProps {
|
|
|
210
228
|
overflowHint?: string;
|
|
211
229
|
/** Extra container style overrides. */
|
|
212
230
|
containerStyle?: ViewStyle | ViewStyle[];
|
|
231
|
+
/**
|
|
232
|
+
* Optional content-column cap (px). When set, the OUTER container — which carries
|
|
233
|
+
* the background + bottom border (`barTheme`/theme surface + any `containerStyle`,
|
|
234
|
+
* e.g. the AML radial-glow appbar) — stays FULL WIDTH (full-bleed), while the INNER
|
|
235
|
+
* row (brand + links + right) is capped to this width and centred. Omit for the
|
|
236
|
+
* default where the inner spans the full container. The priority+ overflow "…" fit
|
|
237
|
+
* measures the (now capped) inner's rendered width via `onLayout`, so it keeps
|
|
238
|
+
* working against the capped column, not the full-bleed container.
|
|
239
|
+
*/
|
|
240
|
+
contentMaxWidth?: number;
|
|
213
241
|
/**
|
|
214
242
|
* Optional CHROME theme override — re-provisions the `@dloizides/ui-feedback`
|
|
215
243
|
* theme for the bar ONLY, so the top bar can be dark while the page below keeps
|
|
@@ -508,6 +536,13 @@ interface NavShellTopBar {
|
|
|
508
536
|
* `ViewStyle`, the AML radial-glow appbar). Style-only.
|
|
509
537
|
*/
|
|
510
538
|
containerStyle?: ViewStyle | ViewStyle[];
|
|
539
|
+
/**
|
|
540
|
+
* Optional content-column cap (px) for the top bar. When set, the bar's
|
|
541
|
+
* background + bottom border (from `containerStyle` / `barTheme`) stay FULL WIDTH
|
|
542
|
+
* (full-bleed) while the inner brand + links + right row is capped to this width
|
|
543
|
+
* and centred — the AML v1-console appbar shape. Forwarded to `NavBar.contentMaxWidth`.
|
|
544
|
+
*/
|
|
545
|
+
contentMaxWidth?: number;
|
|
511
546
|
/**
|
|
512
547
|
* Optional CHROME theme override for the top bar only (dark bar over a light
|
|
513
548
|
* page). Forwarded to `NavBar.barTheme`. Style-only — see `NavBar`.
|
|
@@ -574,8 +609,9 @@ interface NavShellProps extends ForwardedShellProps {
|
|
|
574
609
|
navigateHint?: (label: string) => string;
|
|
575
610
|
/**
|
|
576
611
|
* Top-bar config → a `NavBar` in the header slot. Required for `'top'` /
|
|
577
|
-
* `'both'`; ignored for `'side'
|
|
578
|
-
* a `
|
|
612
|
+
* `'both'`; **ignored for `'side'`** — `layout` decides which regions exist, so
|
|
613
|
+
* a `'side'` app that also wants a bar asks for `layout="both"`. A `'side'` app
|
|
614
|
+
* that wants its OWN header node uses the `header` escape hatch instead.
|
|
579
615
|
*/
|
|
580
616
|
topBar?: NavShellTopBar;
|
|
581
617
|
/**
|
package/dist/index.js
CHANGED
|
@@ -540,6 +540,7 @@ var Topbar = ({
|
|
|
540
540
|
accessibilityLabel: language.label,
|
|
541
541
|
ringColor: primaryColor,
|
|
542
542
|
style: navStyles.topbarRowItem,
|
|
543
|
+
testID: language.testID,
|
|
543
544
|
onPress: language.onPress,
|
|
544
545
|
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [navStyles.topbarLabel, { color: colors.text }], children: language.label })
|
|
545
546
|
}
|
|
@@ -553,10 +554,10 @@ var Topbar = ({
|
|
|
553
554
|
richAccount.onAccount ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
554
555
|
FocusableTouchable,
|
|
555
556
|
{
|
|
556
|
-
accessibilityHint: richAccount.displayName,
|
|
557
|
+
accessibilityHint: richAccount.accountHint ?? richAccount.displayName,
|
|
557
558
|
accessibilityLabel: richAccount.displayName,
|
|
558
559
|
ringColor: primaryColor,
|
|
559
|
-
testID: NAV_TEST_IDS.accountName,
|
|
560
|
+
testID: richAccount.accountTestID ?? NAV_TEST_IDS.accountName,
|
|
560
561
|
onPress: richAccount.onAccount,
|
|
561
562
|
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [navStyles.userName, { color: colors.text }], children: richAccount.displayName })
|
|
562
563
|
}
|
|
@@ -806,7 +807,8 @@ var NavBarInner = ({
|
|
|
806
807
|
collapseBelow = DEFAULT_COLLAPSE_BELOW,
|
|
807
808
|
overflowLabel = "More",
|
|
808
809
|
overflowHint = "",
|
|
809
|
-
containerStyle
|
|
810
|
+
containerStyle,
|
|
811
|
+
contentMaxWidth
|
|
810
812
|
}) => {
|
|
811
813
|
const { theme } = uiFeedback.useUi();
|
|
812
814
|
const colors = theme.colors;
|
|
@@ -818,6 +820,10 @@ var NavBarInner = ({
|
|
|
818
820
|
const { visibleCount, setAvailableWidth, setItemWidth, setMoreWidth } = useNavOverflow(items.length, NAV_LINK_GAP);
|
|
819
821
|
const collapsed = width < collapseBelow;
|
|
820
822
|
const showLinks = !collapsed || open;
|
|
823
|
+
const innerCapStyle = React5.useMemo(
|
|
824
|
+
() => contentMaxWidth === void 0 ? null : { maxWidth: contentMaxWidth, width: "100%", alignSelf: "center" },
|
|
825
|
+
[contentMaxWidth]
|
|
826
|
+
);
|
|
821
827
|
const toggleMenu = React5.useCallback(() => setOpen((v) => !v), []);
|
|
822
828
|
const handlePress = React5.useCallback(
|
|
823
829
|
(route) => {
|
|
@@ -847,7 +853,7 @@ var NavBarInner = ({
|
|
|
847
853
|
{ backgroundColor: colors.surface, borderBottomColor: colors.border },
|
|
848
854
|
containerStyle
|
|
849
855
|
],
|
|
850
|
-
children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [navBarStyles.inner, collapsed ? null : navBarStyles.innerNoWrap], children: [
|
|
856
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [navBarStyles.inner, collapsed ? null : navBarStyles.innerNoWrap, innerCapStyle], children: [
|
|
851
857
|
brand !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: navBarStyles.brand, children: brand }) : null,
|
|
852
858
|
collapsed ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
853
859
|
reactNative.Pressable,
|
|
@@ -1236,8 +1242,8 @@ var CollapsedRail = ({
|
|
|
1236
1242
|
}
|
|
1237
1243
|
);
|
|
1238
1244
|
};
|
|
1239
|
-
function wantsTopBar(layout, hasHeader
|
|
1240
|
-
return layout === "top" || layout === "both" || hasHeader
|
|
1245
|
+
function wantsTopBar(layout, hasHeader) {
|
|
1246
|
+
return layout === "top" || layout === "both" || hasHeader;
|
|
1241
1247
|
}
|
|
1242
1248
|
function wantsSideRail(layout, hasSideRail) {
|
|
1243
1249
|
return (layout === "side" || layout === "both") && hasSideRail;
|
|
@@ -1256,7 +1262,7 @@ var NavShell = ({
|
|
|
1256
1262
|
collapsedRailRange: collapsedRailRangeProp,
|
|
1257
1263
|
...shellProps
|
|
1258
1264
|
}) => {
|
|
1259
|
-
const showTopBar = wantsTopBar(layout, header !== void 0
|
|
1265
|
+
const showTopBar = wantsTopBar(layout, header !== void 0);
|
|
1260
1266
|
const showSideRail = wantsSideRail(layout, sideRail !== void 0);
|
|
1261
1267
|
const headerNode = header !== void 0 ? header : showTopBar && topBar !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1262
1268
|
NavBar,
|
|
@@ -1265,6 +1271,7 @@ var NavShell = ({
|
|
|
1265
1271
|
brand: topBar.brand,
|
|
1266
1272
|
collapseBelow: topBar.collapseBelow,
|
|
1267
1273
|
containerStyle: topBar.containerStyle,
|
|
1274
|
+
contentMaxWidth: topBar.contentMaxWidth,
|
|
1268
1275
|
items: topBar.items,
|
|
1269
1276
|
menuHint: topBar.menuHint,
|
|
1270
1277
|
menuLabel: topBar.menuLabel,
|