@flamingo-stack/openframe-frontend-core 0.0.499 → 0.0.500

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.
Files changed (156) hide show
  1. package/dist/{chunk-XV22URNM.cjs → chunk-4NDGD2YH.cjs} +19 -19
  2. package/dist/{chunk-XV22URNM.cjs.map → chunk-4NDGD2YH.cjs.map} +1 -1
  3. package/dist/{chunk-YDNIQLAP.js → chunk-5CY6YKWY.js} +6 -6
  4. package/dist/{chunk-Q66TP2LY.js → chunk-6TJJFQ74.js} +6 -6
  5. package/dist/{chunk-47LHA4QI.cjs → chunk-C5R2OX7A.cjs} +7 -2
  6. package/dist/chunk-C5R2OX7A.cjs.map +1 -0
  7. package/dist/{chunk-NSDDV4F6.cjs → chunk-ETU6VFOL.cjs} +40 -40
  8. package/dist/{chunk-NSDDV4F6.cjs.map → chunk-ETU6VFOL.cjs.map} +1 -1
  9. package/dist/{chunk-WJ2S6I34.cjs → chunk-GDUWGCPP.cjs} +12 -12
  10. package/dist/{chunk-WJ2S6I34.cjs.map → chunk-GDUWGCPP.cjs.map} +1 -1
  11. package/dist/{chunk-44YXQBNM.cjs → chunk-I64ZGPA6.cjs} +3 -3
  12. package/dist/{chunk-44YXQBNM.cjs.map → chunk-I64ZGPA6.cjs.map} +1 -1
  13. package/dist/{chunk-7DUMNMED.cjs → chunk-IDRKW6MS.cjs} +12 -12
  14. package/dist/{chunk-7DUMNMED.cjs.map → chunk-IDRKW6MS.cjs.map} +1 -1
  15. package/dist/{chunk-SGL6URIJ.cjs → chunk-L5CI57RM.cjs} +9 -5
  16. package/dist/chunk-L5CI57RM.cjs.map +1 -0
  17. package/dist/{chunk-Q6B7VAHN.cjs → chunk-LN2X4XAB.cjs} +18 -18
  18. package/dist/{chunk-Q6B7VAHN.cjs.map → chunk-LN2X4XAB.cjs.map} +1 -1
  19. package/dist/{chunk-RAL274JM.js → chunk-P3B65FBX.js} +4 -4
  20. package/dist/{chunk-QWCW4Z75.js → chunk-P5UMIPKR.js} +5 -5
  21. package/dist/{chunk-QL5YB4F7.js → chunk-P7IOCYGD.js} +52 -47
  22. package/dist/chunk-P7IOCYGD.js.map +1 -0
  23. package/dist/{chunk-V2APV4E4.cjs → chunk-PAO45LBQ.cjs} +1113 -916
  24. package/dist/chunk-PAO45LBQ.cjs.map +1 -0
  25. package/dist/{chunk-3AXJESKA.js → chunk-QQAKDJPE.js} +3 -3
  26. package/dist/{chunk-O7JY7LRP.js → chunk-R2IGOYHU.js} +3 -3
  27. package/dist/{chunk-RBZOUGCL.cjs → chunk-S3COVC7H.cjs} +73 -73
  28. package/dist/{chunk-RBZOUGCL.cjs.map → chunk-S3COVC7H.cjs.map} +1 -1
  29. package/dist/{chunk-QUFB5SDZ.js → chunk-SOX7CGFX.js} +303 -106
  30. package/dist/{chunk-QUFB5SDZ.js.map → chunk-SOX7CGFX.js.map} +1 -1
  31. package/dist/{chunk-W42256FC.cjs → chunk-TJCUGL7S.cjs} +33 -33
  32. package/dist/{chunk-W42256FC.cjs.map → chunk-TJCUGL7S.cjs.map} +1 -1
  33. package/dist/{chunk-SSYE7BQC.js → chunk-UM2YFCPE.js} +3 -3
  34. package/dist/{chunk-2CEDCSYD.cjs → chunk-V5ALIJZ6.cjs} +32 -32
  35. package/dist/{chunk-2CEDCSYD.cjs.map → chunk-V5ALIJZ6.cjs.map} +1 -1
  36. package/dist/{chunk-EP7ELZTU.cjs → chunk-VLI6ESJN.cjs} +93 -93
  37. package/dist/{chunk-EP7ELZTU.cjs.map → chunk-VLI6ESJN.cjs.map} +1 -1
  38. package/dist/{chunk-BAMTV6GL.js → chunk-VMDQDD5Y.js} +2 -2
  39. package/dist/{chunk-IUP26OXG.js → chunk-VPQBQJ4S.js} +2 -2
  40. package/dist/{chunk-TTJSLIXA.js → chunk-W6Y66G4K.js} +3 -3
  41. package/dist/{chunk-QKWOBV55.js → chunk-WHJGRRK2.js} +9 -5
  42. package/dist/chunk-WHJGRRK2.js.map +1 -0
  43. package/dist/{chunk-AEZ5BXLJ.js → chunk-XBLRT3MX.js} +4 -4
  44. package/dist/{chunk-FLZTUE2T.cjs → chunk-YSN2AZJR.cjs} +5 -5
  45. package/dist/{chunk-FLZTUE2T.cjs.map → chunk-YSN2AZJR.cjs.map} +1 -1
  46. package/dist/{chunk-6QVV3OLL.cjs → chunk-ZLJCE75C.cjs} +40 -40
  47. package/dist/{chunk-6QVV3OLL.cjs.map → chunk-ZLJCE75C.cjs.map} +1 -1
  48. package/dist/{chunk-UD4IT3GY.js → chunk-ZU27I5NZ.js} +6 -6
  49. package/dist/components/case-studies/index.cjs +10 -10
  50. package/dist/components/case-studies/index.js +4 -4
  51. package/dist/components/chat/index.cjs +4 -4
  52. package/dist/components/chat/index.js +3 -3
  53. package/dist/components/contact/index.cjs +5 -5
  54. package/dist/components/contact/index.js +4 -4
  55. package/dist/components/docs/index.cjs +7 -7
  56. package/dist/components/docs/index.js +6 -6
  57. package/dist/components/embeds/index.cjs +5 -5
  58. package/dist/components/embeds/index.js +4 -4
  59. package/dist/components/faq/index.cjs +6 -6
  60. package/dist/components/faq/index.js +5 -5
  61. package/dist/components/features/filters-dropdown.d.ts.map +1 -1
  62. package/dist/components/features/index.cjs +4 -4
  63. package/dist/components/features/index.js +3 -3
  64. package/dist/components/help-center-pages/index.cjs +23 -23
  65. package/dist/components/help-center-pages/index.js +14 -14
  66. package/dist/components/index.cjs +165 -161
  67. package/dist/components/index.cjs.map +1 -1
  68. package/dist/components/index.js +16 -12
  69. package/dist/components/index.js.map +1 -1
  70. package/dist/components/navigation/app-header.d.ts +48 -0
  71. package/dist/components/navigation/app-header.d.ts.map +1 -1
  72. package/dist/components/navigation/header-mingo-button.d.ts.map +1 -1
  73. package/dist/components/navigation/index.cjs +6 -4
  74. package/dist/components/navigation/index.cjs.map +1 -1
  75. package/dist/components/navigation/index.d.ts +2 -2
  76. package/dist/components/navigation/index.d.ts.map +1 -1
  77. package/dist/components/navigation/index.js +5 -3
  78. package/dist/components/navigation/navigation-sidebar-item.d.ts +14 -0
  79. package/dist/components/navigation/navigation-sidebar-item.d.ts.map +1 -1
  80. package/dist/components/navigation/navigation-sidebar-toggle.d.ts.map +1 -1
  81. package/dist/components/navigation/navigation-sidebar.d.ts +18 -0
  82. package/dist/components/navigation/navigation-sidebar.d.ts.map +1 -1
  83. package/dist/components/onboarding-guides/index.cjs +8 -8
  84. package/dist/components/onboarding-guides/index.js +7 -7
  85. package/dist/components/related-content/index.cjs +6 -6
  86. package/dist/components/related-content/index.js +5 -5
  87. package/dist/components/tickets/index.cjs +7 -7
  88. package/dist/components/tickets/index.js +6 -6
  89. package/dist/components/ui/button/button-styles.d.ts +1 -0
  90. package/dist/components/ui/button/button-styles.d.ts.map +1 -1
  91. package/dist/components/ui/button/button.d.ts.map +1 -1
  92. package/dist/components/ui/data-table/data-table-body.d.ts.map +1 -1
  93. package/dist/components/ui/data-table/data-table-column-filter.d.ts.map +1 -1
  94. package/dist/components/ui/data-table/data-table-header.d.ts.map +1 -1
  95. package/dist/components/ui/data-table/data-table-row.d.ts.map +1 -1
  96. package/dist/components/ui/data-table/data-table-skeleton.d.ts +12 -0
  97. package/dist/components/ui/data-table/data-table-skeleton.d.ts.map +1 -1
  98. package/dist/components/ui/data-table/index.d.ts +1 -1
  99. package/dist/components/ui/data-table/index.d.ts.map +1 -1
  100. package/dist/components/ui/data-table/utils.d.ts +18 -0
  101. package/dist/components/ui/data-table/utils.d.ts.map +1 -1
  102. package/dist/components/ui/file-manager/index.cjs +50 -50
  103. package/dist/components/ui/file-manager/index.js +1 -1
  104. package/dist/components/ui/index.cjs +6 -4
  105. package/dist/components/ui/index.cjs.map +1 -1
  106. package/dist/components/ui/index.js +5 -3
  107. package/dist/hooks/index.cjs +4 -2
  108. package/dist/hooks/index.cjs.map +1 -1
  109. package/dist/hooks/index.js +3 -1
  110. package/dist/hooks/ui/index.d.ts +1 -0
  111. package/dist/hooks/ui/index.d.ts.map +1 -1
  112. package/dist/hooks/ui/use-isomorphic-layout-effect.d.ts +17 -0
  113. package/dist/hooks/ui/use-isomorphic-layout-effect.d.ts.map +1 -0
  114. package/dist/index.cjs +10 -4
  115. package/dist/index.cjs.map +1 -1
  116. package/dist/index.js +9 -3
  117. package/dist/types/navigation.d.ts +24 -0
  118. package/dist/types/navigation.d.ts.map +1 -1
  119. package/package.json +1 -1
  120. package/src/components/features/filters-dropdown.tsx +17 -5
  121. package/src/components/navigation/app-header.tsx +178 -0
  122. package/src/components/navigation/header-mingo-button.tsx +5 -0
  123. package/src/components/navigation/index.ts +2 -2
  124. package/src/components/navigation/navigation-sidebar-item.tsx +22 -0
  125. package/src/components/navigation/navigation-sidebar-toggle.tsx +10 -6
  126. package/src/components/navigation/navigation-sidebar.tsx +187 -44
  127. package/src/components/ui/button/button-styles.ts +11 -0
  128. package/src/components/ui/button/button.tsx +6 -3
  129. package/src/components/ui/button/split-button.tsx +2 -2
  130. package/src/components/ui/data-table/data-table-body.tsx +5 -2
  131. package/src/components/ui/data-table/data-table-column-filter.tsx +54 -25
  132. package/src/components/ui/data-table/data-table-header.tsx +134 -45
  133. package/src/components/ui/data-table/data-table-row.tsx +3 -2
  134. package/src/components/ui/data-table/data-table-skeleton.tsx +17 -2
  135. package/src/components/ui/data-table/index.ts +1 -1
  136. package/src/components/ui/data-table/utils.ts +49 -3
  137. package/src/hooks/ui/index.ts +1 -0
  138. package/src/hooks/ui/use-isomorphic-layout-effect.ts +20 -0
  139. package/src/types/navigation.ts +21 -0
  140. package/dist/chunk-47LHA4QI.cjs.map +0 -1
  141. package/dist/chunk-QKWOBV55.js.map +0 -1
  142. package/dist/chunk-QL5YB4F7.js.map +0 -1
  143. package/dist/chunk-SGL6URIJ.cjs.map +0 -1
  144. package/dist/chunk-V2APV4E4.cjs.map +0 -1
  145. /package/dist/{chunk-YDNIQLAP.js.map → chunk-5CY6YKWY.js.map} +0 -0
  146. /package/dist/{chunk-Q66TP2LY.js.map → chunk-6TJJFQ74.js.map} +0 -0
  147. /package/dist/{chunk-RAL274JM.js.map → chunk-P3B65FBX.js.map} +0 -0
  148. /package/dist/{chunk-QWCW4Z75.js.map → chunk-P5UMIPKR.js.map} +0 -0
  149. /package/dist/{chunk-3AXJESKA.js.map → chunk-QQAKDJPE.js.map} +0 -0
  150. /package/dist/{chunk-O7JY7LRP.js.map → chunk-R2IGOYHU.js.map} +0 -0
  151. /package/dist/{chunk-SSYE7BQC.js.map → chunk-UM2YFCPE.js.map} +0 -0
  152. /package/dist/{chunk-BAMTV6GL.js.map → chunk-VMDQDD5Y.js.map} +0 -0
  153. /package/dist/{chunk-IUP26OXG.js.map → chunk-VPQBQJ4S.js.map} +0 -0
  154. /package/dist/{chunk-TTJSLIXA.js.map → chunk-W6Y66G4K.js.map} +0 -0
  155. /package/dist/{chunk-AEZ5BXLJ.js.map → chunk-XBLRT3MX.js.map} +0 -0
  156. /package/dist/{chunk-UD4IT3GY.js.map → chunk-ZU27I5NZ.js.map} +0 -0
package/dist/index.js CHANGED
@@ -394,6 +394,7 @@ import {
394
394
  MspOrganizationCard,
395
395
  MspOrganizationCardSkeleton,
396
396
  MultiLevelNavigation,
397
+ NAVIGATION_SIDEBAR_WIDTH_VAR,
397
398
  NETWORK_CONFIG,
398
399
  NEW_TAB_FEATURES,
399
400
  NavLinkAnchorViaRuntime,
@@ -475,6 +476,7 @@ import {
475
476
  ROADMAP_STATUS_OPTIONS,
476
477
  ROW_HEIGHT_DESKTOP,
477
478
  ROW_HEIGHT_MOBILE,
479
+ ROW_SHELL_CLASSES,
478
480
  RadioGroup,
479
481
  RadioGroupBlock,
480
482
  RadioGroupItem,
@@ -989,7 +991,7 @@ import {
989
991
  walkthroughDismissCookieName,
990
992
  withFirstTouchAttribution,
991
993
  writeDismissCookie
992
- } from "./chunk-QUFB5SDZ.js";
994
+ } from "./chunk-SOX7CGFX.js";
993
995
  import {
994
996
  CommandApprovalToast,
995
997
  DEFAULT_MIN_FILL_MS,
@@ -1079,6 +1081,7 @@ import {
1079
1081
  useHumanitySignals,
1080
1082
  useImageEdgeColor,
1081
1083
  useIsTruncated,
1084
+ useIsomorphicLayoutEffect,
1082
1085
  useLgUp,
1083
1086
  useLocalStorage,
1084
1087
  useMarqueeEngine,
@@ -1109,7 +1112,7 @@ import {
1109
1112
  validateSchema,
1110
1113
  validateVariables,
1111
1114
  variablesToUrlParams
1112
- } from "./chunk-QL5YB4F7.js";
1115
+ } from "./chunk-P7IOCYGD.js";
1113
1116
  import {
1114
1117
  PLAY_ICON_PATH
1115
1118
  } from "./chunk-OYGFWWLQ.js";
@@ -1148,7 +1151,7 @@ import {
1148
1151
  SkeletonText,
1149
1152
  SplitButton,
1150
1153
  buttonVariants
1151
- } from "./chunk-QKWOBV55.js";
1154
+ } from "./chunk-WHJGRRK2.js";
1152
1155
  import "./chunk-6U3IUD57.js";
1153
1156
  import "./chunk-OHPI2HRK.js";
1154
1157
  import {
@@ -1823,6 +1826,7 @@ export {
1823
1826
  MspOrganizationCard,
1824
1827
  MspOrganizationCardSkeleton,
1825
1828
  MultiLevelNavigation,
1829
+ NAVIGATION_SIDEBAR_WIDTH_VAR,
1826
1830
  NEAR_VIEWPORT_ROOT_MARGIN,
1827
1831
  NETWORK_CONFIG,
1828
1832
  NEW_TAB_FEATURES,
@@ -1927,6 +1931,7 @@ export {
1927
1931
  ROADMAP_STATUS_OPTIONS,
1928
1932
  ROW_HEIGHT_DESKTOP,
1929
1933
  ROW_HEIGHT_MOBILE,
1934
+ ROW_SHELL_CLASSES,
1930
1935
  RadioGroup,
1931
1936
  RadioGroupBlock,
1932
1937
  RadioGroupItem,
@@ -2550,6 +2555,7 @@ export {
2550
2555
  useHumanitySignals,
2551
2556
  useImageEdgeColor,
2552
2557
  useIsTruncated,
2558
+ useIsomorphicLayoutEffect,
2553
2559
  useJetStreamDialogSubscription,
2554
2560
  useLgUp,
2555
2561
  useLoading,
@@ -87,6 +87,30 @@ export interface SlidingSidebarConfig {
87
87
  */
88
88
  export interface NavigationSidebarConfig {
89
89
  items: NavigationSidebarItem[];
90
+ /**
91
+ * Draw placeholder rows instead of `items`.
92
+ *
93
+ * For hosts whose navigation is not knowable at first paint — entries gated by
94
+ * server-loaded feature flags, permissions, or tenant config. Such a host has no
95
+ * good option without this: rendering the items it has yet means a nav that grows
96
+ * and shifts as answers arrive, and guessing the gated ones wrong shows entries
97
+ * that don't belong to the user (a flag that HIDES a legacy entry when enabled
98
+ * makes "not answered yet" indistinguishable from "off").
99
+ *
100
+ * The rows reuse the real entry's geometry and the sidebar's own minimized state,
101
+ * so the placeholder is correct in both the expanded and the minimized rail with
102
+ * nothing to pass in — and the handoff to the real nav moves nothing.
103
+ */
104
+ loading?: boolean;
105
+ /**
106
+ * How many placeholder rows `loading` draws, per section. Defaults to 7 primary
107
+ * and 2 secondary — the shape of a typical console nav. Pass the counts the host
108
+ * expects so the placeholder and the loaded nav are the same height.
109
+ */
110
+ loadingRows?: {
111
+ primary?: number;
112
+ secondary?: number;
113
+ };
90
114
  minimized?: boolean;
91
115
  onNavigate?: (path: string) => void;
92
116
  onToggleMinimized?: () => void;
@@ -1 +1 @@
1
- {"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../src/types/navigation.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../src/types/navigation.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flamingo-stack/openframe-frontend-core",
3
- "version": "0.0.499",
3
+ "version": "0.0.500",
4
4
  "description": "Shared design system and components for all Flamingo platforms",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -212,6 +212,19 @@ export const FiltersDropdown: React.FC<FiltersDropdownProps> = ({
212
212
  }
213
213
  }, [currentFiltersStr])
214
214
 
215
+ // Closing must NOT reset `actualPlacement`.
216
+ //
217
+ // The panel stays mounted through its exit transition (see `shouldRender`), so
218
+ // a reset here lands on something still on screen: a dropdown that had flipped
219
+ // to the opposite side snaps back the moment it starts fading, sliding roughly
220
+ // its own width sideways on the way out. It showed up on whichever filter sits
221
+ // nearest an edge — the last column on tablet, and the same column on desktop
222
+ // once the window is narrow enough to force the flip.
223
+ //
224
+ // Nothing is lost by dropping it: opening recomputes the placement from the
225
+ // trigger's current rect (see the effect above), so the value left behind is
226
+ // never read again.
227
+
215
228
  // Close dropdown when clicking outside
216
229
  useEffect(() => {
217
230
  const handleClickOutside = (event: MouseEvent) => {
@@ -221,7 +234,6 @@ export const FiltersDropdown: React.FC<FiltersDropdownProps> = ({
221
234
  !containerRef.current.contains(event.target as Node)
222
235
  ) {
223
236
  setIsOpen(false)
224
- setActualPlacement(placement)
225
237
  }
226
238
  }
227
239
 
@@ -236,19 +248,18 @@ export const FiltersDropdown: React.FC<FiltersDropdownProps> = ({
236
248
  document.removeEventListener('mousedown', handleClickOutside)
237
249
  }
238
250
  }
239
- }, [isOpen, placement])
251
+ }, [isOpen])
240
252
 
241
253
  // Handle escape key
242
254
  useEffect(() => {
243
255
  const handleEscape = (e: KeyboardEvent) => {
244
256
  if (e.key === 'Escape' && isOpen) {
245
257
  setIsOpen(false)
246
- setActualPlacement(placement)
247
258
  }
248
259
  }
249
260
  document.addEventListener('keydown', handleEscape)
250
261
  return () => document.removeEventListener('keydown', handleEscape)
251
- }, [isOpen, placement])
262
+ }, [isOpen])
252
263
 
253
264
  const handleToggleOption = (sectionId: string, optionId: string, sectionType: string) => {
254
265
  setSelectedFilters(prev => {
@@ -298,8 +309,9 @@ export const FiltersDropdown: React.FC<FiltersDropdownProps> = ({
298
309
 
299
310
  const handleApply = () => {
300
311
  onApply(selectedFilters)
312
+ // No placement reset — see the note above the outside-click effect. `Reset`
313
+ // never did one, which is why only Apply and the dismiss paths slid.
301
314
  setIsOpen(false)
302
- setActualPlacement(placement)
303
315
  }
304
316
 
305
317
  const getActiveFiltersCount = () => {
@@ -50,8 +50,57 @@ export interface AppHeaderProps {
50
50
  * EXCEPT the mobile burger menu toggle, which remains interactive.
51
51
  */
52
52
  disabled?: boolean;
53
+ /**
54
+ * Draw placeholder cells instead of the live controls.
55
+ *
56
+ * Solves a problem this header cannot solve on its own: its mobile/desktop split
57
+ * is decided by `useMdUp()`, which answers `undefined` until an effect has run —
58
+ * and `?? false` turns that into "mobile". The first render is therefore always
59
+ * the phone header (burger + wordmark, no side actions), which on a desktop load
60
+ * is a visible flash before the real layout appears. It is also what a host has
61
+ * to show while the user and the action flags are still loading.
62
+ *
63
+ * The placeholder decides mobile vs desktop in CSS instead, so it is correct at
64
+ * every width on the very first paint, server-rendered included.
65
+ */
66
+ loading?: boolean;
67
+ /**
68
+ * Shape of the trailing action cells `loading` reserves.
69
+ *
70
+ * Needed because the `show*` props are usually the very thing the host is still
71
+ * waiting on — they are typically driven by feature flags or permissions, so during
72
+ * `loading` they all read `false` and the placeholder collapses to whatever is
73
+ * hardcoded on (often just the avatar), which looks nothing like the loaded header.
74
+ *
75
+ * Pass the cells the header settles on, in order — see `HeaderLoadingCell` for the
76
+ * footprints. A bare number is accepted as shorthand for all-`'icon'`.
77
+ *
78
+ * Unlike the sidebar's rows, a cell that turns out not to exist is cheap here: the
79
+ * cells are a right-aligned cluster in otherwise empty space, so one disappearing
80
+ * shifts nothing else on the page.
81
+ *
82
+ * Defaults to the shape implied by the `show*` props, which is right for a host whose
83
+ * header composition is known up front.
84
+ */
85
+ loadingActionCells?: number | ReadonlyArray<HeaderLoadingCell>;
53
86
  }
54
87
 
88
+ /**
89
+ * A trailing header cell's footprint while loading — see `loadingActionCells`.
90
+ *
91
+ * The breakpoint-scoped members exist because several live cells are themselves
92
+ * responsive, and a placeholder that ignores that is wider than the header it hands
93
+ * off to on exactly the viewports where the cluster is tightest:
94
+ *
95
+ * - `'icon'` — a fixed 48/56px cell at every width (time tracker, notifications).
96
+ * - `'icon-md'` — the same cell, from md only (the avatar: `isMdUp && showUser`).
97
+ * - `'icon-lg'` — the same cell, from lg only (the organization filter, which the
98
+ * live header renders `hidden lg:flex`).
99
+ * - `'wide'` — the Mingo launcher: a 48px square below md, where the live button is
100
+ * `iconOnly`, and the fixed 140px labelled cell from md.
101
+ */
102
+ export type HeaderLoadingCell = 'icon' | 'icon-md' | 'icon-lg' | 'wide';
103
+
55
104
  export const AppHeader = React.memo(function AppHeader({
56
105
  showSearch,
57
106
  onSearch,
@@ -75,9 +124,36 @@ export const AppHeader = React.memo(function AppHeader({
75
124
  isMobileMenuOpen,
76
125
  onToggleMobileMenu,
77
126
  disabled = false,
127
+ loading = false,
128
+ loadingActionCells,
78
129
  }: AppHeaderProps) {
79
130
  const isMdUp = useMdUp() ?? false;
80
131
 
132
+ // After the only hook above, so the hook order is identical in both branches.
133
+ if (loading) {
134
+ return (
135
+ <AppHeaderSkeleton
136
+ showSearch={showSearch}
137
+ actionCells={
138
+ loadingActionCells ??
139
+ // Fallback: what the `show*` props imply, in the order the live cells are
140
+ // mounted below. Only correct for a host that knows its header composition
141
+ // before it starts loading. The cell types carry each control's own
142
+ // breakpoint — the organization filter is `hidden lg:flex`, the avatar is
143
+ // gated on `isMdUp`, and Mingo is `iconOnly` below md.
144
+ ([
145
+ showOrganizations && 'icon-lg',
146
+ showTimeTracker && 'icon',
147
+ showNotifications && 'icon',
148
+ showUser && 'icon-md',
149
+ showMingoAI && 'wide',
150
+ ].filter(Boolean) as HeaderLoadingCell[])
151
+ }
152
+ className={className}
153
+ />
154
+ );
155
+ }
156
+
81
157
  const dimmedClass = disabled ? 'pointer-events-none opacity-50' : '';
82
158
  // Cells carry their own dividers in the unified TopNavigation model
83
159
  // (the shell no longer applies divide-x).
@@ -268,3 +344,105 @@ function NotificationsHeaderButton({ fallbackUnreadCount, disabled, dimmedClass
268
344
  }
269
345
 
270
346
  export default AppHeader;
347
+
348
+ // Literal class strings — Tailwind's scanner needs to see them spelled out.
349
+ const CELL_VISIBILITY: Record<Exclude<HeaderLoadingCell, 'wide'>, string | undefined> = {
350
+ icon: undefined,
351
+ 'icon-md': 'hidden md:flex',
352
+ 'icon-lg': 'hidden lg:flex',
353
+ };
354
+
355
+ /** One trailing action cell placeholder — mirrors the live cells' 48/56px box. */
356
+ function HeaderCellSkeleton({ className }: { className?: string }) {
357
+ return (
358
+ <div
359
+ className={cn(
360
+ 'flex h-full w-12 shrink-0 items-center justify-center border-l border-ods-border md:w-14',
361
+ className,
362
+ )}
363
+ >
364
+ <div className="h-4 w-4 animate-pulse rounded bg-ods-border md:h-6 md:w-6" />
365
+ </div>
366
+ );
367
+ }
368
+
369
+ /**
370
+ * Wide cell placeholder — mirrors `HeaderMingoButton` at both widths, which are
371
+ * two different shapes: the live button gets `iconOnly={!isMdUp}`, so below md it
372
+ * drops its wordmark and collapses to the same 48px square as any other cell, and
373
+ * from md it is the fixed 140px labelled cell. Reserving 140px-worth of icon,
374
+ * label, gap and padding on a phone would make the placeholder cluster ~70px
375
+ * wider than the header it hands off to.
376
+ */
377
+ function HeaderWideCellSkeleton() {
378
+ return (
379
+ <div className="flex h-full w-12 shrink-0 items-center justify-center gap-2 border-l border-ods-border md:w-[140px] md:px-4">
380
+ <div className="h-4 w-4 shrink-0 animate-pulse rounded bg-ods-border md:h-6 md:w-6" />
381
+ <div className="hidden h-5 animate-pulse rounded bg-ods-border md:block md:w-[72px]" />
382
+ </div>
383
+ );
384
+ }
385
+
386
+ /**
387
+ * `AppHeader`'s loading state — see `AppHeaderProps.loading`.
388
+ *
389
+ * Keep in sync with `AppHeader` above; a diverging placeholder makes the handoff
390
+ * jump. Every breakpoint decision here is a Tailwind variant on purpose: this is the
391
+ * one thing the live header cannot do (its split runs through `useMdUp()`), and it
392
+ * is the whole reason this branch exists.
393
+ */
394
+ function AppHeaderSkeleton({
395
+ showSearch,
396
+ actionCells,
397
+ className,
398
+ }: {
399
+ showSearch?: boolean;
400
+ actionCells: number | ReadonlyArray<HeaderLoadingCell>;
401
+ className?: string;
402
+ }) {
403
+ const cells: ReadonlyArray<HeaderLoadingCell> =
404
+ typeof actionCells === 'number'
405
+ ? Array.from({ length: Math.max(0, actionCells) }, () => 'icon' as const)
406
+ : actionCells;
407
+ return (
408
+ <TopNavigation
409
+ className={cn('sticky top-0 z-40', className)}
410
+ centerBreakpoint="md"
411
+ aria-busy="true"
412
+ leading={
413
+ // Burger cell: mobile only, in CSS.
414
+ <div className="flex h-full w-12 shrink-0 items-center justify-center border-r border-ods-border md:hidden">
415
+ <div className="h-4 w-4 animate-pulse rounded bg-ods-border" />
416
+ </div>
417
+ }
418
+ logo={
419
+ <>
420
+ <div className="h-6 w-6 shrink-0 animate-pulse rounded bg-ods-border" />
421
+ <div className="h-4 w-24 animate-pulse rounded bg-ods-border" />
422
+ </>
423
+ }
424
+ // `md:hidden` belongs on the WRAPPER, not the placeholder inside it:
425
+ // `TopNavigation` builds its padded logo zone whenever `logo` is truthy, and
426
+ // that padding runs to 24px at md and 80px at lg. The live header passes
427
+ // `false` here from md up, so the zone does not exist at all — leaving the
428
+ // wrapper visible would inset the search field by 80px on desktop and shift
429
+ // it back on handoff, which is the exact jump this branch removes.
430
+ logoClassName="gap-2 md:hidden"
431
+ // `TopNavigation` already hides the center zone below `centerBreakpoint`.
432
+ center={showSearch ? <div className="h-10 w-full animate-pulse rounded-md bg-ods-border" /> : undefined}
433
+ sideActions={
434
+ <>
435
+ {/* Mobile-only search trigger; from md the search lives in the center zone. */}
436
+ {showSearch && <HeaderCellSkeleton className="md:hidden" />}
437
+ {cells.map((cell, i) =>
438
+ cell === 'wide' ? (
439
+ <HeaderWideCellSkeleton key={i} />
440
+ ) : (
441
+ <HeaderCellSkeleton key={i} className={CELL_VISIBILITY[cell]} />
442
+ ),
443
+ )}
444
+ </>
445
+ }
446
+ />
447
+ );
448
+ }
@@ -39,6 +39,11 @@ export function HeaderMingoButton({
39
39
  aria-pressed={isActive}
40
40
  className={cn(
41
41
  'flex items-center shrink-0 h-full gap-2 px-4',
42
+ // Fixed 140px cell from md, so the header's right-hand cluster has a
43
+ // known width and a loading placeholder can reserve it exactly
44
+ // (`AppHeaderProps.loadingActionCells` → `'wide'`). Content-width below
45
+ // md, and in `iconOnly` mode, where the control is a square affordance.
46
+ !iconOnly && 'md:w-[140px] md:justify-center',
42
47
  'transition-colors duration-200',
43
48
  'focus:outline-none focus-visible:ring-2 focus-visible:ring-ods-accent',
44
49
  // Transparent at rest so the cell inherits the bar's background
@@ -22,11 +22,11 @@ export type { SlidingSidebarProps } from './sliding-sidebar'
22
22
  export { StickySectionNav, useSectionNavigation } from './sticky-section-nav'
23
23
  export type { StickyNavSection } from './sticky-section-nav'
24
24
 
25
- export { NavigationSidebar } from './navigation-sidebar'
25
+ export { NAVIGATION_SIDEBAR_WIDTH_VAR, NavigationSidebar } from './navigation-sidebar'
26
26
  export type { NavigationSidebarProps } from './navigation-sidebar'
27
27
 
28
28
  export { AppHeader } from './app-header'
29
- export type { AppHeaderProps } from './app-header'
29
+ export type { AppHeaderProps, HeaderLoadingCell } from './app-header'
30
30
 
31
31
  export { AppLayout, useAppLayoutDrawerContainer } from './app-layout'
32
32
  export type { AppLayoutProps } from './app-layout'
@@ -130,3 +130,25 @@ export const NavigationSidebarItemButton = memo(function NavigationSidebarItemBu
130
130
  </Link>
131
131
  )
132
132
  })
133
+
134
+ /**
135
+ * One placeholder entry, for `NavigationSidebarConfig.loading`.
136
+ *
137
+ * Deliberately in this file, beside `NavigationSidebarItemButton`: the two must
138
+ * agree on `h-14`, the `p-[var(--spacing-system-m)]` inset, the 24px icon box and
139
+ * the label column, or the nav resizes on the handoff. Same reason
140
+ * `header-skeleton.tsx` sits beside `header.tsx`.
141
+ *
142
+ * `showLabel` is the sidebar's own minimized state, so the caller passes what it
143
+ * already passes the real row — no separate width handling.
144
+ */
145
+ export function NavigationSidebarItemSkeleton({ showLabel }: { showLabel: boolean }) {
146
+ return (
147
+ <div className="w-full flex items-center justify-start h-14 p-[var(--spacing-system-m)]" aria-hidden="true">
148
+ <div className="relative flex items-center justify-center flex-shrink-0">
149
+ <div className="h-6 w-6 animate-pulse rounded bg-ods-border" />
150
+ </div>
151
+ {showLabel && <div className="h-4 flex-1 ml-[var(--spacing-system-xs)] animate-pulse rounded bg-ods-border" />}
152
+ </div>
153
+ )
154
+ }
@@ -30,12 +30,16 @@ export function NavigationSidebarToggle({
30
30
  aria-label={"Hide Menu"}
31
31
  >
32
32
  <div className="flex items-center justify-center flex-shrink-0">
33
- <Chevrons03LeftIcon
34
- className={cn(
35
- "text-ods-text-secondary w-6 h-6 transition-transform duration-300",
36
- minimized && "rotate-180",
37
- )}
38
- />
33
+ {/* Direction follows the rail's measured width (the `of-nav-sidebar`
34
+ container declared in `navigation-sidebar.tsx`), not `minimized`:
35
+ before hydration that prop can only carry the server's answer, and
36
+ a chevron pointing the wrong way in a 56px rail is the one piece of
37
+ sidebar state a user actually notices arriving late.
38
+
39
+ Collapsed is the default because container queries are min-width —
40
+ the rail is narrower than every threshold, so it needs no query of
41
+ its own. */}
42
+ <Chevrons03LeftIcon className="rotate-180 @[140px]/of-nav-sidebar:rotate-0 text-ods-text-secondary w-6 h-6 transition-transform duration-300" />
39
43
  </div>
40
44
 
41
45
  <span