@flamingo-stack/openframe-frontend-core 0.0.493 → 0.0.494

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 (115) hide show
  1. package/dist/{chunk-WPFLV4QJ.js → chunk-4BGDD6CU.js} +4 -4
  2. package/dist/{chunk-LIRXKEOE.cjs → chunk-7M24CJRH.cjs} +15 -15
  3. package/dist/{chunk-LIRXKEOE.cjs.map → chunk-7M24CJRH.cjs.map} +1 -1
  4. package/dist/{chunk-5ICPQAUI.cjs → chunk-CDRNCBLS.cjs} +59 -59
  5. package/dist/{chunk-5ICPQAUI.cjs.map → chunk-CDRNCBLS.cjs.map} +1 -1
  6. package/dist/{chunk-FG5ALORQ.js → chunk-DZU3FCOQ.js} +2 -2
  7. package/dist/{chunk-J2J36PEN.cjs → chunk-E5AIN5SS.cjs} +31 -31
  8. package/dist/{chunk-J2J36PEN.cjs.map → chunk-E5AIN5SS.cjs.map} +1 -1
  9. package/dist/{chunk-O6S6Q52W.cjs → chunk-FFL3D5LX.cjs} +26 -26
  10. package/dist/{chunk-O6S6Q52W.cjs.map → chunk-FFL3D5LX.cjs.map} +1 -1
  11. package/dist/{chunk-VML66FJQ.cjs → chunk-FP62TQ3A.cjs} +9 -9
  12. package/dist/{chunk-VML66FJQ.cjs.map → chunk-FP62TQ3A.cjs.map} +1 -1
  13. package/dist/{chunk-QHRNQTW5.js → chunk-GANUPKHV.js} +2 -2
  14. package/dist/{chunk-LVDI7R7T.cjs → chunk-J6MC4SY7.cjs} +37 -37
  15. package/dist/{chunk-LVDI7R7T.cjs.map → chunk-J6MC4SY7.cjs.map} +1 -1
  16. package/dist/{chunk-U5LU56JE.js → chunk-KDPQGHQ2.js} +2 -2
  17. package/dist/{chunk-56J7RLMM.js → chunk-KXOSLXL2.js} +1667 -1602
  18. package/dist/chunk-KXOSLXL2.js.map +1 -0
  19. package/dist/{chunk-OFFYASUY.cjs → chunk-L5GMHETR.cjs} +26 -26
  20. package/dist/{chunk-OFFYASUY.cjs.map → chunk-L5GMHETR.cjs.map} +1 -1
  21. package/dist/{chunk-P3ITKTUE.js → chunk-OQFXEWW2.js} +2 -2
  22. package/dist/{chunk-2XAAFAQT.cjs → chunk-P77L6MPJ.cjs} +11 -11
  23. package/dist/{chunk-2XAAFAQT.cjs.map → chunk-P77L6MPJ.cjs.map} +1 -1
  24. package/dist/{chunk-4J3CB4KO.js → chunk-PBAFZETL.js} +2 -2
  25. package/dist/{chunk-PI5BMKV3.cjs → chunk-QHGGQZFK.cjs} +62 -62
  26. package/dist/{chunk-PI5BMKV3.cjs.map → chunk-QHGGQZFK.cjs.map} +1 -1
  27. package/dist/{chunk-M4SCYPCR.cjs → chunk-QRHAIJMG.cjs} +7 -7
  28. package/dist/{chunk-M4SCYPCR.cjs.map → chunk-QRHAIJMG.cjs.map} +1 -1
  29. package/dist/{chunk-4MLOMMDX.cjs → chunk-UCZHGV4E.cjs} +635 -570
  30. package/dist/chunk-UCZHGV4E.cjs.map +1 -0
  31. package/dist/{chunk-SZWMJIH3.js → chunk-VCHK2B6W.js} +2 -2
  32. package/dist/{chunk-5RHJVPL7.js → chunk-VUUIYBIH.js} +4 -4
  33. package/dist/{chunk-4AWRPTKE.js → chunk-WUM5BTDY.js} +4 -4
  34. package/dist/{chunk-BTIRYGDT.js → chunk-YQ4B7LCE.js} +4 -4
  35. package/dist/{chunk-WKWZ7LVC.js → chunk-ZP4QMJX4.js} +2 -2
  36. package/dist/{chunk-DY6AJNVK.cjs → chunk-ZVCODNW7.cjs} +5 -5
  37. package/dist/{chunk-DY6AJNVK.cjs.map → chunk-ZVCODNW7.cjs.map} +1 -1
  38. package/dist/components/case-studies/index.cjs +8 -8
  39. package/dist/components/case-studies/index.js +2 -2
  40. package/dist/components/chat/index.cjs +2 -2
  41. package/dist/components/chat/index.js +1 -1
  42. package/dist/components/contact/index.cjs +3 -3
  43. package/dist/components/contact/index.js +2 -2
  44. package/dist/components/docs/index.cjs +5 -5
  45. package/dist/components/docs/index.js +4 -4
  46. package/dist/components/embeds/index.cjs +3 -3
  47. package/dist/components/embeds/index.js +2 -2
  48. package/dist/components/faq/index.cjs +3 -3
  49. package/dist/components/faq/index.js +2 -2
  50. package/dist/components/features/index.cjs +2 -2
  51. package/dist/components/features/index.js +1 -1
  52. package/dist/components/features/time-tracker/time-tracker-header-button.d.ts.map +1 -1
  53. package/dist/components/help-center-pages/index.cjs +20 -20
  54. package/dist/components/help-center-pages/index.js +11 -11
  55. package/dist/components/index.cjs +132 -130
  56. package/dist/components/index.cjs.map +1 -1
  57. package/dist/components/index.js +11 -9
  58. package/dist/components/index.js.map +1 -1
  59. package/dist/components/navigation/app-header.d.ts.map +1 -1
  60. package/dist/components/navigation/header-button.d.ts.map +1 -1
  61. package/dist/components/navigation/header-global-search.d.ts.map +1 -1
  62. package/dist/components/navigation/header-mingo-button.d.ts.map +1 -1
  63. package/dist/components/navigation/header-organization-filter.d.ts.map +1 -1
  64. package/dist/components/navigation/header-skeleton.d.ts +6 -0
  65. package/dist/components/navigation/header-skeleton.d.ts.map +1 -1
  66. package/dist/components/navigation/header.d.ts.map +1 -1
  67. package/dist/components/navigation/index.cjs +4 -2
  68. package/dist/components/navigation/index.cjs.map +1 -1
  69. package/dist/components/navigation/index.d.ts +2 -0
  70. package/dist/components/navigation/index.d.ts.map +1 -1
  71. package/dist/components/navigation/index.js +3 -1
  72. package/dist/components/navigation/mingo-ai-button.d.ts.map +1 -1
  73. package/dist/components/navigation/mobile-nav-panel.d.ts.map +1 -1
  74. package/dist/components/navigation/top-navigation.d.ts +55 -0
  75. package/dist/components/navigation/top-navigation.d.ts.map +1 -0
  76. package/dist/components/onboarding-guides/index.cjs +5 -5
  77. package/dist/components/onboarding-guides/index.js +4 -4
  78. package/dist/components/related-content/index.cjs +3 -3
  79. package/dist/components/related-content/index.js +2 -2
  80. package/dist/components/tickets/index.cjs +5 -5
  81. package/dist/components/tickets/index.js +4 -4
  82. package/dist/components/ui/index.cjs +2 -2
  83. package/dist/components/ui/index.js +1 -1
  84. package/dist/index.cjs +4 -2
  85. package/dist/index.cjs.map +1 -1
  86. package/dist/index.js +3 -1
  87. package/package.json +1 -1
  88. package/src/components/features/time-tracker/time-tracker-header-button.tsx +42 -38
  89. package/src/components/navigation/app-header.tsx +190 -198
  90. package/src/components/navigation/header-button.tsx +3 -1
  91. package/src/components/navigation/header-global-search.tsx +3 -1
  92. package/src/components/navigation/header-mingo-button.tsx +3 -1
  93. package/src/components/navigation/header-organization-filter.tsx +2 -1
  94. package/src/components/navigation/header-skeleton.tsx +57 -54
  95. package/src/components/navigation/header.tsx +231 -208
  96. package/src/components/navigation/index.ts +3 -0
  97. package/src/components/navigation/mingo-ai-button.tsx +24 -19
  98. package/src/components/navigation/mobile-nav-panel.tsx +6 -3
  99. package/src/components/navigation/top-navigation.tsx +128 -0
  100. package/src/stories/AppHeader.stories.tsx +148 -0
  101. package/src/stories/Header.stories.tsx +18 -23
  102. package/src/stories/TopNavigation.stories.tsx +150 -0
  103. package/dist/chunk-4MLOMMDX.cjs.map +0 -1
  104. package/dist/chunk-56J7RLMM.js.map +0 -1
  105. /package/dist/{chunk-WPFLV4QJ.js.map → chunk-4BGDD6CU.js.map} +0 -0
  106. /package/dist/{chunk-FG5ALORQ.js.map → chunk-DZU3FCOQ.js.map} +0 -0
  107. /package/dist/{chunk-QHRNQTW5.js.map → chunk-GANUPKHV.js.map} +0 -0
  108. /package/dist/{chunk-U5LU56JE.js.map → chunk-KDPQGHQ2.js.map} +0 -0
  109. /package/dist/{chunk-P3ITKTUE.js.map → chunk-OQFXEWW2.js.map} +0 -0
  110. /package/dist/{chunk-4J3CB4KO.js.map → chunk-PBAFZETL.js.map} +0 -0
  111. /package/dist/{chunk-SZWMJIH3.js.map → chunk-VCHK2B6W.js.map} +0 -0
  112. /package/dist/{chunk-5RHJVPL7.js.map → chunk-VUUIYBIH.js.map} +0 -0
  113. /package/dist/{chunk-4AWRPTKE.js.map → chunk-WUM5BTDY.js.map} +0 -0
  114. /package/dist/{chunk-BTIRYGDT.js.map → chunk-YQ4B7LCE.js.map} +0 -0
  115. /package/dist/{chunk-WKWZ7LVC.js.map → chunk-ZP4QMJX4.js.map} +0 -0
@@ -1,24 +1,24 @@
1
- 'use client'
1
+ 'use client';
2
2
 
3
- import * as PopoverPrimitive from '@radix-ui/react-popover'
4
- import { useEffect, useRef } from 'react'
5
- import { cn } from '../../../utils/cn'
6
- import { ClockHistoryIcon } from '../../icons-v2-generated/date-and-time/clock-history-icon'
3
+ import * as PopoverPrimitive from '@radix-ui/react-popover';
4
+ import { useEffect, useRef } from 'react';
5
+ import { cn } from '../../../utils/cn';
6
+ import { ClockHistoryIcon } from '../../icons-v2-generated/date-and-time/clock-history-icon';
7
7
  import {
8
8
  type AppLayoutDrawerHandle,
9
9
  useAppLayoutDrawerContainer,
10
10
  useAppLayoutDrawerCoordination,
11
- } from '../../navigation/app-layout-context'
12
- import { HeaderButton } from '../../navigation/header-button'
13
- import { OVERLAY_BACKDROP_CLASS } from '../../ui/drawer'
14
- import { TimeTrackerPanel } from './time-tracker-panel'
15
- import { useOptionalTimeTracker } from './time-tracker-context'
16
- import { useTrackerClock } from './use-tracker-clock'
11
+ } from '../../navigation/app-layout-context';
12
+ import { HeaderButton } from '../../navigation/header-button';
13
+ import { OVERLAY_BACKDROP_CLASS } from '../../ui/drawer';
14
+ import { useOptionalTimeTracker } from './time-tracker-context';
15
+ import { TimeTrackerPanel } from './time-tracker-panel';
16
+ import { useTrackerClock } from './use-tracker-clock';
17
17
 
18
18
  export interface TimeTrackerHeaderButtonProps {
19
19
  /** Dimming/disabled passthrough from the header. */
20
- className?: string
21
- disabled?: boolean
20
+ className?: string;
21
+ disabled?: boolean;
22
22
  }
23
23
 
24
24
  /**
@@ -32,49 +32,49 @@ export interface TimeTrackerHeaderButtonProps {
32
32
  * falls back to a viewport-wide backdrop.
33
33
  */
34
34
  export function TimeTrackerHeaderButton({ className, disabled }: TimeTrackerHeaderButtonProps) {
35
- const ctx = useOptionalTimeTracker()
35
+ const ctx = useOptionalTimeTracker();
36
36
  const elapsedLabel = useTrackerClock({
37
37
  status: ctx?.status ?? 'ready',
38
38
  runningSince: ctx?.runningSince,
39
39
  accumulatedMs: ctx?.accumulatedMs,
40
- })
41
- const layoutContainer = useAppLayoutDrawerContainer()
42
- const coordination = useAppLayoutDrawerCoordination()
40
+ });
41
+ const layoutContainer = useAppLayoutDrawerContainer();
42
+ const coordination = useAppLayoutDrawerCoordination();
43
43
 
44
- const isOpen = ctx?.isOpen ?? false
45
- const isOpenRef = useRef(isOpen)
46
- isOpenRef.current = isOpen
47
- const closeRef = useRef(ctx?.close)
48
- closeRef.current = ctx?.close
44
+ const isOpen = ctx?.isOpen ?? false;
45
+ const isOpenRef = useRef(isOpen);
46
+ isOpenRef.current = isOpen;
47
+ const closeRef = useRef(ctx?.close);
48
+ closeRef.current = ctx?.close;
49
49
  // Stable handle: the same object must be registered AND passed as `self`
50
50
  // to notifyDrawerDidOpen so the coordinator can skip it when closing the
51
51
  // other panels.
52
- const selfHandleRef = useRef<AppLayoutDrawerHandle | null>(null)
52
+ const selfHandleRef = useRef<AppLayoutDrawerHandle | null>(null);
53
53
  if (!selfHandleRef.current) {
54
54
  selfHandleRef.current = {
55
55
  close: () => {
56
- if (isOpenRef.current) closeRef.current?.()
56
+ if (isOpenRef.current) closeRef.current?.();
57
57
  },
58
- }
58
+ };
59
59
  }
60
60
 
61
61
  useEffect(() => {
62
- if (isOpen) coordination?.notifyDrawerDidOpen(selfHandleRef.current ?? undefined)
63
- }, [isOpen, coordination])
62
+ if (isOpen) coordination?.notifyDrawerDidOpen(selfHandleRef.current ?? undefined);
63
+ }, [isOpen, coordination]);
64
64
 
65
65
  useEffect(() => {
66
- if (!selfHandleRef.current) return
67
- return coordination?.registerDrawer(selfHandleRef.current)
68
- }, [coordination])
66
+ if (!selfHandleRef.current) return;
67
+ return coordination?.registerDrawer(selfHandleRef.current);
68
+ }, [coordination]);
69
69
 
70
- if (!ctx) return null
70
+ if (!ctx) return null;
71
71
 
72
- const { open, close, status } = ctx
73
- const isPaused = status === 'paused'
74
- const isActive = status === 'tracking' || isPaused
72
+ const { open, close, status } = ctx;
73
+ const isPaused = status === 'paused';
74
+ const isActive = status === 'tracking' || isPaused;
75
75
 
76
76
  return (
77
- <PopoverPrimitive.Root open={isOpen} onOpenChange={(next) => (next ? open() : close())}>
77
+ <PopoverPrimitive.Root open={isOpen} onOpenChange={next => (next ? open() : close())}>
78
78
  <PopoverPrimitive.Trigger asChild>
79
79
  <HeaderButton
80
80
  aria-label="Time tracker"
@@ -82,7 +82,11 @@ export function TimeTrackerHeaderButton({ className, disabled }: TimeTrackerHead
82
82
  disabled={disabled}
83
83
  className={cn(
84
84
  'outline-none',
85
- isActive && 'w-auto gap-[var(--spacing-system-xsf)] px-[var(--spacing-system-sf)] md:w-auto',
85
+ // ODS top-navigation spec (Figma 2797-5978): the active cell keeps
86
+ // its square icon-only footprint on mobile and becomes a fixed
87
+ // 144px cell with the live clock from md up.
88
+ isActive &&
89
+ 'md:w-auto md:min-w-[144px] md:justify-center md:gap-[var(--spacing-system-xsf)] md:px-[var(--spacing-system-sf)]',
86
90
  className,
87
91
  )}
88
92
  icon={
@@ -96,7 +100,7 @@ export function TimeTrackerHeaderButton({ className, disabled }: TimeTrackerHead
96
100
  {isActive && (
97
101
  <span
98
102
  className={cn(
99
- 'text-h5 md:text-h4 !font-mono',
103
+ 'hidden md:inline text-h5 md:text-h4 !font-mono',
100
104
  isPaused ? 'text-ods-text-secondary' : 'text-ods-text-primary',
101
105
  )}
102
106
  >
@@ -141,5 +145,5 @@ export function TimeTrackerHeaderButton({ className, disabled }: TimeTrackerHead
141
145
  </PopoverPrimitive.Content>
142
146
  </PopoverPrimitive.Portal>
143
147
  </PopoverPrimitive.Root>
144
- )
148
+ );
145
149
  }
@@ -1,54 +1,55 @@
1
- 'use client'
1
+ 'use client';
2
2
 
3
- import React from 'react'
4
- import { useMdUp } from '../../hooks/ui/use-media-query'
5
- import { cn } from '../../utils/cn'
6
- import { useOptionalNotifications } from '../features/notifications/notifications-context'
7
- import { TimeTrackerHeaderButton } from '../features/time-tracker'
8
- import { LogOutIcon, OpenFrameLogo, OpenFrameText, UserIcon } from '../icons'
9
- import { BellIcon } from '../icons-v2-generated/interface/bell-icon'
10
- import { Menu01Icon, SearchIcon, XmarkIcon } from '../icons-v2-generated'
11
- import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, SquareAvatar } from '../ui'
12
- import { HeaderButton } from './header-button'
13
- import { HeaderGlobalSearch } from './header-global-search'
14
- import { HeaderMingoButton } from './header-mingo-button'
15
- import { HeaderOrganizationFilter } from './header-organization-filter'
3
+ import React from 'react';
4
+ import { useMdUp } from '../../hooks/ui/use-media-query';
5
+ import { cn } from '../../utils/cn';
6
+ import { useOptionalNotifications } from '../features/notifications/notifications-context';
7
+ import { TimeTrackerHeaderButton } from '../features/time-tracker';
8
+ import { LogOutIcon, OpenFrameLogo, OpenFrameText, UserIcon } from '../icons';
9
+ import { Menu01Icon, SearchIcon, XmarkIcon } from '../icons-v2-generated';
10
+ import { BellIcon } from '../icons-v2-generated/interface/bell-icon';
11
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, SquareAvatar } from '../ui';
12
+ import { HeaderButton } from './header-button';
13
+ import { HeaderGlobalSearch } from './header-global-search';
14
+ import { HeaderMingoButton } from './header-mingo-button';
15
+ import { HeaderOrganizationFilter } from './header-organization-filter';
16
+ import { TopNavigation } from './top-navigation';
16
17
 
17
18
  export interface AppHeaderProps {
18
- showSearch?: boolean
19
- onSearch?: (query: string) => void
20
- showOrganizations?: boolean
21
- organizations?: { id: string; name: string }[]
22
- selectedOrgId?: string
23
- onOrgChange?: (id: string) => void
24
- showNotifications?: boolean
25
- unreadCount?: number
19
+ showSearch?: boolean;
20
+ onSearch?: (query: string) => void;
21
+ showOrganizations?: boolean;
22
+ organizations?: { id: string; name: string }[];
23
+ selectedOrgId?: string;
24
+ onOrgChange?: (id: string) => void;
25
+ showNotifications?: boolean;
26
+ unreadCount?: number;
26
27
  /** Render the time-tracker button + popup. Requires wrapping the app in `<TimeTrackerProvider>`. */
27
- showTimeTracker?: boolean
28
+ showTimeTracker?: boolean;
28
29
  /** Render the "Mingo AI" launcher button (drawer-style trigger for an
29
30
  * in-layout `AppLayoutDrawer` hosting the chat panel). Defaults to off. */
30
- showMingoAI?: boolean
31
+ showMingoAI?: boolean;
31
32
  /** Click handler for the Mingo AI button — typically toggles the drawer. */
32
- onMingoAI?: () => void
33
+ onMingoAI?: () => void;
33
34
  /** Whether the Mingo drawer is currently open (visually pressed state). */
34
- isMingoAIActive?: boolean
35
+ isMingoAIActive?: boolean;
35
36
  // User block
36
- showUser?: boolean
37
- userName?: string
38
- userEmail?: string
39
- userAvatarUrl?: string | null
40
- onProfile?: () => void
41
- onLogout?: () => void
42
- className?: string
37
+ showUser?: boolean;
38
+ userName?: string;
39
+ userEmail?: string;
40
+ userAvatarUrl?: string | null;
41
+ onProfile?: () => void;
42
+ onLogout?: () => void;
43
+ className?: string;
43
44
  /** Whether the mobile menu is open */
44
- isMobileMenuOpen: boolean
45
+ isMobileMenuOpen: boolean;
45
46
  /** Callback to toggle mobile menu */
46
- onToggleMobileMenu?: () => void
47
+ onToggleMobileMenu?: () => void;
47
48
  /**
48
49
  * When true, all header controls are disabled and visually dimmed
49
50
  * EXCEPT the mobile burger menu toggle, which remains interactive.
50
51
  */
51
- disabled?: boolean
52
+ disabled?: boolean;
52
53
  }
53
54
 
54
55
  export const AppHeader = React.memo(function AppHeader({
@@ -75,180 +76,173 @@ export const AppHeader = React.memo(function AppHeader({
75
76
  onToggleMobileMenu,
76
77
  disabled = false,
77
78
  }: AppHeaderProps) {
78
- const isMdUp = useMdUp() ?? false
79
+ const isMdUp = useMdUp() ?? false;
79
80
 
80
- const dimmedClass = disabled ? 'pointer-events-none opacity-50' : ''
81
+ const dimmedClass = disabled ? 'pointer-events-none opacity-50' : '';
82
+ // Cells carry their own dividers in the unified TopNavigation model
83
+ // (the shell no longer applies divide-x).
84
+ const cellDivider = 'border-l border-ods-border';
81
85
 
82
86
  return (
83
- <header
84
- className={cn(
85
- 'flex items-center w-full sticky top-0 z-40 border-b border-ods-border bg-ods-card h-12 md:h-14 divide-x divide-ods-border',
86
- className
87
- )}
88
- >
89
- {/* Mobile: Burger Menu Button */}
90
- {!isMdUp && <HeaderButton
91
- onClick={onToggleMobileMenu}
92
- isActive={isMobileMenuOpen}
93
- icon={isMobileMenuOpen ? <XmarkIcon className="w-4 h-4" /> : <Menu01Icon className="w-4 h-4" />}
94
- aria-label={isMobileMenuOpen ? "Close menu" : "Open menu"}
95
- aria-expanded={isMobileMenuOpen}
96
- />}
97
-
98
- {/* Mobile: Logo section */}
99
- {!isMdUp && <div className="flex items-center gap-2 px-3 h-full flex-1">
100
- <OpenFrameLogo
101
- className="w-6 h-6 shrink-0"
102
- upperPathColor="var(--color-text-primary)"
103
- lowerPathColor="var(--color-accent-primary)"
104
- />
105
- <OpenFrameText textColor="var(--color-text-primary)" className="h-4" />
106
- </div>}
107
-
108
- {/* Desktop: Global Search */}
109
- {showSearch ? (
110
- <HeaderGlobalSearch
111
- onSubmit={onSearch}
112
- className={cn("hidden md:flex", dimmedClass)}
113
- />
114
- ) : <div className="hidden md:flex w-full" />}
115
-
116
- {/* Mobile: Search button */}
117
- {showSearch && (
118
- <HeaderButton
119
- icon={<SearchIcon className="w-4 h-4 md:w-6 md:h-6" />}
120
- aria-label="Search"
121
- className={cn("md:hidden", dimmedClass)}
122
- disabled={disabled}
123
- />
124
- )}
125
-
126
- {/* Desktop: Organizations filter */}
127
- {showOrganizations && (
128
- <HeaderOrganizationFilter
129
- organizations={organizations}
130
- selectedOrgId={selectedOrgId}
131
- onOrgChange={onOrgChange}
132
- className={cn("hidden lg:flex", dimmedClass)}
133
- />
134
- )}
87
+ <TopNavigation
88
+ className={cn('sticky top-0 z-40', className)}
89
+ centerBreakpoint="md"
90
+ leading={
91
+ // Mobile: Burger Menu Button
92
+ !isMdUp && (
93
+ <HeaderButton
94
+ onClick={onToggleMobileMenu}
95
+ isActive={isMobileMenuOpen}
96
+ icon={isMobileMenuOpen ? <XmarkIcon className="w-4 h-4" /> : <Menu01Icon className="w-4 h-4" />}
97
+ aria-label={isMobileMenuOpen ? 'Close menu' : 'Open menu'}
98
+ aria-expanded={isMobileMenuOpen}
99
+ className="border-r border-ods-border"
100
+ />
101
+ )
102
+ }
103
+ logo={
104
+ !isMdUp && (
105
+ <>
106
+ <OpenFrameLogo
107
+ className="w-6 h-6 shrink-0"
108
+ upperPathColor="var(--color-text-primary)"
109
+ lowerPathColor="var(--color-accent-primary)"
110
+ />
111
+ <OpenFrameText textColor="var(--color-text-primary)" className="h-4" />
112
+ </>
113
+ )
114
+ }
115
+ logoClassName="gap-2"
116
+ center={showSearch ? <HeaderGlobalSearch onSubmit={onSearch} className={cn('w-full', dimmedClass)} /> : undefined}
117
+ sideActions={
118
+ <>
119
+ {/* Mobile: Search button */}
120
+ {showSearch && (
121
+ <HeaderButton
122
+ icon={<SearchIcon className="w-4 h-4 md:w-6 md:h-6" />}
123
+ aria-label="Search"
124
+ className={cn('md:hidden', cellDivider, dimmedClass)}
125
+ disabled={disabled}
126
+ />
127
+ )}
135
128
 
136
- {/* Time tracker button */}
137
- {showTimeTracker && (
138
- <TimeTrackerHeaderButton disabled={disabled} className={dimmedClass} />
139
- )}
129
+ {/* Desktop: Organizations filter (carries its own border-l) */}
130
+ {showOrganizations && (
131
+ <HeaderOrganizationFilter
132
+ organizations={organizations}
133
+ selectedOrgId={selectedOrgId}
134
+ onOrgChange={onOrgChange}
135
+ className={cn('hidden lg:flex', dimmedClass)}
136
+ />
137
+ )}
140
138
 
141
- {/* Notifications button */}
142
- {showNotifications && (
143
- <NotificationsHeaderButton
144
- fallbackUnreadCount={unreadCount}
145
- disabled={disabled}
146
- dimmedClass={dimmedClass}
147
- />
148
- )}
139
+ {/* Time tracker button */}
140
+ {showTimeTracker && <TimeTrackerHeaderButton disabled={disabled} className={cn(cellDivider, dimmedClass)} />}
149
141
 
150
- {isMdUp && showUser && (
151
- <DropdownMenu>
152
- <DropdownMenuTrigger asChild disabled={disabled}>
153
- <HeaderButton
154
- icon={<SquareAvatar
155
- src={userAvatarUrl || undefined}
156
- alt={userName || 'User'}
157
- size="sm"
158
- variant="round"
159
- className="shrink-0 w-8 h-8 md:w-10 md:h-10"
160
- />}
161
- aria-label="User"
142
+ {/* Notifications button */}
143
+ {showNotifications && (
144
+ <NotificationsHeaderButton
145
+ fallbackUnreadCount={unreadCount}
162
146
  disabled={disabled}
163
- className={cn(
164
- 'outline-none ring-0 focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0',
165
- dimmedClass,
166
- )}
147
+ dimmedClass={cn(cellDivider, dimmedClass)}
167
148
  />
168
- </DropdownMenuTrigger>
169
- <DropdownMenuContent
170
- align="end"
171
- // z-[104] — above the in-layout AppLayoutDrawer panel (z-[103]) so the
172
- // user menu stays clickable while the Mingo AI drawer is open
173
- className="w-[280px] p-0 bg-ods-bg border-ods-border rounded-[6px] overflow-hidden z-[104]"
174
- >
175
- {/* User info header section */}
176
- <div className="bg-ods-card border-b border-ods-border p-3 flex items-center gap-2">
177
- <SquareAvatar
178
- src={userAvatarUrl || undefined}
179
- alt={userName || 'User'}
180
- size="sm"
181
- variant="round"
182
- className="shrink-0"
183
- />
184
- <div className="flex-1 min-w-0">
185
- {userName && (
186
- <div className="text-h4 text-ods-text-primary truncate">
187
- {userName}
188
- </div>
189
- )}
190
- {userEmail && (
191
- <div className="text-h6 text-ods-text-secondary truncate">
192
- {userEmail}
149
+ )}
150
+
151
+ {isMdUp && showUser && (
152
+ <DropdownMenu>
153
+ <DropdownMenuTrigger asChild disabled={disabled}>
154
+ <HeaderButton
155
+ icon={
156
+ <SquareAvatar
157
+ src={userAvatarUrl || undefined}
158
+ alt={userName || 'User'}
159
+ size="sm"
160
+ variant="round"
161
+ className="shrink-0 w-8 h-8 md:w-10 md:h-10"
162
+ />
163
+ }
164
+ aria-label="User"
165
+ disabled={disabled}
166
+ className={cn(
167
+ 'outline-none ring-0 focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0',
168
+ cellDivider,
169
+ dimmedClass,
170
+ )}
171
+ />
172
+ </DropdownMenuTrigger>
173
+ <DropdownMenuContent
174
+ align="end"
175
+ // z-[104] — above the in-layout AppLayoutDrawer panel (z-[103]) so the
176
+ // user menu stays clickable while the Mingo AI drawer is open
177
+ className="w-[280px] p-0 bg-ods-bg border-ods-border rounded-[6px] overflow-hidden z-[104]"
178
+ >
179
+ {/* User info header section */}
180
+ <div className="bg-ods-card border-b border-ods-border p-3 flex items-center gap-2">
181
+ <SquareAvatar
182
+ src={userAvatarUrl || undefined}
183
+ alt={userName || 'User'}
184
+ size="sm"
185
+ variant="round"
186
+ className="shrink-0"
187
+ />
188
+ <div className="flex-1 min-w-0">
189
+ {userName && <div className="text-h4 text-ods-text-primary truncate">{userName}</div>}
190
+ {userEmail && <div className="text-h6 text-ods-text-secondary truncate">{userEmail}</div>}
193
191
  </div>
194
- )}
195
- </div>
196
- </div>
192
+ </div>
197
193
 
198
- {/* Menu items */}
199
- <DropdownMenuItem
200
- onClick={onProfile}
201
- className="bg-ods-card border-b border-ods-border rounded-none px-3 py-3 hover:bg-ods-card/80 focus:bg-ods-card/80 cursor-pointer"
202
- >
203
- <div className="flex items-center gap-2">
204
- <UserIcon className="h-6 w-6 text-ods-text-primary shrink-0" />
205
- <span className="text-h4 text-ods-text-primary">Profile Settings</span>
206
- </div>
207
- </DropdownMenuItem>
194
+ {/* Menu items */}
195
+ <DropdownMenuItem
196
+ onClick={onProfile}
197
+ className="bg-ods-card border-b border-ods-border rounded-none px-3 py-3 hover:bg-ods-card/80 focus:bg-ods-card/80 cursor-pointer"
198
+ >
199
+ <div className="flex items-center gap-2">
200
+ <UserIcon className="h-6 w-6 text-ods-text-primary shrink-0" />
201
+ <span className="text-h4 text-ods-text-primary">Profile Settings</span>
202
+ </div>
203
+ </DropdownMenuItem>
208
204
 
209
- <DropdownMenuItem
210
- onClick={onLogout}
211
- className="bg-ods-card rounded-none px-3 py-3 hover:bg-ods-card/80 focus:bg-ods-card/80 cursor-pointer"
212
- >
213
- <div className="flex items-center gap-2">
214
- <LogOutIcon className="text-ods-error shrink-0" size={24} />
215
- <span className="text-h4 text-ods-text-primary">Log Out</span>
216
- </div>
217
- </DropdownMenuItem>
218
- </DropdownMenuContent>
219
- </DropdownMenu>
220
- )}
205
+ <DropdownMenuItem
206
+ onClick={onLogout}
207
+ className="bg-ods-card rounded-none px-3 py-3 hover:bg-ods-card/80 focus:bg-ods-card/80 cursor-pointer"
208
+ >
209
+ <div className="flex items-center gap-2">
210
+ <LogOutIcon className="text-ods-error shrink-0" size={24} />
211
+ <span className="text-h4 text-ods-text-primary">Log Out</span>
212
+ </div>
213
+ </DropdownMenuItem>
214
+ </DropdownMenuContent>
215
+ </DropdownMenu>
216
+ )}
221
217
 
222
- {/* Mingo AI launcher — anchored at the very end of the header. On mobile
218
+ {/* Mingo AI launcher — anchored at the very end of the header. On mobile
223
219
  it collapses to an icon-only affordance (no "Mingo AI" wordmark). */}
224
- {showMingoAI && (
225
- <HeaderMingoButton
226
- onClick={onMingoAI}
227
- isActive={isMingoAIActive}
228
- iconOnly={!isMdUp}
229
- disabled={disabled || !onMingoAI}
230
- className={dimmedClass}
231
- />
232
- )}
233
- </header>
234
- )
235
- })
220
+ {showMingoAI && (
221
+ <HeaderMingoButton
222
+ onClick={onMingoAI}
223
+ isActive={isMingoAIActive}
224
+ iconOnly={!isMdUp}
225
+ disabled={disabled || !onMingoAI}
226
+ className={cn(cellDivider, dimmedClass)}
227
+ />
228
+ )}
229
+ </>
230
+ }
231
+ />
232
+ );
233
+ });
236
234
 
237
235
  interface NotificationsHeaderButtonProps {
238
- fallbackUnreadCount: number
239
- disabled: boolean
240
- dimmedClass: string
236
+ fallbackUnreadCount: number;
237
+ disabled: boolean;
238
+ dimmedClass: string;
241
239
  }
242
240
 
243
- function NotificationsHeaderButton({
244
- fallbackUnreadCount,
245
- disabled,
246
- dimmedClass,
247
- }: NotificationsHeaderButtonProps) {
248
- const ctx = useOptionalNotifications()
249
- const hasUnread = (ctx?.unreadCount ?? fallbackUnreadCount) > 0
250
- const isActive = ctx?.isOpen ?? false
251
- const onClick = ctx?.toggle
241
+ function NotificationsHeaderButton({ fallbackUnreadCount, disabled, dimmedClass }: NotificationsHeaderButtonProps) {
242
+ const ctx = useOptionalNotifications();
243
+ const hasUnread = (ctx?.unreadCount ?? fallbackUnreadCount) > 0;
244
+ const isActive = ctx?.isOpen ?? false;
245
+ const onClick = ctx?.toggle;
252
246
 
253
247
  return (
254
248
  <HeaderButton
@@ -259,9 +253,7 @@ function NotificationsHeaderButton({
259
253
  <div className="relative w-4 h-4 md:w-6 md:h-6">
260
254
  <BellIcon className="w-full h-full" />
261
255
  {hasUnread && (
262
- <span
263
- className="absolute top-0 right-0 bg-ods-warning rounded-full w-1.5 h-1.5 md:w-2 md:h-2"
264
- />
256
+ <span className="absolute top-0 right-0 bg-ods-warning rounded-full w-1.5 h-1.5 md:w-2 md:h-2" />
265
257
  )}
266
258
  </div>
267
259
  )
@@ -272,7 +264,7 @@ function NotificationsHeaderButton({
272
264
  disabled={disabled || !onClick}
273
265
  className={dimmedClass}
274
266
  />
275
- )
267
+ );
276
268
  }
277
269
 
278
- export default AppHeader
270
+ export default AppHeader;
@@ -26,7 +26,9 @@ export function HeaderButton({
26
26
  "w-12 h-full md:w-14",
27
27
  isActive
28
28
  ? "text-ods-text-primary bg-ods-bg-active"
29
- : "text-ods-text-secondary bg-ods-card hover:bg-ods-bg-hover",
29
+ : // Transparent at rest so the cell inherits the bar's background
30
+ // (TopNavigation `backgroundClassName` can differ per platform).
31
+ "text-ods-text-secondary bg-transparent hover:bg-ods-bg-hover",
30
32
  className
31
33
  )}
32
34
  {...props}
@@ -53,7 +53,9 @@ export function HeaderGlobalSearch({
53
53
  onSubmit={handleSubmit}
54
54
  className={cn(
55
55
  "flex flex-1 items-center gap-2 h-full px-3",
56
- "bg-ods-card",
56
+ // Transparent so the cell inherits the bar's background
57
+ // (TopNavigation `backgroundClassName` can differ per platform).
58
+ "bg-transparent",
57
59
  className
58
60
  )}
59
61
  >
@@ -41,7 +41,9 @@ export function HeaderMingoButton({
41
41
  'flex items-center shrink-0 h-full gap-2 px-4',
42
42
  'transition-colors duration-200',
43
43
  'focus:outline-none focus-visible:ring-2 focus-visible:ring-ods-accent',
44
- 'text-ods-text-primary bg-ods-card hover:bg-ods-bg-hover',
44
+ // Transparent at rest so the cell inherits the bar's background
45
+ // (TopNavigation `backgroundClassName` can differ per platform).
46
+ 'text-ods-text-primary bg-transparent hover:bg-ods-bg-hover',
45
47
  className,
46
48
  )}
47
49
  {...props}
@@ -40,7 +40,8 @@ export function HeaderOrganizationFilter({
40
40
  <button
41
41
  className={cn(
42
42
  "flex items-center gap-4 h-full px-4",
43
- "bg-ods-card border-l border-ods-border",
43
+ // Transparent so the cell inherits the bar's background.
44
+ "bg-transparent border-l border-ods-border",
44
45
  "w-[240px] shrink-0",
45
46
  "hover:bg-ods-bg-hover transition-colors",
46
47
  className