File size: 9,768 Bytes
eeb9404
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
'use client';

import React, { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Logo } from '@/components/ui/logo';
import { ChevronDown, ChevronUp, Menu } from 'lucide-react';

export interface HeaderAction {
  id: string;
  label: string;
  icon?: React.ComponentType<{ className?: string }>;
  onClick: () => void;
  variant?: 'default' | 'outline' | 'ghost' | 'secondary';
  size?: 'default' | 'sm' | 'lg' | 'icon';
  disabled?: boolean;
  content?: React.ReactNode;
  dataTourId?: string;
}

export interface ViewTab {
  id: string;
  label: string;
  icon?: React.ComponentType<{ className?: string }>;
}

interface AppHeaderProps {
  title?: string;
  subtitle?: string;
  badge?: string;
  onLogoClick?: () => void;
  actions?: HeaderAction[];
  mobileMenuContent?: React.ReactNode;
  desktopOnlyContent?: React.ReactNode; // Content shown only on desktop
  className?: string;
  leftText?: React.ReactNode; // Text to show next to logo on desktop, centered on mobile
  leftSubtext?: string; // Secondary line below leftText on mobile (e.g. active panel name)
  mobileVisibleActions?: string[]; // Action IDs to show outside dropdown on mobile
  viewTabs?: ViewTab[]; // View tabs for switching between sections
  activeViewTab?: string; // Currently active view tab
  onViewTabChange?: (tabId: string) => void; // Callback when view tab changes
  hideLogo?: boolean; // Hide the logo button (for use with sidebar)
  showMobileMenu?: boolean; // Show hamburger menu button on mobile (for sidebar)
  onMobileMenuClick?: () => void; // Callback when hamburger is clicked
  hideActionsOnMobile?: boolean; // Hide all actions on mobile (actions in sidebar instead)
  pageName?: string; // Page name to show in center on mobile (when showMobileMenu is true)
}

export function AppHeader({
  title,
  subtitle,
  badge,
  onLogoClick,
  actions = [],
  mobileMenuContent,
  desktopOnlyContent,
  className = '',
  leftText,
  leftSubtext,
  mobileVisibleActions = [],
  viewTabs,
  activeViewTab,
  onViewTabChange,
  hideLogo = false,
  showMobileMenu = false,
  onMobileMenuClick,
  hideActionsOnMobile = false,
  pageName
}: AppHeaderProps) {
  const [mobileMenuOpen, setMobileMenuOpen] = useState(false);

  // Split actions into mobile-visible and dropdown-only
  const mobileVisibleActionsSet = new Set(mobileVisibleActions);
  const visibleOnMobile = hideActionsOnMobile ? [] : actions.filter(a => mobileVisibleActionsSet.has(a.id));
  const dropdownOnlyActions = hideActionsOnMobile ? [] : actions.filter(a => !mobileVisibleActionsSet.has(a.id));

  return (
    <div className={`border-b bg-card shadow-sm relative z-20 ${className}`}>
      <div className="px-3 py-2 flex items-center justify-between">
        {/* Left side - Logo + pageName (mobile when showMobileMenu is true) */}
        {showMobileMenu && (
          <div className="md:hidden flex items-center gap-3">
            <Logo width={24} height={24} />
            {pageName && <span className="text-sm font-semibold">{pageName}</span>}
          </div>
        )}

        {/* Left side - Logo + text (desktop), empty (mobile with sidebar) */}
        {!hideLogo && !showMobileMenu && (
          <button
            onClick={onLogoClick}
            className="flex items-center gap-2 p-1 pr-2 hover:ring-1 hover:ring-border rounded-sm transition-all"
          >
            <Logo width={24} height={24} />
            {leftText && (
              <div className="flex flex-col items-start min-w-0">
                <span className="font-semibold text-sm md:text-lg truncate max-w-[160px] md:max-w-none">{leftText}</span>
                {leftSubtext && <span className="text-xs text-muted-foreground md:hidden">{leftSubtext}</span>}
              </div>
            )}
          </button>
        )}

        {/* Center - View tabs or leftText/title (desktop only now) */}
        <div className="flex items-center gap-2 flex-1 justify-center md:justify-start md:ml-6">
          {viewTabs && viewTabs.length > 0 ? (
            /* Show view tabs as pill toggle */
            <div className="flex border rounded-full">
              {viewTabs.map((tab, index) => (
                <Button
                  key={tab.id}
                  variant={activeViewTab === tab.id ? 'secondary' : 'ghost'}
                  size="sm"
                  onClick={() => onViewTabChange?.(tab.id)}
                  className={`gap-2 ${
                    index === 0 ? 'rounded-r-none rounded-l-full' :
                    index === viewTabs.length - 1 ? 'rounded-l-none rounded-r-full' :
                    'rounded-none'
                  }`}
                >
                  {tab.icon && <tab.icon className="h-4 w-4" />}
                  {tab.label}
                </Button>
              ))}
            </div>
          ) : title ? (
            <>
              {title && <h1 className="text-lg md:text-xl font-semibold">{title}</h1>}
              {badge && <Badge variant="secondary">{badge}</Badge>}
            </>
          ) : null}
        </div>

        {/* Desktop - Show subtitle only if no leftText and no center title */}
        {!leftText && !title && subtitle && (
          <div className="hidden md:flex items-center flex-1 ml-6">
            <span className="text-sm text-muted-foreground">{subtitle}</span>
          </div>
        )}

        {/* Right side controls */}
        <div className="flex items-center gap-2">
          {/* Desktop actions */}
          <div className="hidden md:flex items-center gap-2">
            {actions.map((action) => (
              action.content ? (
                <div key={action.id}>{action.content}</div>
              ) : (
                <Button
                  key={action.id}
                  variant={action.variant || 'outline'}
                  size={action.size || 'sm'}
                  onClick={action.onClick}
                  disabled={action.disabled}
                  className="justify-start"
                  data-tour-id={action.dataTourId}
                >
                  {action.icon && <action.icon className="h-4 w-4 mr-2" />}
                  {action.label}
                </Button>
              )
            ))}
            {desktopOnlyContent}
          </div>

          {/* Mobile visible actions */}
          <div className="md:hidden flex items-center gap-2">
            {visibleOnMobile.map((action) => (
              action.content ? (
                <div key={action.id}>{action.content}</div>
              ) : (
                <Button
                  key={action.id}
                  variant={action.variant || 'outline'}
                  size={action.size || 'sm'}
                  onClick={action.onClick}
                  disabled={action.disabled}
                  className="h-8 px-3"
                  data-tour-id={action.dataTourId}
                >
                  {action.icon && <action.icon className="h-4 w-4 mr-2" />}
                  {action.label}
                </Button>
              )
            ))}
          </div>

          {/* Mobile menu toggle */}
          {(dropdownOnlyActions.length > 0 || mobileMenuContent) && (
            <Button
              variant="ghost"
              size="icon"
              onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
              className="h-8 w-8 md:hidden"
            >
              {mobileMenuOpen ? (
                <ChevronUp className="h-4 w-4" />
              ) : (
                <ChevronDown className="h-4 w-4" />
              )}
            </Button>
          )}

          {/* Mobile hamburger menu (on right side) */}
          {showMobileMenu && (
            <Button
              variant="ghost"
              size="icon"
              onClick={onMobileMenuClick}
              className="md:hidden h-8 w-8"
            >
              <Menu className="h-5 w-5" />
            </Button>
          )}
        </div>
      </div>
      
      {/* Mobile dropdown menu */}
      {mobileMenuOpen && (dropdownOnlyActions.length > 0 || mobileMenuContent) && (
        <div className="md:hidden border-t bg-muted/30 px-4 py-4 space-y-3">
          {/* Show subtitle on mobile if no leftText and no center title */}
          {!leftText && !title && subtitle && (
            <div className="pb-2 border-b border-border/50">
              <p className="text-sm text-muted-foreground">{subtitle}</p>
            </div>
          )}

          {/* Mobile dropdown-only actions */}
          {dropdownOnlyActions.length > 0 && (
            <div className="space-y-2">
              {dropdownOnlyActions.map((action) => (
                action.content ? (
                  <div key={action.id}>{action.content}</div>
                ) : (
                  <Button
                    key={action.id}
                    variant={action.variant || 'outline'}
                    size={action.size || 'sm'}
                    onClick={() => {
                      action.onClick();
                      setMobileMenuOpen(false);
                    }}
                    disabled={action.disabled}
                    className="w-full justify-start"
                    data-tour-id={action.dataTourId}
                  >
                    {action.icon && <action.icon className="h-4 w-4 mr-2" />}
                    {action.label}
                  </Button>
                )
              ))}
            </div>
          )}

          {/* Custom mobile menu content */}
          {mobileMenuContent && (
            <div className="pt-2 border-t border-border/50">
              {mobileMenuContent}
            </div>
          )}
        </div>
      )}
    </div>
  );
}