@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.
- package/package.json +2 -2
- package/src/app.tsx +1 -1
- package/src/components/ContentView.tsx +26 -58
- package/src/components/LoadingOverlay.tsx +1 -1
- package/src/components/ui/sidebar.tsx +2 -124
- package/src/features/layouts/AppLayout.tsx +22 -19
- package/src/features/layouts/LayoutFallback.tsx +8 -0
- package/src/features/layouts/OverlayShell.tsx +21 -40
- package/src/features/layouts/{AppBarLayout.tsx → appbar/AppBarLayout.tsx} +72 -78
- package/src/features/layouts/{FullscreenLayout.tsx → fullscreen/FullscreenLayout.tsx} +5 -11
- package/src/features/layouts/sidebar/BottomNavItem.tsx +88 -0
- package/src/features/layouts/sidebar/MobileBottomNav.tsx +168 -0
- package/src/features/layouts/sidebar/NavigationContent.tsx +159 -0
- package/src/features/layouts/sidebar/SidebarIcons.tsx +93 -0
- package/src/features/layouts/sidebar/SidebarInner.tsx +48 -0
- package/src/features/layouts/sidebar/SidebarLayout.tsx +86 -0
- package/src/features/layouts/sidebar/sidebarUtils.ts +23 -0
- package/src/features/layouts/sidebar/types.ts +8 -0
- package/src/features/layouts/utils.ts +1 -1
- package/src/features/layouts/{WindowsLayout.tsx → windows/WindowsLayout.tsx} +199 -204
- package/src/features/settings/SettingsView.tsx +177 -180
- package/src/{components → routes/components}/HomeView.tsx +1 -1
- package/src/{components → routes/components}/IndexRoute.tsx +4 -4
- package/src/routes/components/NavigationItemRoute.tsx +19 -0
- package/src/{components → routes/components}/NotFoundView.tsx +3 -3
- package/src/{components → routes/components}/RouteErrorBoundary.tsx +1 -1
- package/src/routes/components/RouteFallback.tsx +8 -0
- package/src/routes/hooks/useNavigationItems.ts +84 -0
- package/src/{router → routes}/routes.tsx +10 -18
- package/src/components/ViewRoute.tsx +0 -74
- package/src/dist/CookiePreferencesView.52b5aec8.js +0 -1182
- package/src/dist/CookiePreferencesView.52b5aec8.js.map +0 -1
- package/src/dist/DefaultLayout.045a82ff.js +0 -1964
- package/src/dist/DefaultLayout.045a82ff.js.map +0 -1
- package/src/dist/DefaultLayout.4454f259.js +0 -4414
- package/src/dist/DefaultLayout.4454f259.js.map +0 -1
- package/src/dist/FullscreenLayout.555c4987.js +0 -1054
- package/src/dist/FullscreenLayout.555c4987.js.map +0 -1
- package/src/dist/HomeView.ddfa7b68.js +0 -771
- package/src/dist/HomeView.ddfa7b68.js.map +0 -1
- package/src/dist/NotFoundView.c75be4f1.js +0 -811
- package/src/dist/NotFoundView.c75be4f1.js.map +0 -1
- package/src/dist/SettingsView.052b03a6.js +0 -4965
- package/src/dist/SettingsView.052b03a6.js.map +0 -1
- package/src/dist/ViewRoute.e6e3b142.js +0 -1042
- package/src/dist/ViewRoute.e6e3b142.js.map +0 -1
- package/src/dist/WindowsLayout.08724167.js +0 -1762
- package/src/dist/WindowsLayout.08724167.js.map +0 -1
- package/src/dist/esm.f0d741e6.js +0 -29520
- package/src/dist/esm.f0d741e6.js.map +0 -1
- package/src/dist/favicon.4367ac1e.svg +0 -14
- package/src/dist/index.parcel.36d65383.js +0 -54089
- package/src/dist/index.parcel.36d65383.js.map +0 -1
- package/src/dist/index.parcel.ca6d8a47.css +0 -3493
- package/src/dist/index.parcel.ca6d8a47.css.map +0 -1
- package/src/dist/index.parcel.html +0 -88
- package/src/features/layouts/DefaultLayout.tsx +0 -670
- package/src/features/layouts/LayoutProviders.tsx +0 -20
- /package/src/{constants.ts → constants/loading.ts} +0 -0
- /package/src/{router → routes}/router.tsx +0 -0
|
@@ -9,20 +9,18 @@ import {
|
|
|
9
9
|
import { useLocation } from 'react-router';
|
|
10
10
|
import { useTranslation } from 'react-i18next';
|
|
11
11
|
import { shellui } from '@shellui/sdk';
|
|
12
|
-
import type { NavigationItem, NavigationGroup } from '
|
|
12
|
+
import type { NavigationItem, NavigationGroup } from '../../config/types';
|
|
13
13
|
import {
|
|
14
14
|
flattenNavigationItems,
|
|
15
15
|
getActivePathPrefix,
|
|
16
16
|
getNavPathPrefix,
|
|
17
17
|
resolveLocalizedString as resolveNavLabel,
|
|
18
18
|
splitNavigationByPosition,
|
|
19
|
-
} from '
|
|
20
|
-
import { useSettings } from '
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import { cn } from '../../lib/utils';
|
|
25
|
-
import { Z_INDEX } from '../../lib/z-index';
|
|
19
|
+
} from '../utils';
|
|
20
|
+
import { useSettings } from '../../settings/SettingsContext';
|
|
21
|
+
import { ContentView } from '../../../components/ContentView';
|
|
22
|
+
import { cn } from '../../../lib/utils';
|
|
23
|
+
import { Z_INDEX } from '../../../lib/z-index';
|
|
26
24
|
|
|
27
25
|
interface WindowsLayoutProps {
|
|
28
26
|
title?: string;
|
|
@@ -650,233 +648,230 @@ export function WindowsLayout({
|
|
|
650
648
|
);
|
|
651
649
|
|
|
652
650
|
return (
|
|
653
|
-
|
|
654
|
-
<
|
|
651
|
+
<>
|
|
652
|
+
<div
|
|
653
|
+
className="fixed inset-0 bg-muted/30"
|
|
654
|
+
style={{ paddingBottom: TASKBAR_HEIGHT }}
|
|
655
|
+
>
|
|
656
|
+
{/* Desktop area: windows */}
|
|
657
|
+
{windows.map((win, index) => {
|
|
658
|
+
const navItem = navigationItems.find((n) => n.path === win.path);
|
|
659
|
+
if (!navItem) return null;
|
|
660
|
+
const isFocused = win.id === frontWindowId;
|
|
661
|
+
const zIndex = Z_INDEX.WINDOWS_WINDOW_BASE + index;
|
|
662
|
+
return (
|
|
663
|
+
<AppWindow
|
|
664
|
+
key={win.id}
|
|
665
|
+
win={win}
|
|
666
|
+
navItem={navItem}
|
|
667
|
+
currentLanguage={currentLanguage}
|
|
668
|
+
isFocused={isFocused}
|
|
669
|
+
onFocus={() => focusWindow(win.id)}
|
|
670
|
+
onClose={() => closeWindow(win.id)}
|
|
671
|
+
onBoundsChange={(bounds) => updateWindowBounds(win.id, bounds)}
|
|
672
|
+
maxZIndex={maxZIndex}
|
|
673
|
+
zIndex={zIndex}
|
|
674
|
+
/>
|
|
675
|
+
);
|
|
676
|
+
})}
|
|
677
|
+
</div>
|
|
678
|
+
|
|
679
|
+
{/* Taskbar */}
|
|
680
|
+
<div
|
|
681
|
+
className="fixed left-0 right-0 bottom-0 flex items-center gap-1 px-2 border-t border-border bg-sidebar-background"
|
|
682
|
+
style={{
|
|
683
|
+
height: TASKBAR_HEIGHT,
|
|
684
|
+
zIndex: Z_INDEX.WINDOWS_TASKBAR,
|
|
685
|
+
paddingBottom: 'env(safe-area-inset-bottom, 0px)',
|
|
686
|
+
}}
|
|
687
|
+
>
|
|
688
|
+
{/* Start button */}
|
|
655
689
|
<div
|
|
656
|
-
className="
|
|
657
|
-
|
|
690
|
+
className="relative shrink-0"
|
|
691
|
+
ref={startPanelRef}
|
|
658
692
|
>
|
|
659
|
-
|
|
660
|
-
|
|
693
|
+
<button
|
|
694
|
+
type="button"
|
|
695
|
+
onClick={() => setStartMenuOpen((o) => !o)}
|
|
696
|
+
className={cn(
|
|
697
|
+
'flex items-center gap-2 h-9 px-3 rounded cursor-pointer transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
|
698
|
+
startMenuOpen
|
|
699
|
+
? 'bg-sidebar-accent text-sidebar-accent-foreground'
|
|
700
|
+
: 'text-sidebar-foreground hover:bg-sidebar-accent/50 hover:text-sidebar-foreground',
|
|
701
|
+
)}
|
|
702
|
+
aria-expanded={startMenuOpen}
|
|
703
|
+
aria-haspopup="true"
|
|
704
|
+
aria-label="Start"
|
|
705
|
+
>
|
|
706
|
+
<StartIcon className="h-5 w-5" />
|
|
707
|
+
<span className="font-semibold text-sm hidden sm:inline">{title || 'Start'}</span>
|
|
708
|
+
</button>
|
|
709
|
+
{/* Start menu panel */}
|
|
710
|
+
{startMenuOpen && (
|
|
711
|
+
<div
|
|
712
|
+
className="absolute bottom-full left-0 mb-1 w-64 max-h-[70vh] overflow-y-auto rounded-lg border border-border bg-popover shadow-lg py-2 z-[10001]"
|
|
713
|
+
style={{ zIndex: Z_INDEX.MODAL_CONTENT }}
|
|
714
|
+
>
|
|
715
|
+
<div className="px-2 pb-2 border-b border-border mb-2">
|
|
716
|
+
<span className="text-sm font-semibold text-popover-foreground">
|
|
717
|
+
{title || 'Applications'}
|
|
718
|
+
</span>
|
|
719
|
+
</div>
|
|
720
|
+
<div className="grid gap-0.5">
|
|
721
|
+
{startNavItems
|
|
722
|
+
.filter((item) => !item.hidden)
|
|
723
|
+
.map((item) => {
|
|
724
|
+
const label =
|
|
725
|
+
typeof item.label === 'string'
|
|
726
|
+
? item.label
|
|
727
|
+
: resolveNavLabel(item.label, currentLanguage);
|
|
728
|
+
const icon =
|
|
729
|
+
item.icon ??
|
|
730
|
+
(item.openIn === 'external' ? getExternalFaviconUrl(item.url) : null);
|
|
731
|
+
return (
|
|
732
|
+
<button
|
|
733
|
+
key={item.path}
|
|
734
|
+
type="button"
|
|
735
|
+
onClick={() => handleNavClick(item)}
|
|
736
|
+
className="flex items-center gap-3 w-full px-3 py-2 text-left text-sm cursor-pointer text-popover-foreground hover:bg-accent hover:text-accent-foreground rounded-none transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
|
737
|
+
>
|
|
738
|
+
{icon ? (
|
|
739
|
+
<img
|
|
740
|
+
src={icon}
|
|
741
|
+
alt=""
|
|
742
|
+
className={cn(
|
|
743
|
+
'h-5 w-5 shrink-0 rounded-sm object-cover',
|
|
744
|
+
isAppIcon(icon) && 'opacity-90 dark:opacity-100 dark:invert',
|
|
745
|
+
)}
|
|
746
|
+
/>
|
|
747
|
+
) : (
|
|
748
|
+
<span className="h-5 w-5 shrink-0 rounded-sm bg-muted" />
|
|
749
|
+
)}
|
|
750
|
+
<span className="truncate">{label}</span>
|
|
751
|
+
</button>
|
|
752
|
+
);
|
|
753
|
+
})}
|
|
754
|
+
</div>
|
|
755
|
+
</div>
|
|
756
|
+
)}
|
|
757
|
+
</div>
|
|
758
|
+
|
|
759
|
+
{/* Window list */}
|
|
760
|
+
<div className="flex-1 flex items-center gap-1 min-w-0 overflow-x-auto">
|
|
761
|
+
{windows.map((win) => {
|
|
661
762
|
const navItem = navigationItems.find((n) => n.path === win.path);
|
|
662
|
-
|
|
763
|
+
const windowLabel = navItem
|
|
764
|
+
? resolveNavLabel(navItem.label, currentLanguage)
|
|
765
|
+
: win.label;
|
|
663
766
|
const isFocused = win.id === frontWindowId;
|
|
664
|
-
const zIndex = Z_INDEX.WINDOWS_WINDOW_BASE + index;
|
|
665
767
|
return (
|
|
666
|
-
<
|
|
768
|
+
<button
|
|
667
769
|
key={win.id}
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
770
|
+
type="button"
|
|
771
|
+
onClick={() => focusWindow(win.id)}
|
|
772
|
+
onContextMenu={(e) => {
|
|
773
|
+
e.preventDefault();
|
|
774
|
+
closeWindow(win.id);
|
|
775
|
+
}}
|
|
776
|
+
className={cn(
|
|
777
|
+
'flex items-center gap-2 h-8 px-2 rounded min-w-0 max-w-[140px] shrink-0 cursor-pointer transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
|
778
|
+
isFocused
|
|
779
|
+
? 'bg-sidebar-accent text-sidebar-accent-foreground'
|
|
780
|
+
: 'text-sidebar-foreground hover:bg-sidebar-accent/50 hover:text-sidebar-foreground',
|
|
781
|
+
)}
|
|
782
|
+
title={windowLabel}
|
|
783
|
+
>
|
|
784
|
+
{win.icon ? (
|
|
785
|
+
<img
|
|
786
|
+
src={win.icon}
|
|
787
|
+
alt=""
|
|
788
|
+
className={cn(
|
|
789
|
+
'h-4 w-4 shrink-0 rounded-sm object-cover',
|
|
790
|
+
isAppIcon(win.icon) && 'opacity-90 dark:opacity-100 dark:invert',
|
|
791
|
+
)}
|
|
792
|
+
/>
|
|
793
|
+
) : (
|
|
794
|
+
<span className="h-4 w-4 shrink-0 rounded-sm bg-muted" />
|
|
795
|
+
)}
|
|
796
|
+
<span className="text-xs truncate">{windowLabel}</span>
|
|
797
|
+
</button>
|
|
678
798
|
);
|
|
679
799
|
})}
|
|
680
800
|
</div>
|
|
681
801
|
|
|
682
|
-
{/*
|
|
683
|
-
|
|
684
|
-
className="
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
<div
|
|
693
|
-
className="relative shrink-0"
|
|
694
|
-
ref={startPanelRef}
|
|
695
|
-
>
|
|
696
|
-
<button
|
|
697
|
-
type="button"
|
|
698
|
-
onClick={() => setStartMenuOpen((o) => !o)}
|
|
699
|
-
className={cn(
|
|
700
|
-
'flex items-center gap-2 h-9 px-3 rounded cursor-pointer transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
|
701
|
-
startMenuOpen
|
|
702
|
-
? 'bg-sidebar-accent text-sidebar-accent-foreground'
|
|
703
|
-
: 'text-sidebar-foreground hover:bg-sidebar-accent/50 hover:text-sidebar-foreground',
|
|
704
|
-
)}
|
|
705
|
-
aria-expanded={startMenuOpen}
|
|
706
|
-
aria-haspopup="true"
|
|
707
|
-
aria-label="Start"
|
|
708
|
-
>
|
|
709
|
-
<StartIcon className="h-5 w-5" />
|
|
710
|
-
<span className="font-semibold text-sm hidden sm:inline">{title || 'Start'}</span>
|
|
711
|
-
</button>
|
|
712
|
-
{/* Start menu panel */}
|
|
713
|
-
{startMenuOpen && (
|
|
714
|
-
<div
|
|
715
|
-
className="absolute bottom-full left-0 mb-1 w-64 max-h-[70vh] overflow-y-auto rounded-lg border border-border bg-popover shadow-lg py-2 z-[10001]"
|
|
716
|
-
style={{ zIndex: Z_INDEX.MODAL_CONTENT }}
|
|
717
|
-
>
|
|
718
|
-
<div className="px-2 pb-2 border-b border-border mb-2">
|
|
719
|
-
<span className="text-sm font-semibold text-popover-foreground">
|
|
720
|
-
{title || 'Applications'}
|
|
721
|
-
</span>
|
|
722
|
-
</div>
|
|
723
|
-
<div className="grid gap-0.5">
|
|
724
|
-
{startNavItems
|
|
725
|
-
.filter((item) => !item.hidden)
|
|
726
|
-
.map((item) => {
|
|
727
|
-
const label =
|
|
728
|
-
typeof item.label === 'string'
|
|
729
|
-
? item.label
|
|
730
|
-
: resolveNavLabel(item.label, currentLanguage);
|
|
731
|
-
const icon =
|
|
732
|
-
item.icon ??
|
|
733
|
-
(item.openIn === 'external' ? getExternalFaviconUrl(item.url) : null);
|
|
734
|
-
return (
|
|
735
|
-
<button
|
|
736
|
-
key={item.path}
|
|
737
|
-
type="button"
|
|
738
|
-
onClick={() => handleNavClick(item)}
|
|
739
|
-
className="flex items-center gap-3 w-full px-3 py-2 text-left text-sm cursor-pointer text-popover-foreground hover:bg-accent hover:text-accent-foreground rounded-none transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
|
740
|
-
>
|
|
741
|
-
{icon ? (
|
|
742
|
-
<img
|
|
743
|
-
src={icon}
|
|
744
|
-
alt=""
|
|
745
|
-
className={cn(
|
|
746
|
-
'h-5 w-5 shrink-0 rounded-sm object-cover',
|
|
747
|
-
isAppIcon(icon) && 'opacity-90 dark:opacity-100 dark:invert',
|
|
748
|
-
)}
|
|
749
|
-
/>
|
|
750
|
-
) : (
|
|
751
|
-
<span className="h-5 w-5 shrink-0 rounded-sm bg-muted" />
|
|
752
|
-
)}
|
|
753
|
-
<span className="truncate">{label}</span>
|
|
754
|
-
</button>
|
|
755
|
-
);
|
|
756
|
-
})}
|
|
757
|
-
</div>
|
|
758
|
-
</div>
|
|
759
|
-
)}
|
|
760
|
-
</div>
|
|
761
|
-
|
|
762
|
-
{/* Window list */}
|
|
763
|
-
<div className="flex-1 flex items-center gap-1 min-w-0 overflow-x-auto">
|
|
764
|
-
{windows.map((win) => {
|
|
765
|
-
const navItem = navigationItems.find((n) => n.path === win.path);
|
|
766
|
-
const windowLabel = navItem
|
|
767
|
-
? resolveNavLabel(navItem.label, currentLanguage)
|
|
768
|
-
: win.label;
|
|
769
|
-
const isFocused = win.id === frontWindowId;
|
|
802
|
+
{/* End navigation items (right side of taskbar) */}
|
|
803
|
+
{endNavItems.length > 0 && (
|
|
804
|
+
<div className="flex items-center gap-0.5 shrink-0 border-l border-sidebar-border pl-2 ml-1">
|
|
805
|
+
{endNavItems.map((item) => {
|
|
806
|
+
const label =
|
|
807
|
+
typeof item.label === 'string'
|
|
808
|
+
? item.label
|
|
809
|
+
: resolveNavLabel(item.label, currentLanguage);
|
|
810
|
+
const icon =
|
|
811
|
+
item.icon ?? (item.openIn === 'external' ? getExternalFaviconUrl(item.url) : null);
|
|
770
812
|
return (
|
|
771
813
|
<button
|
|
772
|
-
key={
|
|
814
|
+
key={item.path}
|
|
773
815
|
type="button"
|
|
774
|
-
onClick={() =>
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
closeWindow(win.id);
|
|
778
|
-
}}
|
|
779
|
-
className={cn(
|
|
780
|
-
'flex items-center gap-2 h-8 px-2 rounded min-w-0 max-w-[140px] shrink-0 cursor-pointer transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
|
781
|
-
isFocused
|
|
782
|
-
? 'bg-sidebar-accent text-sidebar-accent-foreground'
|
|
783
|
-
: 'text-sidebar-foreground hover:bg-sidebar-accent/50 hover:text-sidebar-foreground',
|
|
784
|
-
)}
|
|
785
|
-
title={windowLabel}
|
|
816
|
+
onClick={() => handleNavClick(item)}
|
|
817
|
+
className="flex items-center gap-2 h-8 px-2 rounded cursor-pointer text-sidebar-foreground hover:bg-sidebar-accent/50 hover:text-sidebar-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
|
818
|
+
title={label}
|
|
786
819
|
>
|
|
787
|
-
{
|
|
820
|
+
{icon ? (
|
|
788
821
|
<img
|
|
789
|
-
src={
|
|
822
|
+
src={icon}
|
|
790
823
|
alt=""
|
|
791
824
|
className={cn(
|
|
792
825
|
'h-4 w-4 shrink-0 rounded-sm object-cover',
|
|
793
|
-
isAppIcon(
|
|
826
|
+
isAppIcon(icon) && 'opacity-90 dark:opacity-100 dark:invert',
|
|
794
827
|
)}
|
|
795
828
|
/>
|
|
796
829
|
) : (
|
|
797
830
|
<span className="h-4 w-4 shrink-0 rounded-sm bg-muted" />
|
|
798
831
|
)}
|
|
799
|
-
<span className="text-xs truncate">{
|
|
832
|
+
<span className="text-xs truncate max-w-[100px]">{label}</span>
|
|
800
833
|
</button>
|
|
801
834
|
);
|
|
802
835
|
})}
|
|
803
836
|
</div>
|
|
837
|
+
)}
|
|
804
838
|
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
title={label}
|
|
823
|
-
>
|
|
824
|
-
{icon ? (
|
|
825
|
-
<img
|
|
826
|
-
src={icon}
|
|
827
|
-
alt=""
|
|
828
|
-
className={cn(
|
|
829
|
-
'h-4 w-4 shrink-0 rounded-sm object-cover',
|
|
830
|
-
isAppIcon(icon) && 'opacity-90 dark:opacity-100 dark:invert',
|
|
831
|
-
)}
|
|
832
|
-
/>
|
|
833
|
-
) : (
|
|
834
|
-
<span className="h-4 w-4 shrink-0 rounded-sm bg-muted" />
|
|
835
|
-
)}
|
|
836
|
-
<span className="text-xs truncate max-w-[100px]">{label}</span>
|
|
837
|
-
</button>
|
|
838
|
-
);
|
|
839
|
-
})}
|
|
840
|
-
</div>
|
|
841
|
-
)}
|
|
842
|
-
|
|
843
|
-
{/* Date and time (extreme bottom right, OS-style); uses region timezone from settings */}
|
|
844
|
-
<div
|
|
845
|
-
className="flex flex-col items-end justify-center shrink-0 px-3 py-1 text-sidebar-foreground border-l border-sidebar-border ml-1 min-w-0"
|
|
846
|
-
style={{ paddingRight: 'max(0.75rem, env(safe-area-inset-right))' }}
|
|
847
|
-
role="timer"
|
|
848
|
-
aria-live="off"
|
|
849
|
-
aria-label={new Intl.DateTimeFormat(currentLanguage, {
|
|
839
|
+
{/* Date and time (extreme bottom right, OS-style); uses region timezone from settings */}
|
|
840
|
+
<div
|
|
841
|
+
className="flex flex-col items-end justify-center shrink-0 px-3 py-1 text-sidebar-foreground border-l border-sidebar-border ml-1 min-w-0"
|
|
842
|
+
style={{ paddingRight: 'max(0.75rem, env(safe-area-inset-right))' }}
|
|
843
|
+
role="timer"
|
|
844
|
+
aria-live="off"
|
|
845
|
+
aria-label={new Intl.DateTimeFormat(currentLanguage, {
|
|
846
|
+
timeZone,
|
|
847
|
+
dateStyle: 'full',
|
|
848
|
+
timeStyle: 'medium',
|
|
849
|
+
}).format(now)}
|
|
850
|
+
>
|
|
851
|
+
<time
|
|
852
|
+
dateTime={now.toISOString()}
|
|
853
|
+
className="text-xs leading-tight tabular-nums whitespace-nowrap"
|
|
854
|
+
>
|
|
855
|
+
{new Intl.DateTimeFormat(currentLanguage, {
|
|
850
856
|
timeZone,
|
|
851
|
-
|
|
852
|
-
|
|
857
|
+
hour: '2-digit',
|
|
858
|
+
minute: '2-digit',
|
|
859
|
+
second: '2-digit',
|
|
853
860
|
}).format(now)}
|
|
861
|
+
</time>
|
|
862
|
+
<time
|
|
863
|
+
dateTime={now.toISOString()}
|
|
864
|
+
className="text-[10px] leading-tight whitespace-nowrap text-sidebar-foreground/90"
|
|
854
865
|
>
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
minute: '2-digit',
|
|
863
|
-
second: '2-digit',
|
|
864
|
-
}).format(now)}
|
|
865
|
-
</time>
|
|
866
|
-
<time
|
|
867
|
-
dateTime={now.toISOString()}
|
|
868
|
-
className="text-[10px] leading-tight whitespace-nowrap text-sidebar-foreground/90"
|
|
869
|
-
>
|
|
870
|
-
{new Intl.DateTimeFormat(currentLanguage, {
|
|
871
|
-
timeZone,
|
|
872
|
-
weekday: 'short',
|
|
873
|
-
day: 'numeric',
|
|
874
|
-
month: 'short',
|
|
875
|
-
}).format(now)}
|
|
876
|
-
</time>
|
|
877
|
-
</div>
|
|
866
|
+
{new Intl.DateTimeFormat(currentLanguage, {
|
|
867
|
+
timeZone,
|
|
868
|
+
weekday: 'short',
|
|
869
|
+
day: 'numeric',
|
|
870
|
+
month: 'short',
|
|
871
|
+
}).format(now)}
|
|
872
|
+
</time>
|
|
878
873
|
</div>
|
|
879
|
-
</
|
|
880
|
-
|
|
874
|
+
</div>
|
|
875
|
+
</>
|
|
881
876
|
);
|
|
882
877
|
}
|