@dloizides/ui-nav 1.9.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 CHANGED
@@ -1,5 +1,39 @@
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
+
3
37
  ## 1.9.0
4
38
 
5
39
  **`NavBar.contentMaxWidth` — a full-bleed bar with a centred content column.**
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
- /** Optional language toggle. */
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;
@@ -591,8 +609,9 @@ interface NavShellProps extends ForwardedShellProps {
591
609
  navigateHint?: (label: string) => string;
592
610
  /**
593
611
  * Top-bar config → a `NavBar` in the header slot. Required for `'top'` /
594
- * `'both'`; ignored for `'side'` unless supplied (a `'side'` app may still pass
595
- * a `topBar` with empty `items` for its brand + user header).
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.
596
615
  */
597
616
  topBar?: NavShellTopBar;
598
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
- /** Optional language toggle. */
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;
@@ -591,8 +609,9 @@ interface NavShellProps extends ForwardedShellProps {
591
609
  navigateHint?: (label: string) => string;
592
610
  /**
593
611
  * Top-bar config → a `NavBar` in the header slot. Required for `'top'` /
594
- * `'both'`; ignored for `'side'` unless supplied (a `'side'` app may still pass
595
- * a `topBar` with empty `items` for its brand + user header).
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.
596
615
  */
597
616
  topBar?: NavShellTopBar;
598
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
  }
@@ -1241,8 +1242,8 @@ var CollapsedRail = ({
1241
1242
  }
1242
1243
  );
1243
1244
  };
1244
- function wantsTopBar(layout, hasHeader, hasTopBar) {
1245
- return layout === "top" || layout === "both" || hasHeader || layout === "side" && hasTopBar;
1245
+ function wantsTopBar(layout, hasHeader) {
1246
+ return layout === "top" || layout === "both" || hasHeader;
1246
1247
  }
1247
1248
  function wantsSideRail(layout, hasSideRail) {
1248
1249
  return (layout === "side" || layout === "both") && hasSideRail;
@@ -1261,7 +1262,7 @@ var NavShell = ({
1261
1262
  collapsedRailRange: collapsedRailRangeProp,
1262
1263
  ...shellProps
1263
1264
  }) => {
1264
- const showTopBar = wantsTopBar(layout, header !== void 0, topBar !== void 0);
1265
+ const showTopBar = wantsTopBar(layout, header !== void 0);
1265
1266
  const showSideRail = wantsSideRail(layout, sideRail !== void 0);
1266
1267
  const headerNode = header !== void 0 ? header : showTopBar && topBar !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(
1267
1268
  NavBar,