@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.
- package/dist/{chunk-WPFLV4QJ.js → chunk-4BGDD6CU.js} +4 -4
- package/dist/{chunk-LIRXKEOE.cjs → chunk-7M24CJRH.cjs} +15 -15
- package/dist/{chunk-LIRXKEOE.cjs.map → chunk-7M24CJRH.cjs.map} +1 -1
- package/dist/{chunk-5ICPQAUI.cjs → chunk-CDRNCBLS.cjs} +59 -59
- package/dist/{chunk-5ICPQAUI.cjs.map → chunk-CDRNCBLS.cjs.map} +1 -1
- package/dist/{chunk-FG5ALORQ.js → chunk-DZU3FCOQ.js} +2 -2
- package/dist/{chunk-J2J36PEN.cjs → chunk-E5AIN5SS.cjs} +31 -31
- package/dist/{chunk-J2J36PEN.cjs.map → chunk-E5AIN5SS.cjs.map} +1 -1
- package/dist/{chunk-O6S6Q52W.cjs → chunk-FFL3D5LX.cjs} +26 -26
- package/dist/{chunk-O6S6Q52W.cjs.map → chunk-FFL3D5LX.cjs.map} +1 -1
- package/dist/{chunk-VML66FJQ.cjs → chunk-FP62TQ3A.cjs} +9 -9
- package/dist/{chunk-VML66FJQ.cjs.map → chunk-FP62TQ3A.cjs.map} +1 -1
- package/dist/{chunk-QHRNQTW5.js → chunk-GANUPKHV.js} +2 -2
- package/dist/{chunk-LVDI7R7T.cjs → chunk-J6MC4SY7.cjs} +37 -37
- package/dist/{chunk-LVDI7R7T.cjs.map → chunk-J6MC4SY7.cjs.map} +1 -1
- package/dist/{chunk-U5LU56JE.js → chunk-KDPQGHQ2.js} +2 -2
- package/dist/{chunk-56J7RLMM.js → chunk-KXOSLXL2.js} +1667 -1602
- package/dist/chunk-KXOSLXL2.js.map +1 -0
- package/dist/{chunk-OFFYASUY.cjs → chunk-L5GMHETR.cjs} +26 -26
- package/dist/{chunk-OFFYASUY.cjs.map → chunk-L5GMHETR.cjs.map} +1 -1
- package/dist/{chunk-P3ITKTUE.js → chunk-OQFXEWW2.js} +2 -2
- package/dist/{chunk-2XAAFAQT.cjs → chunk-P77L6MPJ.cjs} +11 -11
- package/dist/{chunk-2XAAFAQT.cjs.map → chunk-P77L6MPJ.cjs.map} +1 -1
- package/dist/{chunk-4J3CB4KO.js → chunk-PBAFZETL.js} +2 -2
- package/dist/{chunk-PI5BMKV3.cjs → chunk-QHGGQZFK.cjs} +62 -62
- package/dist/{chunk-PI5BMKV3.cjs.map → chunk-QHGGQZFK.cjs.map} +1 -1
- package/dist/{chunk-M4SCYPCR.cjs → chunk-QRHAIJMG.cjs} +7 -7
- package/dist/{chunk-M4SCYPCR.cjs.map → chunk-QRHAIJMG.cjs.map} +1 -1
- package/dist/{chunk-4MLOMMDX.cjs → chunk-UCZHGV4E.cjs} +635 -570
- package/dist/chunk-UCZHGV4E.cjs.map +1 -0
- package/dist/{chunk-SZWMJIH3.js → chunk-VCHK2B6W.js} +2 -2
- package/dist/{chunk-5RHJVPL7.js → chunk-VUUIYBIH.js} +4 -4
- package/dist/{chunk-4AWRPTKE.js → chunk-WUM5BTDY.js} +4 -4
- package/dist/{chunk-BTIRYGDT.js → chunk-YQ4B7LCE.js} +4 -4
- package/dist/{chunk-WKWZ7LVC.js → chunk-ZP4QMJX4.js} +2 -2
- package/dist/{chunk-DY6AJNVK.cjs → chunk-ZVCODNW7.cjs} +5 -5
- package/dist/{chunk-DY6AJNVK.cjs.map → chunk-ZVCODNW7.cjs.map} +1 -1
- package/dist/components/case-studies/index.cjs +8 -8
- package/dist/components/case-studies/index.js +2 -2
- package/dist/components/chat/index.cjs +2 -2
- package/dist/components/chat/index.js +1 -1
- package/dist/components/contact/index.cjs +3 -3
- package/dist/components/contact/index.js +2 -2
- package/dist/components/docs/index.cjs +5 -5
- package/dist/components/docs/index.js +4 -4
- package/dist/components/embeds/index.cjs +3 -3
- package/dist/components/embeds/index.js +2 -2
- package/dist/components/faq/index.cjs +3 -3
- package/dist/components/faq/index.js +2 -2
- package/dist/components/features/index.cjs +2 -2
- package/dist/components/features/index.js +1 -1
- package/dist/components/features/time-tracker/time-tracker-header-button.d.ts.map +1 -1
- package/dist/components/help-center-pages/index.cjs +20 -20
- package/dist/components/help-center-pages/index.js +11 -11
- package/dist/components/index.cjs +132 -130
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +11 -9
- package/dist/components/index.js.map +1 -1
- package/dist/components/navigation/app-header.d.ts.map +1 -1
- package/dist/components/navigation/header-button.d.ts.map +1 -1
- package/dist/components/navigation/header-global-search.d.ts.map +1 -1
- package/dist/components/navigation/header-mingo-button.d.ts.map +1 -1
- package/dist/components/navigation/header-organization-filter.d.ts.map +1 -1
- package/dist/components/navigation/header-skeleton.d.ts +6 -0
- package/dist/components/navigation/header-skeleton.d.ts.map +1 -1
- package/dist/components/navigation/header.d.ts.map +1 -1
- package/dist/components/navigation/index.cjs +4 -2
- package/dist/components/navigation/index.cjs.map +1 -1
- package/dist/components/navigation/index.d.ts +2 -0
- package/dist/components/navigation/index.d.ts.map +1 -1
- package/dist/components/navigation/index.js +3 -1
- package/dist/components/navigation/mingo-ai-button.d.ts.map +1 -1
- package/dist/components/navigation/mobile-nav-panel.d.ts.map +1 -1
- package/dist/components/navigation/top-navigation.d.ts +55 -0
- package/dist/components/navigation/top-navigation.d.ts.map +1 -0
- package/dist/components/onboarding-guides/index.cjs +5 -5
- package/dist/components/onboarding-guides/index.js +4 -4
- package/dist/components/related-content/index.cjs +3 -3
- package/dist/components/related-content/index.js +2 -2
- package/dist/components/tickets/index.cjs +5 -5
- package/dist/components/tickets/index.js +4 -4
- package/dist/components/ui/index.cjs +2 -2
- package/dist/components/ui/index.js +1 -1
- package/dist/index.cjs +4 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -1
- package/package.json +1 -1
- package/src/components/features/time-tracker/time-tracker-header-button.tsx +42 -38
- package/src/components/navigation/app-header.tsx +190 -198
- package/src/components/navigation/header-button.tsx +3 -1
- package/src/components/navigation/header-global-search.tsx +3 -1
- package/src/components/navigation/header-mingo-button.tsx +3 -1
- package/src/components/navigation/header-organization-filter.tsx +2 -1
- package/src/components/navigation/header-skeleton.tsx +57 -54
- package/src/components/navigation/header.tsx +231 -208
- package/src/components/navigation/index.ts +3 -0
- package/src/components/navigation/mingo-ai-button.tsx +24 -19
- package/src/components/navigation/mobile-nav-panel.tsx +6 -3
- package/src/components/navigation/top-navigation.tsx +128 -0
- package/src/stories/AppHeader.stories.tsx +148 -0
- package/src/stories/Header.stories.tsx +18 -23
- package/src/stories/TopNavigation.stories.tsx +150 -0
- package/dist/chunk-4MLOMMDX.cjs.map +0 -1
- package/dist/chunk-56J7RLMM.js.map +0 -1
- /package/dist/{chunk-WPFLV4QJ.js.map → chunk-4BGDD6CU.js.map} +0 -0
- /package/dist/{chunk-FG5ALORQ.js.map → chunk-DZU3FCOQ.js.map} +0 -0
- /package/dist/{chunk-QHRNQTW5.js.map → chunk-GANUPKHV.js.map} +0 -0
- /package/dist/{chunk-U5LU56JE.js.map → chunk-KDPQGHQ2.js.map} +0 -0
- /package/dist/{chunk-P3ITKTUE.js.map → chunk-OQFXEWW2.js.map} +0 -0
- /package/dist/{chunk-4J3CB4KO.js.map → chunk-PBAFZETL.js.map} +0 -0
- /package/dist/{chunk-SZWMJIH3.js.map → chunk-VCHK2B6W.js.map} +0 -0
- /package/dist/{chunk-5RHJVPL7.js.map → chunk-VUUIYBIH.js.map} +0 -0
- /package/dist/{chunk-4AWRPTKE.js.map → chunk-WUM5BTDY.js.map} +0 -0
- /package/dist/{chunk-BTIRYGDT.js.map → chunk-YQ4B7LCE.js.map} +0 -0
- /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 {
|
|
15
|
-
import {
|
|
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={
|
|
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
|
-
|
|
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 {
|
|
10
|
-
import {
|
|
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
|
-
<
|
|
84
|
-
className={cn(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
{
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
142
|
-
|
|
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
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|