@shellui/core 0.2.0-beta.0 → 0.2.0-beta.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.
Files changed (60) hide show
  1. package/package.json +2 -2
  2. package/src/app.tsx +1 -1
  3. package/src/components/ContentView.tsx +26 -58
  4. package/src/components/LoadingOverlay.tsx +1 -1
  5. package/src/components/ui/sidebar.tsx +2 -124
  6. package/src/features/layouts/AppLayout.tsx +22 -19
  7. package/src/features/layouts/LayoutFallback.tsx +8 -0
  8. package/src/features/layouts/OverlayShell.tsx +21 -40
  9. package/src/features/layouts/{AppBarLayout.tsx → appbar/AppBarLayout.tsx} +72 -78
  10. package/src/features/layouts/{FullscreenLayout.tsx → fullscreen/FullscreenLayout.tsx} +5 -11
  11. package/src/features/layouts/sidebar/BottomNavItem.tsx +88 -0
  12. package/src/features/layouts/sidebar/MobileBottomNav.tsx +168 -0
  13. package/src/features/layouts/sidebar/NavigationContent.tsx +159 -0
  14. package/src/features/layouts/sidebar/SidebarIcons.tsx +93 -0
  15. package/src/features/layouts/sidebar/SidebarInner.tsx +48 -0
  16. package/src/features/layouts/sidebar/SidebarLayout.tsx +86 -0
  17. package/src/features/layouts/sidebar/sidebarUtils.ts +23 -0
  18. package/src/features/layouts/sidebar/types.ts +8 -0
  19. package/src/features/layouts/utils.ts +1 -1
  20. package/src/features/layouts/{WindowsLayout.tsx → windows/WindowsLayout.tsx} +199 -204
  21. package/src/features/settings/SettingsView.tsx +177 -180
  22. package/src/{components → routes/components}/HomeView.tsx +1 -1
  23. package/src/{components → routes/components}/IndexRoute.tsx +4 -4
  24. package/src/routes/components/NavigationItemRoute.tsx +19 -0
  25. package/src/{components → routes/components}/NotFoundView.tsx +3 -3
  26. package/src/{components → routes/components}/RouteErrorBoundary.tsx +1 -1
  27. package/src/routes/components/RouteFallback.tsx +8 -0
  28. package/src/routes/hooks/useNavigationItems.ts +84 -0
  29. package/src/{router → routes}/routes.tsx +10 -18
  30. package/src/components/ViewRoute.tsx +0 -74
  31. package/src/dist/CookiePreferencesView.52b5aec8.js +0 -1182
  32. package/src/dist/CookiePreferencesView.52b5aec8.js.map +0 -1
  33. package/src/dist/DefaultLayout.045a82ff.js +0 -1964
  34. package/src/dist/DefaultLayout.045a82ff.js.map +0 -1
  35. package/src/dist/DefaultLayout.4454f259.js +0 -4414
  36. package/src/dist/DefaultLayout.4454f259.js.map +0 -1
  37. package/src/dist/FullscreenLayout.555c4987.js +0 -1054
  38. package/src/dist/FullscreenLayout.555c4987.js.map +0 -1
  39. package/src/dist/HomeView.ddfa7b68.js +0 -771
  40. package/src/dist/HomeView.ddfa7b68.js.map +0 -1
  41. package/src/dist/NotFoundView.c75be4f1.js +0 -811
  42. package/src/dist/NotFoundView.c75be4f1.js.map +0 -1
  43. package/src/dist/SettingsView.052b03a6.js +0 -4965
  44. package/src/dist/SettingsView.052b03a6.js.map +0 -1
  45. package/src/dist/ViewRoute.e6e3b142.js +0 -1042
  46. package/src/dist/ViewRoute.e6e3b142.js.map +0 -1
  47. package/src/dist/WindowsLayout.08724167.js +0 -1762
  48. package/src/dist/WindowsLayout.08724167.js.map +0 -1
  49. package/src/dist/esm.f0d741e6.js +0 -29520
  50. package/src/dist/esm.f0d741e6.js.map +0 -1
  51. package/src/dist/favicon.4367ac1e.svg +0 -14
  52. package/src/dist/index.parcel.36d65383.js +0 -54089
  53. package/src/dist/index.parcel.36d65383.js.map +0 -1
  54. package/src/dist/index.parcel.ca6d8a47.css +0 -3493
  55. package/src/dist/index.parcel.ca6d8a47.css.map +0 -1
  56. package/src/dist/index.parcel.html +0 -88
  57. package/src/features/layouts/DefaultLayout.tsx +0 -670
  58. package/src/features/layouts/LayoutProviders.tsx +0 -20
  59. /package/src/{constants.ts → constants/loading.ts} +0 -0
  60. /package/src/{router → routes}/router.tsx +0 -0
@@ -0,0 +1,93 @@
1
+ import { cn } from '../../../lib/utils';
2
+
3
+ /** Inline SVG: external-link icon. Bundled so consumers don't need to serve static SVGs. */
4
+ export function ExternalLinkIcon({ className }: { className?: string }) {
5
+ return (
6
+ <svg
7
+ xmlns="http://www.w3.org/2000/svg"
8
+ width="24"
9
+ height="24"
10
+ viewBox="0 0 24 24"
11
+ fill="none"
12
+ stroke="currentColor"
13
+ strokeWidth="2"
14
+ strokeLinecap="round"
15
+ strokeLinejoin="round"
16
+ className={cn('shrink-0', className)}
17
+ aria-hidden
18
+ >
19
+ <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
20
+ <polyline points="15 3 21 3 21 9" />
21
+ <line
22
+ x1="10"
23
+ y1="14"
24
+ x2="21"
25
+ y2="3"
26
+ />
27
+ </svg>
28
+ );
29
+ }
30
+
31
+ /** Caret up: expand (show second line). */
32
+ export function CaretUpIcon({ className }: { className?: string }) {
33
+ return (
34
+ <svg
35
+ xmlns="http://www.w3.org/2000/svg"
36
+ width="24"
37
+ height="24"
38
+ viewBox="0 0 24 24"
39
+ fill="none"
40
+ stroke="currentColor"
41
+ strokeWidth="2"
42
+ strokeLinecap="round"
43
+ strokeLinejoin="round"
44
+ className={cn('shrink-0', className)}
45
+ aria-hidden
46
+ >
47
+ <path d="m18 15-6-6-6 6" />
48
+ </svg>
49
+ );
50
+ }
51
+
52
+ /** Caret down: collapse (hide second line). */
53
+ export function CaretDownIcon({ className }: { className?: string }) {
54
+ return (
55
+ <svg
56
+ xmlns="http://www.w3.org/2000/svg"
57
+ width="24"
58
+ height="24"
59
+ viewBox="0 0 24 24"
60
+ fill="none"
61
+ stroke="currentColor"
62
+ strokeWidth="2"
63
+ strokeLinecap="round"
64
+ strokeLinejoin="round"
65
+ className={cn('shrink-0', className)}
66
+ aria-hidden
67
+ >
68
+ <path d="m6 9 6 6 6-6" />
69
+ </svg>
70
+ );
71
+ }
72
+
73
+ /** Home icon for mobile bottom bar (same as sidebar logo action). */
74
+ export function HomeIcon({ className }: { className?: string }) {
75
+ return (
76
+ <svg
77
+ xmlns="http://www.w3.org/2000/svg"
78
+ width="24"
79
+ height="24"
80
+ viewBox="0 0 24 24"
81
+ fill="none"
82
+ stroke="currentColor"
83
+ strokeWidth="2"
84
+ strokeLinecap="round"
85
+ strokeLinejoin="round"
86
+ className={cn('shrink-0', className)}
87
+ aria-hidden
88
+ >
89
+ <path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
90
+ <polyline points="9 22 9 12 15 12 15 22" />
91
+ </svg>
92
+ );
93
+ }
@@ -0,0 +1,48 @@
1
+ import { Link } from 'react-router';
2
+ import type { NavigationItem, NavigationGroup } from '../../config/types';
3
+ import { SidebarHeader, SidebarContent, SidebarFooter } from '../../../components/ui/sidebar';
4
+ import { NavigationContent } from './NavigationContent';
5
+
6
+ /** Reusable sidebar inner: header, main nav, footer. Used in desktop Sidebar and mobile Drawer. */
7
+ export function SidebarInner({
8
+ title,
9
+ logo,
10
+ startNav,
11
+ endItems,
12
+ }: {
13
+ title?: string;
14
+ logo?: string;
15
+ startNav: (NavigationItem | NavigationGroup)[];
16
+ endItems: (NavigationItem | NavigationGroup)[];
17
+ }) {
18
+ return (
19
+ <>
20
+ <SidebarHeader className="border-b border-sidebar-border pb-4">
21
+ {(title || logo) && (
22
+ <Link
23
+ to="/"
24
+ className="flex items-center pl-1 pr-3 py-2 text-lg font-semibold text-sidebar-foreground hover:text-sidebar-foreground/80 transition-colors"
25
+ >
26
+ {logo && logo.trim() ? (
27
+ <img
28
+ src={logo}
29
+ alt={title || 'Logo'}
30
+ className="h-5 w-auto shrink-0 object-contain sidebar-logo"
31
+ />
32
+ ) : title ? (
33
+ <span className="leading-none">{title}</span>
34
+ ) : null}
35
+ </Link>
36
+ )}
37
+ </SidebarHeader>
38
+ <SidebarContent className="gap-1">
39
+ <NavigationContent navigation={startNav} />
40
+ </SidebarContent>
41
+ {endItems.length > 0 && (
42
+ <SidebarFooter>
43
+ <NavigationContent navigation={endItems} />
44
+ </SidebarFooter>
45
+ )}
46
+ </>
47
+ );
48
+ }
@@ -0,0 +1,86 @@
1
+ import { Outlet } from 'react-router';
2
+ import { useMemo, useEffect } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { Sidebar } from '../../../components/ui/sidebar';
5
+ import { cn } from '../../../lib/utils';
6
+ import {
7
+ filterNavigationByViewport,
8
+ filterNavigationForSidebar,
9
+ flattenNavigationItems,
10
+ resolveLocalizedString as resolveLocalizedLabel,
11
+ splitNavigationByPosition,
12
+ } from '../utils';
13
+ import { SidebarInner } from './SidebarInner';
14
+ import { MobileBottomNav } from './MobileBottomNav';
15
+ import type { SidebarLayoutProps } from './types';
16
+ import { useNavigationItems } from '../../../routes/hooks/useNavigationItems';
17
+
18
+ const SidebarLayoutContent = ({ title, logo, navigation }: SidebarLayoutProps) => {
19
+ const { i18n } = useTranslation();
20
+ const { navigationItem, rootItem } = useNavigationItems();
21
+
22
+ const currentLanguage = useMemo(() => {
23
+ return i18n.language || 'en';
24
+ }, [i18n]);
25
+
26
+ const { startNav, endItems, mobileNavItems } = useMemo(() => {
27
+ const desktopNav = filterNavigationByViewport(navigation, 'desktop');
28
+ const mobileNav = filterNavigationByViewport(navigation, 'mobile');
29
+ const { start, end } = splitNavigationByPosition(desktopNav);
30
+ const mobileFlat = flattenNavigationItems(mobileNav);
31
+
32
+ return {
33
+ startNav: filterNavigationForSidebar(start),
34
+ endItems: end,
35
+ mobileNavItems: mobileFlat,
36
+ };
37
+ }, [navigation]);
38
+
39
+ useEffect(() => {
40
+ if (!title) return;
41
+ if (navigationItem) {
42
+ const label = resolveLocalizedLabel(navigationItem.label, currentLanguage);
43
+ document.title = `${label} | ${title}`;
44
+ } else {
45
+ document.title = title;
46
+ }
47
+ }, [navigationItem, title, currentLanguage]);
48
+
49
+ return (
50
+ <div>
51
+ <div className="flex h-screen overflow-hidden">
52
+ <Sidebar className={cn('hidden md:flex shrink-0')}>
53
+ <SidebarInner
54
+ title={title}
55
+ logo={logo}
56
+ startNav={startNav}
57
+ endItems={endItems}
58
+ />
59
+ </Sidebar>
60
+
61
+ <main className="flex-1 flex flex-col overflow-hidden bg-background relative min-w-0">
62
+ <div className="flex-1 flex flex-col overflow-auto pb-16 md:pb-0">
63
+ <Outlet />
64
+ </div>
65
+ </main>
66
+ </div>
67
+
68
+ <MobileBottomNav
69
+ items={mobileNavItems}
70
+ currentLanguage={currentLanguage}
71
+ showHomeButton={!rootItem}
72
+ />
73
+ </div>
74
+ );
75
+ };
76
+
77
+ export function SidebarLayout({ title, appIcon, logo, navigation }: SidebarLayoutProps) {
78
+ return (
79
+ <SidebarLayoutContent
80
+ title={title}
81
+ appIcon={appIcon}
82
+ logo={logo}
83
+ navigation={navigation}
84
+ />
85
+ );
86
+ }
@@ -0,0 +1,23 @@
1
+ /** DuckDuckGo favicon URL for a given page URL (used when openIn === 'external' and no icon is set). */
2
+ export function getExternalFaviconUrl(url: string): string | null {
3
+ try {
4
+ const parsed = new URL(url);
5
+ const hostname = parsed.hostname;
6
+ if (!hostname) return null;
7
+ return `https://icons.duckduckgo.com/ip3/${hostname}.ico`;
8
+ } catch {
9
+ return null;
10
+ }
11
+ }
12
+
13
+ /** Approximate width per slot (icon + label + padding) and gap for dynamic slot count. */
14
+ export const BOTTOM_NAV_SLOT_WIDTH = 64;
15
+ export const BOTTOM_NAV_GAP = 4;
16
+ export const BOTTOM_NAV_PX = 12;
17
+ /** Max slots in the row (Home + nav + optional More) to avoid overflow/duplicated wrap. */
18
+ export const BOTTOM_NAV_MAX_SLOTS = 6;
19
+
20
+ /** True when the icon is a local app icon (/icons/); external images (avatars, favicons) are shown as-is. */
21
+ export function isAppIcon(src: string): boolean {
22
+ return src.startsWith('/icons/');
23
+ }
@@ -0,0 +1,8 @@
1
+ import type { NavigationItem, NavigationGroup } from '../../config/types';
2
+
3
+ export interface SidebarLayoutProps {
4
+ title?: string;
5
+ appIcon?: string;
6
+ logo?: string;
7
+ navigation: (NavigationItem | NavigationGroup)[];
8
+ }
@@ -2,7 +2,7 @@ import type { NavigationItem, NavigationGroup, LocalizedString } from '../config
2
2
 
3
3
  /** Path prefix for a nav item: "/" for root (path '' or '/'), otherwise "/{path}". */
4
4
  export function getNavPathPrefix(item: NavigationItem): string {
5
- return item.path === '/' || item.path === '' ? '/' : `/${item.path}`;
5
+ return item.path === '' ? '/' : `/${item.path}`;
6
6
  }
7
7
 
8
8
  /** Whether a URL string uses hash-based routing (e.g. contains /#/). */