@sudobility/building_blocks 0.0.1

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/index.js ADDED
@@ -0,0 +1,769 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import React, { useState, useRef, useMemo, useCallback, useEffect } from "react";
3
+ import { TopbarProvider, Topbar, TopbarLeft, TopbarNavigation, TopbarLogo, TopbarRight, TopbarActions, BreadcrumbSection, Footer, FooterCompact, FooterCompactLeft, FooterVersion, FooterCopyright, FooterCompactRight, FooterGrid, FooterLinkSection, FooterLink, FooterBottom, FooterBrand, Logo, FooterSocialLinks, LayoutProvider } from "@sudobility/components";
4
+ import { clsx } from "clsx";
5
+ import { twMerge } from "tailwind-merge";
6
+ import { ChevronDownIcon, CalendarDaysIcon } from "@heroicons/react/24/outline";
7
+ import { GRADIENT_CLASSES } from "@sudobility/design";
8
+ import { cva } from "class-variance-authority";
9
+ function cn(...inputs) {
10
+ return twMerge(clsx(inputs));
11
+ }
12
+ const DEFAULT_LANGUAGES = [
13
+ { code: "en", name: "English", flag: "🇺🇸" },
14
+ { code: "ar", name: "العربية", flag: "🇸🇦" },
15
+ { code: "de", name: "Deutsch", flag: "🇩🇪" },
16
+ { code: "es", name: "Español", flag: "🇪🇸" },
17
+ { code: "fr", name: "Français", flag: "🇫🇷" },
18
+ { code: "it", name: "Italiano", flag: "🇮🇹" },
19
+ { code: "ja", name: "日本語", flag: "🇯🇵" },
20
+ { code: "ko", name: "한국어", flag: "🇰🇷" },
21
+ { code: "pt", name: "Português", flag: "🇵🇹" },
22
+ { code: "ru", name: "Русский", flag: "🇷🇺" },
23
+ { code: "sv", name: "Svenska", flag: "🇸🇪" },
24
+ { code: "th", name: "ไทย", flag: "🇹🇭" },
25
+ { code: "uk", name: "Українська", flag: "🇺🇦" },
26
+ { code: "vi", name: "Tiếng Việt", flag: "🇻🇳" },
27
+ { code: "zh", name: "简体中文", flag: "🇨🇳" },
28
+ { code: "zh-hant", name: "繁體中文", flag: "🇹🇼" }
29
+ ];
30
+ const RTL_LANGUAGES = ["ar"];
31
+ function isRTL(languageCode) {
32
+ return RTL_LANGUAGES.includes(languageCode);
33
+ }
34
+ const LanguageSelector = ({
35
+ languages = DEFAULT_LANGUAGES,
36
+ currentLanguage = "en",
37
+ onLanguageChange,
38
+ variant = "compact",
39
+ className,
40
+ label = "Language",
41
+ helperText = "Select your preferred language"
42
+ }) => {
43
+ const [isOpen, setIsOpen] = useState(false);
44
+ const dropdownRef = useRef(null);
45
+ const sortedLanguages = useMemo(
46
+ () => [...languages].sort((a, b) => a.name.localeCompare(b.name)),
47
+ [languages]
48
+ );
49
+ const currentLang = useMemo(
50
+ () => languages.find((lang) => lang.code === currentLanguage) || languages[0],
51
+ [languages, currentLanguage]
52
+ );
53
+ const handleLanguageChange = useCallback(
54
+ (langCode) => {
55
+ if (langCode !== currentLanguage) {
56
+ onLanguageChange == null ? void 0 : onLanguageChange(langCode);
57
+ }
58
+ setIsOpen(false);
59
+ },
60
+ [currentLanguage, onLanguageChange]
61
+ );
62
+ useEffect(() => {
63
+ const handleClickOutside = (event) => {
64
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
65
+ setIsOpen(false);
66
+ }
67
+ };
68
+ if (isOpen) {
69
+ document.addEventListener("mousedown", handleClickOutside);
70
+ return () => document.removeEventListener("mousedown", handleClickOutside);
71
+ }
72
+ }, [isOpen]);
73
+ useEffect(() => {
74
+ const handleEscape = (event) => {
75
+ if (event.key === "Escape") {
76
+ setIsOpen(false);
77
+ }
78
+ };
79
+ if (isOpen) {
80
+ document.addEventListener("keydown", handleEscape);
81
+ return () => document.removeEventListener("keydown", handleEscape);
82
+ }
83
+ }, [isOpen]);
84
+ if (variant === "compact") {
85
+ return /* @__PURE__ */ jsxs("div", { ref: dropdownRef, className: cn("relative", className), children: [
86
+ /* @__PURE__ */ jsxs(
87
+ "button",
88
+ {
89
+ onClick: () => setIsOpen(!isOpen),
90
+ className: cn(
91
+ "flex items-center gap-2 px-3 py-2 h-10 rounded-lg",
92
+ "hover:bg-gray-100 dark:hover:bg-gray-700",
93
+ "transition-colors"
94
+ ),
95
+ "aria-label": "Select language",
96
+ "aria-expanded": isOpen,
97
+ "aria-haspopup": "listbox",
98
+ children: [
99
+ /* @__PURE__ */ jsx("span", { className: "text-lg leading-none", children: currentLang == null ? void 0 : currentLang.flag }),
100
+ /* @__PURE__ */ jsx("span", { className: "hidden sm:block text-sm font-medium text-gray-700 dark:text-gray-300", children: currentLang == null ? void 0 : currentLang.name }),
101
+ /* @__PURE__ */ jsx(
102
+ ChevronDownIcon,
103
+ {
104
+ className: cn(
105
+ "h-4 w-4 text-gray-500 dark:text-gray-400 transition-transform",
106
+ isOpen && "rotate-180"
107
+ )
108
+ }
109
+ )
110
+ ]
111
+ }
112
+ ),
113
+ isOpen && /* @__PURE__ */ jsx(
114
+ "div",
115
+ {
116
+ className: cn(
117
+ "absolute right-0 mt-2 w-48 py-1 z-50",
118
+ "bg-white dark:bg-gray-800",
119
+ "border border-gray-200 dark:border-gray-700",
120
+ "rounded-lg shadow-lg",
121
+ "max-h-64 overflow-y-auto"
122
+ ),
123
+ role: "listbox",
124
+ "aria-label": "Languages",
125
+ children: sortedLanguages.map((lang) => /* @__PURE__ */ jsxs(
126
+ "button",
127
+ {
128
+ onClick: () => handleLanguageChange(lang.code),
129
+ className: cn(
130
+ "w-full flex items-center gap-3 px-3 py-2 text-left",
131
+ "hover:bg-gray-100 dark:hover:bg-gray-700",
132
+ "transition-colors",
133
+ lang.code === currentLanguage && "bg-gray-100 dark:bg-gray-700 font-medium"
134
+ ),
135
+ role: "option",
136
+ "aria-selected": lang.code === currentLanguage,
137
+ children: [
138
+ /* @__PURE__ */ jsx("span", { className: "text-lg leading-none", children: lang.flag }),
139
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-700 dark:text-gray-300", children: lang.name })
140
+ ]
141
+ },
142
+ lang.code
143
+ ))
144
+ }
145
+ )
146
+ ] });
147
+ }
148
+ return /* @__PURE__ */ jsxs("div", { ref: dropdownRef, className: cn("space-y-2", className), children: [
149
+ /* @__PURE__ */ jsx("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300 flex items-center gap-2", children: /* @__PURE__ */ jsx("span", { children: label }) }),
150
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
151
+ /* @__PURE__ */ jsxs(
152
+ "button",
153
+ {
154
+ onClick: () => setIsOpen(!isOpen),
155
+ className: cn(
156
+ "flex items-center justify-between w-full px-3 py-2 text-left",
157
+ "bg-white dark:bg-gray-800",
158
+ "border border-gray-300 dark:border-gray-600",
159
+ "rounded-md",
160
+ "hover:bg-gray-50 dark:hover:bg-gray-700",
161
+ "transition-colors"
162
+ ),
163
+ "aria-expanded": isOpen,
164
+ "aria-haspopup": "listbox",
165
+ children: [
166
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
167
+ /* @__PURE__ */ jsx("span", { className: "text-lg leading-none", children: currentLang == null ? void 0 : currentLang.flag }),
168
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-700 dark:text-gray-300", children: currentLang == null ? void 0 : currentLang.name })
169
+ ] }),
170
+ /* @__PURE__ */ jsx(
171
+ ChevronDownIcon,
172
+ {
173
+ className: cn(
174
+ "h-4 w-4 text-gray-500 dark:text-gray-400 transition-transform",
175
+ isOpen && "rotate-180"
176
+ )
177
+ }
178
+ )
179
+ ]
180
+ }
181
+ ),
182
+ isOpen && /* @__PURE__ */ jsx(
183
+ "div",
184
+ {
185
+ className: cn(
186
+ "absolute left-0 right-0 mt-1 py-1 z-50",
187
+ "bg-white dark:bg-gray-800",
188
+ "border border-gray-200 dark:border-gray-700",
189
+ "rounded-md shadow-lg",
190
+ "max-h-64 overflow-y-auto"
191
+ ),
192
+ role: "listbox",
193
+ "aria-label": "Languages",
194
+ children: sortedLanguages.map((lang) => /* @__PURE__ */ jsxs(
195
+ "button",
196
+ {
197
+ onClick: () => handleLanguageChange(lang.code),
198
+ className: cn(
199
+ "w-full flex items-center gap-3 px-3 py-2 text-left",
200
+ "hover:bg-gray-100 dark:hover:bg-gray-700",
201
+ "transition-colors",
202
+ lang.code === currentLanguage && "bg-gray-100 dark:bg-gray-700 font-medium"
203
+ ),
204
+ role: "option",
205
+ "aria-selected": lang.code === currentLanguage,
206
+ children: [
207
+ /* @__PURE__ */ jsx("span", { className: "text-lg leading-none", children: lang.flag }),
208
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-700 dark:text-gray-300", children: lang.name })
209
+ ]
210
+ },
211
+ lang.code
212
+ ))
213
+ }
214
+ )
215
+ ] }),
216
+ helperText && /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: helperText })
217
+ ] });
218
+ };
219
+ const DefaultLinkComponent$2 = ({
220
+ href,
221
+ className,
222
+ children,
223
+ onClick
224
+ }) => /* @__PURE__ */ jsx("a", { href, className, onClick, children });
225
+ const AppTopBar = ({
226
+ logo,
227
+ menuItems,
228
+ languages = DEFAULT_LANGUAGES,
229
+ currentLanguage = "en",
230
+ onLanguageChange,
231
+ hideLanguageSelector = false,
232
+ languageSelectorProps,
233
+ collapseBelow = "lg",
234
+ renderAccountSection,
235
+ LinkComponent = DefaultLinkComponent$2,
236
+ sticky = true,
237
+ variant = "default",
238
+ mobileMenuLabel = "Menu",
239
+ className,
240
+ zIndex = "highest",
241
+ ariaLabel = "Main navigation"
242
+ }) => {
243
+ const visibleMenuItems = useMemo(
244
+ () => menuItems.filter((item) => item.show !== false),
245
+ [menuItems]
246
+ );
247
+ const navItems = useMemo(
248
+ () => visibleMenuItems.map((item) => ({
249
+ id: item.id,
250
+ label: item.label,
251
+ icon: item.icon,
252
+ href: item.href
253
+ })),
254
+ [visibleMenuItems]
255
+ );
256
+ const LinkWrapper = useMemo(
257
+ () => ({ href, className: className2, children }) => /* @__PURE__ */ jsx(LinkComponent, { href, className: className2, children }),
258
+ [LinkComponent]
259
+ );
260
+ const handleLogoClick = () => {
261
+ var _a;
262
+ (_a = logo.onClick) == null ? void 0 : _a.call(logo);
263
+ };
264
+ return /* @__PURE__ */ jsx(TopbarProvider, { variant, sticky, children: /* @__PURE__ */ jsxs(
265
+ Topbar,
266
+ {
267
+ variant,
268
+ sticky,
269
+ zIndex,
270
+ "aria-label": ariaLabel,
271
+ className: cn(className),
272
+ children: [
273
+ /* @__PURE__ */ jsx(TopbarLeft, { children: /* @__PURE__ */ jsx(
274
+ TopbarNavigation,
275
+ {
276
+ items: navItems,
277
+ collapseBelow,
278
+ LinkComponent: LinkWrapper,
279
+ mobileMenuLabel,
280
+ children: /* @__PURE__ */ jsx(TopbarLogo, { onClick: handleLogoClick, size: "md", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
281
+ /* @__PURE__ */ jsx(
282
+ "img",
283
+ {
284
+ src: logo.src,
285
+ alt: logo.alt || logo.appName,
286
+ className: "h-8 w-8 object-contain"
287
+ }
288
+ ),
289
+ /* @__PURE__ */ jsx("span", { className: "text-lg font-semibold text-gray-900 dark:text-white", children: logo.appName })
290
+ ] }) })
291
+ }
292
+ ) }),
293
+ /* @__PURE__ */ jsx(TopbarRight, { children: /* @__PURE__ */ jsxs(TopbarActions, { gap: "md", children: [
294
+ !hideLanguageSelector && /* @__PURE__ */ jsx(
295
+ LanguageSelector,
296
+ {
297
+ languages,
298
+ currentLanguage,
299
+ onLanguageChange,
300
+ variant: "compact",
301
+ ...languageSelectorProps
302
+ }
303
+ ),
304
+ renderAccountSection == null ? void 0 : renderAccountSection()
305
+ ] }) })
306
+ ]
307
+ }
308
+ ) });
309
+ };
310
+ const AppTopBarWithFirebaseAuth = ({
311
+ AuthActionComponent,
312
+ authenticatedMenuItems = [],
313
+ onLoginClick,
314
+ avatarSize = 32,
315
+ dropdownAlign = "right",
316
+ loginButtonText,
317
+ loginButtonClassName,
318
+ ...topBarProps
319
+ }) => {
320
+ const renderAccountSection = () => /* @__PURE__ */ jsx(
321
+ AuthActionComponent,
322
+ {
323
+ avatarSize,
324
+ dropdownAlign,
325
+ onLoginClick,
326
+ menuItems: authenticatedMenuItems,
327
+ loginButtonText,
328
+ loginButtonClassName: cn(
329
+ "px-4 py-2 text-sm font-medium rounded-lg",
330
+ "bg-blue-600 text-white hover:bg-blue-700",
331
+ "transition-colors",
332
+ loginButtonClassName
333
+ )
334
+ }
335
+ );
336
+ return /* @__PURE__ */ jsx(AppTopBar, { ...topBarProps, renderAccountSection });
337
+ };
338
+ var AuthStatus = /* @__PURE__ */ ((AuthStatus2) => {
339
+ AuthStatus2["DISCONNECTED"] = "disconnected";
340
+ AuthStatus2["CONNECTED"] = "connected";
341
+ AuthStatus2["VERIFIED"] = "verified";
342
+ return AuthStatus2;
343
+ })(AuthStatus || {});
344
+ var ChainType = /* @__PURE__ */ ((ChainType2) => {
345
+ ChainType2["EVM"] = "evm";
346
+ ChainType2["SOLANA"] = "solana";
347
+ return ChainType2;
348
+ })(ChainType || {});
349
+ const DefaultConnectButton = ({ onClick, content = "Connect Wallet", className, useGradient }) => /* @__PURE__ */ jsx(
350
+ "button",
351
+ {
352
+ onClick,
353
+ className: cn(
354
+ useGradient ? GRADIENT_CLASSES.headerButton : "px-4 py-2 text-sm font-medium rounded-lg bg-blue-600 text-white hover:bg-blue-700 transition-colors",
355
+ className
356
+ ),
357
+ children: content
358
+ }
359
+ );
360
+ const FallbackWalletDisplay = ({ walletAddress, onDisconnect }) => {
361
+ const truncatedAddress = `${walletAddress.slice(0, 6)}...${walletAddress.slice(-4)}`;
362
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
363
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: truncatedAddress }),
364
+ /* @__PURE__ */ jsx(
365
+ "button",
366
+ {
367
+ onClick: () => onDisconnect(),
368
+ className: "text-xs text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200",
369
+ children: "Disconnect"
370
+ }
371
+ )
372
+ ] });
373
+ };
374
+ const AppTopBarWithWallet = ({
375
+ WalletDropdownMenuComponent,
376
+ isConnected,
377
+ walletAddress,
378
+ authStatus = "disconnected",
379
+ chainType = "unknown",
380
+ onConnect,
381
+ onDisconnect,
382
+ walletMenuItems = [],
383
+ connectButtonContent,
384
+ connectButtonClassName,
385
+ useGradientButton = true,
386
+ avatar,
387
+ displayName,
388
+ statusLabels,
389
+ ...topBarProps
390
+ }) => {
391
+ const renderAccountSection = () => {
392
+ if (!isConnected || !walletAddress) {
393
+ return /* @__PURE__ */ jsx(
394
+ DefaultConnectButton,
395
+ {
396
+ onClick: onConnect,
397
+ content: connectButtonContent,
398
+ className: connectButtonClassName,
399
+ useGradient: useGradientButton
400
+ }
401
+ );
402
+ }
403
+ if (WalletDropdownMenuComponent) {
404
+ return /* @__PURE__ */ jsx(
405
+ WalletDropdownMenuComponent,
406
+ {
407
+ walletAddress,
408
+ authStatus,
409
+ chainType,
410
+ menuItems: walletMenuItems,
411
+ avatar,
412
+ displayName,
413
+ statusLabels
414
+ }
415
+ );
416
+ }
417
+ return /* @__PURE__ */ jsx(
418
+ FallbackWalletDisplay,
419
+ {
420
+ walletAddress,
421
+ onDisconnect
422
+ }
423
+ );
424
+ };
425
+ return /* @__PURE__ */ jsx(AppTopBar, { ...topBarProps, renderAccountSection });
426
+ };
427
+ const breadcrumbContainerVariants = cva("border-b", {
428
+ variants: {
429
+ variant: {
430
+ default: "bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700",
431
+ transparent: "bg-transparent border-transparent",
432
+ subtle: "bg-gray-50 dark:bg-gray-900/50 border-gray-200 dark:border-gray-700"
433
+ }
434
+ },
435
+ defaultVariants: {
436
+ variant: "default"
437
+ }
438
+ });
439
+ const TalkToFounderButton = ({ config }) => {
440
+ const IconComponent = config.icon || CalendarDaysIcon;
441
+ const buttonText = config.buttonText || "Talk to Founder";
442
+ return /* @__PURE__ */ jsxs(
443
+ "a",
444
+ {
445
+ href: config.meetingUrl,
446
+ target: "_blank",
447
+ rel: "noopener noreferrer",
448
+ className: cn(
449
+ "inline-flex items-center gap-2 px-3 py-1.5",
450
+ "text-sm font-medium",
451
+ "text-blue-600 dark:text-blue-400",
452
+ "hover:text-blue-700 dark:hover:text-blue-300",
453
+ "bg-blue-50 dark:bg-blue-900/20",
454
+ "hover:bg-blue-100 dark:hover:bg-blue-900/30",
455
+ "rounded-full",
456
+ "border border-blue-200 dark:border-blue-800",
457
+ "transition-colors"
458
+ ),
459
+ children: [
460
+ /* @__PURE__ */ jsx(IconComponent, { className: "h-4 w-4" }),
461
+ /* @__PURE__ */ jsx("span", { children: buttonText })
462
+ ]
463
+ }
464
+ );
465
+ };
466
+ const AppBreadcrumbs = ({
467
+ items,
468
+ shareConfig,
469
+ talkToFounder,
470
+ variant = "default",
471
+ className,
472
+ contentClassName
473
+ }) => {
474
+ if (!items || items.length === 0) {
475
+ return null;
476
+ }
477
+ return /* @__PURE__ */ jsx("div", { className: cn(breadcrumbContainerVariants({ variant }), className), children: /* @__PURE__ */ jsx("div", { className: cn("max-w-7xl mx-auto px-4 py-3", contentClassName), children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4", children: [
478
+ /* @__PURE__ */ jsx("div", { className: "flex-1 min-w-0", children: /* @__PURE__ */ jsx(BreadcrumbSection, { items, shareConfig }) }),
479
+ talkToFounder && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-3 flex-shrink-0", children: /* @__PURE__ */ jsx(TalkToFounderButton, { config: talkToFounder }) })
480
+ ] }) }) });
481
+ };
482
+ const DefaultLinkComponent$1 = ({
483
+ href,
484
+ className,
485
+ children,
486
+ onClick
487
+ }) => /* @__PURE__ */ jsx("a", { href, className, onClick, children });
488
+ function getCopyrightYear$1(startYear = 2025) {
489
+ const currentYear = (/* @__PURE__ */ new Date()).getFullYear();
490
+ if (currentYear === startYear) {
491
+ return String(startYear);
492
+ } else if (currentYear > startYear) {
493
+ return `${startYear}-${currentYear}`;
494
+ }
495
+ return String(startYear);
496
+ }
497
+ const AppFooter = ({
498
+ version,
499
+ copyrightYear,
500
+ companyName,
501
+ companyUrl,
502
+ rightsText = "All rights reserved",
503
+ statusIndicator,
504
+ StatusIndicatorComponent,
505
+ links = [],
506
+ LinkComponent = DefaultLinkComponent$1,
507
+ sticky = true,
508
+ isNetworkOnline = true,
509
+ className
510
+ }) => {
511
+ const year = copyrightYear || getCopyrightYear$1();
512
+ const companyLink = companyUrl ? /* @__PURE__ */ jsx(
513
+ LinkComponent,
514
+ {
515
+ href: companyUrl,
516
+ className: "text-blue-400 hover:text-blue-300 transition-colors",
517
+ children: companyName
518
+ }
519
+ ) : void 0;
520
+ return /* @__PURE__ */ jsx(
521
+ Footer,
522
+ {
523
+ variant: "compact",
524
+ sticky,
525
+ className: cn(className),
526
+ children: /* @__PURE__ */ jsxs(FooterCompact, { children: [
527
+ /* @__PURE__ */ jsxs(FooterCompactLeft, { children: [
528
+ version && /* @__PURE__ */ jsx(FooterVersion, { version }),
529
+ /* @__PURE__ */ jsx(
530
+ FooterCopyright,
531
+ {
532
+ year,
533
+ companyName,
534
+ rightsText,
535
+ companyLink
536
+ }
537
+ ),
538
+ statusIndicator && StatusIndicatorComponent && /* @__PURE__ */ jsx(
539
+ StatusIndicatorComponent,
540
+ {
541
+ statusPageUrl: statusIndicator.statusPageUrl,
542
+ apiEndpoint: statusIndicator.apiEndpoint,
543
+ refreshInterval: statusIndicator.refreshInterval || 6e4,
544
+ size: "sm",
545
+ version,
546
+ isNetworkOnline
547
+ }
548
+ )
549
+ ] }),
550
+ /* @__PURE__ */ jsx(FooterCompactRight, { children: links.map((link, index) => /* @__PURE__ */ jsx(React.Fragment, { children: link.onClick ? /* @__PURE__ */ jsx(
551
+ "button",
552
+ {
553
+ onClick: link.onClick,
554
+ className: "text-gray-400 hover:text-white transition-colors",
555
+ children: link.label
556
+ }
557
+ ) : /* @__PURE__ */ jsx(
558
+ LinkComponent,
559
+ {
560
+ href: link.href,
561
+ className: "text-gray-400 hover:text-white transition-colors",
562
+ children: link.label
563
+ }
564
+ ) }, link.href || index)) })
565
+ ] })
566
+ }
567
+ );
568
+ };
569
+ const DefaultLinkComponent = ({
570
+ href,
571
+ className,
572
+ children,
573
+ onClick
574
+ }) => /* @__PURE__ */ jsx("a", { href, className, onClick, children });
575
+ function getCopyrightYear(startYear = 2025) {
576
+ const currentYear = (/* @__PURE__ */ new Date()).getFullYear();
577
+ if (currentYear === startYear) {
578
+ return String(startYear);
579
+ } else if (currentYear > startYear) {
580
+ return `${startYear}-${currentYear}`;
581
+ }
582
+ return String(startYear);
583
+ }
584
+ function getGridColumnsClass(sectionCount, explicit) {
585
+ const cols = explicit || Math.min(sectionCount, 4);
586
+ switch (cols) {
587
+ case 2:
588
+ return "md:grid-cols-2";
589
+ case 3:
590
+ return "md:grid-cols-3";
591
+ case 4:
592
+ return "md:grid-cols-4";
593
+ case 5:
594
+ return "md:grid-cols-5";
595
+ default:
596
+ return "md:grid-cols-4";
597
+ }
598
+ }
599
+ const AppFooterForHomePage = ({
600
+ logo,
601
+ linkSections,
602
+ socialLinks,
603
+ statusIndicator,
604
+ StatusIndicatorComponent,
605
+ version,
606
+ copyrightYear,
607
+ companyName,
608
+ companyUrl,
609
+ description,
610
+ rightsText = "All rights reserved",
611
+ LinkComponent = DefaultLinkComponent,
612
+ isNetworkOnline = true,
613
+ className,
614
+ gridColumns
615
+ }) => {
616
+ const year = copyrightYear || getCopyrightYear();
617
+ const gridClass = getGridColumnsClass(linkSections.length, gridColumns);
618
+ const companyLink = companyUrl ? /* @__PURE__ */ jsx(
619
+ LinkComponent,
620
+ {
621
+ href: companyUrl,
622
+ className: "text-blue-400 hover:text-blue-300 transition-colors",
623
+ children: companyName
624
+ }
625
+ ) : void 0;
626
+ return /* @__PURE__ */ jsxs(Footer, { variant: "full", className: cn(className), children: [
627
+ /* @__PURE__ */ jsx(FooterGrid, { className: gridClass, children: linkSections.map((section, sectionIndex) => /* @__PURE__ */ jsx(
628
+ FooterLinkSection,
629
+ {
630
+ title: section.title,
631
+ children: section.links.map((link, linkIndex) => /* @__PURE__ */ jsx(FooterLink, { children: link.onClick ? /* @__PURE__ */ jsx("button", { onClick: link.onClick, className: "text-left", children: link.label }) : /* @__PURE__ */ jsx(LinkComponent, { href: link.href, children: link.label }) }, link.href || linkIndex))
632
+ },
633
+ section.title || sectionIndex
634
+ )) }),
635
+ /* @__PURE__ */ jsxs(FooterBottom, { children: [
636
+ /* @__PURE__ */ jsx(
637
+ FooterBrand,
638
+ {
639
+ description,
640
+ className: "flex flex-col items-center",
641
+ children: /* @__PURE__ */ jsx(
642
+ LinkComponent,
643
+ {
644
+ href: "/",
645
+ className: "text-white hover:opacity-80 transition-opacity",
646
+ children: logo.src ? /* @__PURE__ */ jsx(
647
+ "img",
648
+ {
649
+ src: logo.src,
650
+ alt: logo.appName,
651
+ className: "h-8 object-contain"
652
+ }
653
+ ) : /* @__PURE__ */ jsx(Logo, { size: "md", showText: true, logoText: logo.appName })
654
+ }
655
+ )
656
+ }
657
+ ),
658
+ version && /* @__PURE__ */ jsx(FooterVersion, { version }),
659
+ /* @__PURE__ */ jsx(
660
+ FooterCopyright,
661
+ {
662
+ year,
663
+ companyName,
664
+ rightsText,
665
+ companyLink
666
+ }
667
+ ),
668
+ statusIndicator && StatusIndicatorComponent && /* @__PURE__ */ jsx(
669
+ StatusIndicatorComponent,
670
+ {
671
+ statusPageUrl: statusIndicator.statusPageUrl,
672
+ apiEndpoint: statusIndicator.apiEndpoint,
673
+ refreshInterval: statusIndicator.refreshInterval || 6e4,
674
+ size: "sm",
675
+ version,
676
+ isNetworkOnline
677
+ }
678
+ )
679
+ ] }),
680
+ socialLinks && /* @__PURE__ */ jsx("div", { className: "flex justify-center mt-4", children: /* @__PURE__ */ jsx(
681
+ FooterSocialLinks,
682
+ {
683
+ twitterUrl: socialLinks.twitterUrl,
684
+ discordUrl: socialLinks.discordUrl,
685
+ linkedinUrl: socialLinks.linkedinUrl,
686
+ githubUrl: socialLinks.githubUrl,
687
+ redditUrl: socialLinks.redditUrl,
688
+ farcasterUrl: socialLinks.farcasterUrl,
689
+ telegramUrl: socialLinks.telegramUrl
690
+ }
691
+ ) })
692
+ ] });
693
+ };
694
+ const layoutVariants = cva("min-h-screen flex flex-col", {
695
+ variants: {
696
+ background: {
697
+ default: "bg-gray-50 dark:bg-gray-900",
698
+ white: "bg-white dark:bg-gray-900",
699
+ gradient: "bg-gradient-to-br from-gray-50 to-blue-50 dark:from-gray-900 dark:to-gray-800"
700
+ }
701
+ },
702
+ defaultVariants: {
703
+ background: "default"
704
+ }
705
+ });
706
+ const maxWidthClasses = {
707
+ sm: "max-w-sm",
708
+ md: "max-w-md",
709
+ lg: "max-w-lg",
710
+ xl: "max-w-xl",
711
+ "2xl": "max-w-2xl",
712
+ "4xl": "max-w-4xl",
713
+ "7xl": "max-w-7xl",
714
+ full: "max-w-full"
715
+ };
716
+ const paddingClasses = {
717
+ none: "",
718
+ sm: "px-4 sm:px-6 py-6",
719
+ md: "px-4 py-8",
720
+ lg: "px-4 py-12"
721
+ };
722
+ const AppPageLayout = ({
723
+ children,
724
+ topBar,
725
+ breadcrumbs,
726
+ footer,
727
+ maxWidth = "7xl",
728
+ contentPadding = "md",
729
+ background = "default",
730
+ layoutMode = "standard",
731
+ className,
732
+ contentClassName,
733
+ mainClassName
734
+ }) => {
735
+ return /* @__PURE__ */ jsx(LayoutProvider, { mode: layoutMode, children: /* @__PURE__ */ jsxs("div", { className: cn(layoutVariants({ background }), className), children: [
736
+ /* @__PURE__ */ jsx("header", { children: topBar }),
737
+ breadcrumbs && breadcrumbs.items && breadcrumbs.items.length > 0 && /* @__PURE__ */ jsx(AppBreadcrumbs, { ...breadcrumbs }),
738
+ /* @__PURE__ */ jsx("main", { className: cn("flex-1 overflow-auto", mainClassName), children: /* @__PURE__ */ jsx(
739
+ "div",
740
+ {
741
+ className: cn(
742
+ "mx-auto",
743
+ maxWidthClasses[maxWidth],
744
+ paddingClasses[contentPadding],
745
+ contentClassName
746
+ ),
747
+ children
748
+ }
749
+ ) }),
750
+ footer && /* @__PURE__ */ jsx("footer", { children: footer })
751
+ ] }) });
752
+ };
753
+ export {
754
+ AppBreadcrumbs,
755
+ AppFooter,
756
+ AppFooterForHomePage,
757
+ AppPageLayout,
758
+ AppTopBar,
759
+ AppTopBarWithFirebaseAuth,
760
+ AppTopBarWithWallet,
761
+ AuthStatus,
762
+ ChainType,
763
+ DEFAULT_LANGUAGES,
764
+ LanguageSelector,
765
+ RTL_LANGUAGES,
766
+ cn,
767
+ isRTL
768
+ };
769
+ //# sourceMappingURL=index.js.map