@xenide-io/the-old-ui-theme 0.3.2 → 0.4.3
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-54ZR4VL5.mjs +4641 -0
- package/dist/chunk-G53YLHVE.mjs +1023 -0
- package/dist/{index-BgG8Homu.d.mts → index-B5NOyoKS.d.mts} +100 -100
- package/dist/{index-BgG8Homu.d.ts → index-B5NOyoKS.d.ts} +100 -100
- package/dist/index.d.mts +9 -145
- package/dist/index.d.ts +9 -145
- package/dist/index.js +653 -1111
- package/dist/index.mjs +86 -342
- package/dist/suite.d.mts +760 -0
- package/dist/suite.d.ts +760 -0
- package/dist/suite.js +2800 -0
- package/dist/suite.mjs +2441 -0
- package/dist/ui.d.mts +1 -2
- package/dist/ui.d.ts +1 -2
- package/dist/ui.js +14 -10
- package/dist/ui.mjs +13 -11
- package/package.json +43 -24
- package/src/components/icons/icons.tsx +0 -2
- package/src/components/icons/index.ts +0 -3
- package/src/components/sections/IconShowcase.tsx +1 -110
- package/src/components/sections/NewComponentsShowcase.tsx +4 -4
- package/src/components/sections/SidebarShowcase.tsx +3 -3
- package/src/components/sections/SuiteShowcase.tsx +669 -0
- package/src/components/ui/ComponentDocs.tsx +2 -2
- package/src/components/ui/ContextMenu.tsx +1 -1
- package/src/components/ui/Menubar.tsx +1 -1
- package/src/components/ui/Sidebar.tsx +10 -8
- package/src/styles/suite-skin.css +255 -0
- package/src/suite/components/ai-panel.tsx +202 -0
- package/src/suite/components/app-switcher.tsx +196 -0
- package/src/suite/components/command-palette-host.tsx +62 -0
- package/src/suite/components/deferred-chrome.tsx +12 -0
- package/src/suite/components/suite-app-layout.tsx +69 -0
- package/src/suite/components/suite-bottom-nav.tsx +113 -0
- package/src/suite/components/suite-layout.tsx +285 -0
- package/src/suite/components/suite-mobile-drawer.tsx +150 -0
- package/src/suite/components/suite-mobile-header.tsx +71 -0
- package/src/suite/components/suite-notification-bell.tsx +227 -0
- package/src/suite/components/suite-settings-mobile-nav.tsx +83 -0
- package/src/suite/components/suite-sidebar.tsx +198 -0
- package/src/suite/components/suite-skeleton.tsx +191 -0
- package/src/suite/components/suite-user-menu.tsx +109 -0
- package/src/suite/components/theme-provider.tsx +174 -0
- package/src/suite/components/today-calibrating.tsx +95 -0
- package/src/suite/components/today-page-frame.tsx +28 -0
- package/src/suite/components/today-ui.tsx +370 -0
- package/src/suite/icons/app-accents.ts +75 -0
- package/src/suite/icons/glyph-parts.tsx +67 -0
- package/src/suite/icons/glyphs.ts +203 -0
- package/src/suite/icons/index.ts +12 -0
- package/src/suite/icons/suite-app-icon.tsx +68 -0
- package/src/suite/icons/suite-icon.tsx +93 -0
- package/src/suite/index.ts +108 -0
- package/src/suite/lib/apps.ts +75 -0
- package/src/suite/lib/cn.ts +6 -0
- package/src/suite/lib/injected.ts +54 -0
- package/src/suite/lib/motion.tsx +48 -0
- package/src/suite/lib/use-idle-mount.ts +25 -0
- package/dist/chunk-5HSOBJ4F.mjs +0 -5968
- package/src/components/icons/lemon-brand-icons.tsx +0 -16
- package/src/components/icons/lemon-category-icons.tsx +0 -72
- package/src/components/icons/lemon-icons.tsx +0 -57
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import Image from 'next/image';
|
|
4
|
+
import { useRouter } from 'next/navigation';
|
|
5
|
+
import { LogOut, Settings } from 'iconoir-react';
|
|
6
|
+
|
|
7
|
+
import { cn } from '../lib/cn';
|
|
8
|
+
import { DropdownMenu, DropdownItem } from '../../components/ui/DropdownMenu';
|
|
9
|
+
|
|
10
|
+
export interface SuiteUserMenuProps {
|
|
11
|
+
name?: string | null;
|
|
12
|
+
email?: string | null;
|
|
13
|
+
image?: string | null;
|
|
14
|
+
/** Destination for the "Profile settings" item. */
|
|
15
|
+
settingsHref: string;
|
|
16
|
+
onSignOut: () => void;
|
|
17
|
+
/** Fallback letter(s) when there is no avatar image. */
|
|
18
|
+
fallbackInitials?: string;
|
|
19
|
+
dataTest?: string;
|
|
20
|
+
className?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function computeInitials(name: string | null | undefined, email: string | null | undefined): string {
|
|
24
|
+
const source = (name || email || '?').trim();
|
|
25
|
+
return source
|
|
26
|
+
.split(/[\s@]+/)
|
|
27
|
+
.filter(Boolean)
|
|
28
|
+
.slice(0, 2)
|
|
29
|
+
.map((part) => part[0])
|
|
30
|
+
.join('')
|
|
31
|
+
.toUpperCase();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Suite account context menu — one tap on the avatar opens a small menu
|
|
36
|
+
* with profile settings and sign out. Used in the mobile app bar of every
|
|
37
|
+
* app so account actions behave identically across the suite (no dedicated
|
|
38
|
+
* logout icon cluttering the mobile chrome).
|
|
39
|
+
*/
|
|
40
|
+
export function SuiteUserMenu({
|
|
41
|
+
name,
|
|
42
|
+
email,
|
|
43
|
+
image,
|
|
44
|
+
settingsHref,
|
|
45
|
+
onSignOut,
|
|
46
|
+
fallbackInitials,
|
|
47
|
+
dataTest = 'suite-user-menu',
|
|
48
|
+
className,
|
|
49
|
+
}: SuiteUserMenuProps) {
|
|
50
|
+
const router = useRouter();
|
|
51
|
+
const initials = fallbackInitials ?? computeInitials(name, email);
|
|
52
|
+
|
|
53
|
+
const avatar = (
|
|
54
|
+
<span className="inline-flex h-11 w-11 items-center justify-center rounded-full transition-colors hover:bg-ph-muted">
|
|
55
|
+
{image ? (
|
|
56
|
+
<Image
|
|
57
|
+
src={image}
|
|
58
|
+
alt=""
|
|
59
|
+
width={32}
|
|
60
|
+
height={32}
|
|
61
|
+
className="h-8 w-8 rounded-full object-cover"
|
|
62
|
+
unoptimized
|
|
63
|
+
/>
|
|
64
|
+
) : (
|
|
65
|
+
<span className="flex h-8 w-8 items-center justify-center rounded-full bg-ph-muted text-sm font-semibold text-ph-brand">
|
|
66
|
+
{initials}
|
|
67
|
+
</span>
|
|
68
|
+
)}
|
|
69
|
+
</span>
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<DropdownMenu
|
|
74
|
+
trigger={avatar}
|
|
75
|
+
aria-label="Account menu"
|
|
76
|
+
align="end"
|
|
77
|
+
side="bottom"
|
|
78
|
+
sideOffset={6}
|
|
79
|
+
collisionPadding={16}
|
|
80
|
+
modal={false}
|
|
81
|
+
className={className}
|
|
82
|
+
data-test={dataTest}
|
|
83
|
+
>
|
|
84
|
+
<div className="px-2 py-1.5">
|
|
85
|
+
<p className="truncate text-sm font-medium text-ph-ink">
|
|
86
|
+
{name || email || 'Account'}
|
|
87
|
+
</p>
|
|
88
|
+
{email ? (
|
|
89
|
+
<p className="truncate text-xs text-ph-mutedtext">{email}</p>
|
|
90
|
+
) : null}
|
|
91
|
+
</div>
|
|
92
|
+
<div className="my-1 border-t border-ph-border" />
|
|
93
|
+
<DropdownItem
|
|
94
|
+
data-test={`${dataTest}-settings`}
|
|
95
|
+
onClick={() => router.push(settingsHref)}
|
|
96
|
+
>
|
|
97
|
+
<Settings className="h-4 w-4 text-ph-mutedtext" aria-hidden />
|
|
98
|
+
Profile settings
|
|
99
|
+
</DropdownItem>
|
|
100
|
+
<DropdownItem
|
|
101
|
+
data-test={`${dataTest}-sign-out`}
|
|
102
|
+
onClick={onSignOut}
|
|
103
|
+
>
|
|
104
|
+
<LogOut className="h-4 w-4 text-ph-mutedtext" aria-hidden />
|
|
105
|
+
Sign out
|
|
106
|
+
</DropdownItem>
|
|
107
|
+
</DropdownMenu>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createContext,
|
|
5
|
+
useCallback,
|
|
6
|
+
useContext,
|
|
7
|
+
useEffect,
|
|
8
|
+
useMemo,
|
|
9
|
+
useState,
|
|
10
|
+
type ReactNode,
|
|
11
|
+
} from 'react';
|
|
12
|
+
|
|
13
|
+
export type SuiteTheme = 'system' | 'light' | 'dark';
|
|
14
|
+
export type SuiteResolvedTheme = 'light' | 'dark';
|
|
15
|
+
|
|
16
|
+
export interface SuiteThemeConfig {
|
|
17
|
+
/** localStorage key holding the stored preference. */
|
|
18
|
+
storageKey: string;
|
|
19
|
+
/** `data-theme` attribute value for the light theme. */
|
|
20
|
+
lightThemeId: string;
|
|
21
|
+
/** `data-theme` attribute value for the dark theme. */
|
|
22
|
+
darkThemeId: string;
|
|
23
|
+
/**
|
|
24
|
+
* Resolved theme to assume when `matchMedia` is unavailable
|
|
25
|
+
* (server render / pre-hydration).
|
|
26
|
+
*/
|
|
27
|
+
fallbackTheme: SuiteResolvedTheme;
|
|
28
|
+
/**
|
|
29
|
+
* Optional `<meta id="theme-color-meta">` colours, synced whenever the
|
|
30
|
+
* theme is (re)applied after mount. Omit both to leave the meta tag
|
|
31
|
+
* untouched.
|
|
32
|
+
*/
|
|
33
|
+
themeColorLight?: string;
|
|
34
|
+
themeColorDark?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface SuiteThemeContextValue {
|
|
38
|
+
theme: SuiteTheme;
|
|
39
|
+
resolvedTheme: SuiteResolvedTheme;
|
|
40
|
+
setTheme: (theme: SuiteTheme) => void;
|
|
41
|
+
toggleTheme: () => void;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const SuiteThemeContext = createContext<SuiteThemeContextValue | null>(null);
|
|
45
|
+
|
|
46
|
+
function systemTheme(fallback: SuiteResolvedTheme): SuiteResolvedTheme {
|
|
47
|
+
if (typeof window === 'undefined') return fallback;
|
|
48
|
+
return window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
49
|
+
? 'dark'
|
|
50
|
+
: 'light';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function resolveTheme(
|
|
54
|
+
theme: SuiteTheme,
|
|
55
|
+
fallback: SuiteResolvedTheme,
|
|
56
|
+
): SuiteResolvedTheme {
|
|
57
|
+
return theme === 'system' ? systemTheme(fallback) : theme;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function applyTheme(
|
|
61
|
+
theme: SuiteTheme,
|
|
62
|
+
config: SuiteThemeConfig,
|
|
63
|
+
): SuiteResolvedTheme {
|
|
64
|
+
const resolved = resolveTheme(theme, config.fallbackTheme);
|
|
65
|
+
if (typeof document === 'undefined') return resolved;
|
|
66
|
+
document.documentElement.setAttribute(
|
|
67
|
+
'data-theme',
|
|
68
|
+
resolved === 'dark' ? config.darkThemeId : config.lightThemeId,
|
|
69
|
+
);
|
|
70
|
+
document.documentElement.classList.toggle('dark', resolved === 'dark');
|
|
71
|
+
if (config.themeColorLight && config.themeColorDark) {
|
|
72
|
+
const meta = document.getElementById(
|
|
73
|
+
'theme-color-meta',
|
|
74
|
+
) as HTMLMetaElement | null;
|
|
75
|
+
if (meta) {
|
|
76
|
+
meta.content =
|
|
77
|
+
resolved === 'light' ? config.themeColorLight : config.themeColorDark;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return resolved;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function readStoredTheme(storageKey: string): SuiteTheme {
|
|
84
|
+
if (typeof window === 'undefined') return 'system';
|
|
85
|
+
const stored = localStorage.getItem(storageKey);
|
|
86
|
+
if (stored === 'light' || stored === 'dark' || stored === 'system')
|
|
87
|
+
return stored;
|
|
88
|
+
return 'system';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Theme provider shared by suite apps. Each app's root layout runs a small
|
|
93
|
+
* boot script that applies `data-theme` before hydration; this provider
|
|
94
|
+
* adopts that DOM state on mount and owns every post-hydration change (user
|
|
95
|
+
* picks a theme, or the OS preference flips while set to `system`).
|
|
96
|
+
*
|
|
97
|
+
* Apps wrap this with their own `ThemeProvider`/`useTheme` module that pins
|
|
98
|
+
* the storage key, theme ids and meta colours.
|
|
99
|
+
*/
|
|
100
|
+
export function SuiteThemeProvider({
|
|
101
|
+
config,
|
|
102
|
+
children,
|
|
103
|
+
}: {
|
|
104
|
+
config: SuiteThemeConfig;
|
|
105
|
+
children: ReactNode;
|
|
106
|
+
}) {
|
|
107
|
+
const [state, setState] = useState<{
|
|
108
|
+
theme: SuiteTheme;
|
|
109
|
+
resolvedTheme: SuiteResolvedTheme;
|
|
110
|
+
}>(() => {
|
|
111
|
+
const theme = readStoredTheme(config.storageKey);
|
|
112
|
+
if (typeof document === 'undefined') {
|
|
113
|
+
return { theme, resolvedTheme: config.fallbackTheme };
|
|
114
|
+
}
|
|
115
|
+
const domTheme = document.documentElement.getAttribute('data-theme');
|
|
116
|
+
const resolvedTheme =
|
|
117
|
+
domTheme === config.darkThemeId
|
|
118
|
+
? 'dark'
|
|
119
|
+
: domTheme === config.lightThemeId
|
|
120
|
+
? 'light'
|
|
121
|
+
: resolveTheme(theme, config.fallbackTheme);
|
|
122
|
+
return { theme, resolvedTheme };
|
|
123
|
+
});
|
|
124
|
+
const { theme, resolvedTheme } = state;
|
|
125
|
+
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
if (theme !== 'system') return;
|
|
128
|
+
const media = window.matchMedia('(prefers-color-scheme: dark)');
|
|
129
|
+
const update = () => {
|
|
130
|
+
const resolved = applyTheme('system', config);
|
|
131
|
+
setState((current) => ({ ...current, resolvedTheme: resolved }));
|
|
132
|
+
};
|
|
133
|
+
media.addEventListener('change', update);
|
|
134
|
+
return () => media.removeEventListener('change', update);
|
|
135
|
+
}, [theme, config]);
|
|
136
|
+
|
|
137
|
+
const setTheme = useCallback(
|
|
138
|
+
(next: SuiteTheme) => {
|
|
139
|
+
const resolved = applyTheme(next, config);
|
|
140
|
+
setState({ theme: next, resolvedTheme: resolved });
|
|
141
|
+
localStorage.setItem(config.storageKey, next);
|
|
142
|
+
},
|
|
143
|
+
[config],
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
const toggleTheme = useCallback(() => {
|
|
147
|
+
setState((prev) => {
|
|
148
|
+
const next: SuiteTheme =
|
|
149
|
+
resolveTheme(prev.theme, config.fallbackTheme) === 'dark'
|
|
150
|
+
? 'light'
|
|
151
|
+
: 'dark';
|
|
152
|
+
const resolved = applyTheme(next, config);
|
|
153
|
+
localStorage.setItem(config.storageKey, next);
|
|
154
|
+
return { theme: next, resolvedTheme: resolved };
|
|
155
|
+
});
|
|
156
|
+
}, [config]);
|
|
157
|
+
|
|
158
|
+
const value = useMemo(
|
|
159
|
+
() => ({ theme, resolvedTheme, setTheme, toggleTheme }),
|
|
160
|
+
[theme, resolvedTheme, setTheme, toggleTheme],
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<SuiteThemeContext.Provider value={value}>
|
|
165
|
+
{children}
|
|
166
|
+
</SuiteThemeContext.Provider>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function useSuiteTheme(): SuiteThemeContextValue {
|
|
171
|
+
const ctx = useContext(SuiteThemeContext);
|
|
172
|
+
if (!ctx) throw new Error('useTheme must be used within ThemeProvider');
|
|
173
|
+
return ctx;
|
|
174
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Refresh as RefreshCw, SunLight as Sun } from 'iconoir-react';
|
|
4
|
+
|
|
5
|
+
import { cn } from '../lib/cn';
|
|
6
|
+
import type { SuiteSpinnerComponent } from '../lib/injected';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Today-page loading/empty calibrating state. Apps inject their branded
|
|
10
|
+
* AppSpinner; `variant` picks the ornamentation style:
|
|
11
|
+
* - `ring` — large spinner ring + ping behind the sun (TurtleTime/ShellStack)
|
|
12
|
+
* - `pulse` — pulsing sun only (Tides/Kraken)
|
|
13
|
+
*/
|
|
14
|
+
export function TodayCalibrating({
|
|
15
|
+
onRetry,
|
|
16
|
+
retrying = false,
|
|
17
|
+
className,
|
|
18
|
+
spinner: Spinner,
|
|
19
|
+
variant = 'ring',
|
|
20
|
+
retrySpinnerClassName = 'text-ph-brand',
|
|
21
|
+
}: {
|
|
22
|
+
onRetry?: () => void;
|
|
23
|
+
retrying?: boolean;
|
|
24
|
+
className?: string;
|
|
25
|
+
spinner: SuiteSpinnerComponent;
|
|
26
|
+
variant?: 'ring' | 'pulse';
|
|
27
|
+
retrySpinnerClassName?: string;
|
|
28
|
+
}) {
|
|
29
|
+
return (
|
|
30
|
+
<div
|
|
31
|
+
className={cn(
|
|
32
|
+
'relative flex min-h-[14rem] flex-col items-center justify-center overflow-hidden rounded-2xl border border-ph-border bg-ph-surface px-5 py-8 text-center shadow-sm',
|
|
33
|
+
className,
|
|
34
|
+
)}
|
|
35
|
+
data-test="today-calibrating"
|
|
36
|
+
>
|
|
37
|
+
<div
|
|
38
|
+
className="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_50%_35%,rgba(251,191,36,0.12),transparent_55%)]"
|
|
39
|
+
aria-hidden
|
|
40
|
+
/>
|
|
41
|
+
<div className="relative mb-6 flex h-24 w-24 items-center justify-center">
|
|
42
|
+
{variant === 'ring' ? (
|
|
43
|
+
<>
|
|
44
|
+
<Spinner
|
|
45
|
+
size="lg"
|
|
46
|
+
className="absolute inset-0 m-auto opacity-40"
|
|
47
|
+
label="Loading today"
|
|
48
|
+
/>
|
|
49
|
+
<span
|
|
50
|
+
className="bg-ph-muted/30 absolute inset-2 animate-ping rounded-full"
|
|
51
|
+
aria-hidden
|
|
52
|
+
/>
|
|
53
|
+
</>
|
|
54
|
+
) : null}
|
|
55
|
+
<span className="relative flex h-14 w-14 items-center justify-center rounded-2xl border border-ph-border bg-ph-surface text-ph-ink shadow-sm">
|
|
56
|
+
<Sun
|
|
57
|
+
className={cn('h-9 w-9', variant === 'pulse' && 'animate-pulse')}
|
|
58
|
+
strokeWidth={2}
|
|
59
|
+
aria-hidden
|
|
60
|
+
/>
|
|
61
|
+
</span>
|
|
62
|
+
</div>
|
|
63
|
+
<h2 className="relative text-base font-semibold tracking-tight text-ph-ink sm:text-lg md:text-xl">
|
|
64
|
+
Calibrating your day
|
|
65
|
+
</h2>
|
|
66
|
+
<p className="relative mt-1.5 max-w-sm text-sm sm:text-[15px] leading-relaxed text-ph-subtle">
|
|
67
|
+
Pulling together tasks, time and updates from your workspace…
|
|
68
|
+
</p>
|
|
69
|
+
<div className="relative mt-5 flex gap-1.5" aria-hidden>
|
|
70
|
+
{[0, 1, 2, 3, 4].map((i) => (
|
|
71
|
+
<span
|
|
72
|
+
key={i}
|
|
73
|
+
className="h-1.5 w-5 animate-pulse rounded-full bg-ph-muted"
|
|
74
|
+
style={{ animationDelay: `${i * 120}ms` }}
|
|
75
|
+
/>
|
|
76
|
+
))}
|
|
77
|
+
</div>
|
|
78
|
+
{onRetry ? (
|
|
79
|
+
<button
|
|
80
|
+
type="button"
|
|
81
|
+
onClick={onRetry}
|
|
82
|
+
disabled={retrying}
|
|
83
|
+
className="relative mt-6 inline-flex items-center gap-2 rounded-xl border border-ph-border bg-ph-canvas px-4 py-2 text-sm font-medium text-ph-ink transition hover:bg-ph-muted disabled:opacity-60"
|
|
84
|
+
>
|
|
85
|
+
{retrying ? (
|
|
86
|
+
<Spinner size="sm" className={retrySpinnerClassName} />
|
|
87
|
+
) : (
|
|
88
|
+
<RefreshCw className="h-4 w-4" aria-hidden />
|
|
89
|
+
)}
|
|
90
|
+
Try again
|
|
91
|
+
</button>
|
|
92
|
+
) : null}
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { ReactNode } from 'react';
|
|
4
|
+
|
|
5
|
+
/** Full-bleed theme wash + soft clouds; uses ph-* tokens from each app's theme. */
|
|
6
|
+
export function TodayPageFrame({ children }: { children: ReactNode }) {
|
|
7
|
+
return (
|
|
8
|
+
<div
|
|
9
|
+
data-test="today-page"
|
|
10
|
+
className="today-page-frame relative flex min-h-0 flex-1 flex-col overflow-y-auto overflow-x-hidden"
|
|
11
|
+
>
|
|
12
|
+
<div
|
|
13
|
+
className="pointer-events-none absolute inset-x-0 top-0 h-[34rem] overflow-hidden"
|
|
14
|
+
aria-hidden
|
|
15
|
+
>
|
|
16
|
+
<div className="today-page-frame__glow absolute inset-0" />
|
|
17
|
+
<div className="today-page-frame__sun absolute -right-16 -top-16 h-72 w-72 rounded-full bg-gradient-to-br from-amber-300/30 via-rose-300/20 to-rose-400/10 blur-3xl opacity-60 motion-safe:animate-[pulse_8s_ease-in-out_infinite] motion-reduce:opacity-40" />
|
|
18
|
+
<div className="today-page-frame__top-fade absolute inset-x-0 top-0 h-64" />
|
|
19
|
+
<div className="today-page-frame__cloud today-page-frame__cloud--1 absolute -left-24 top-28 h-80 w-[26rem] rounded-[45%]" />
|
|
20
|
+
<div className="today-page-frame__cloud today-page-frame__cloud--2 absolute -right-20 top-40 h-72 w-80 rounded-[40%]" />
|
|
21
|
+
<div className="today-page-frame__cloud today-page-frame__cloud--3 absolute left-[28%] top-[58%] h-64 w-[22rem] rounded-[50%]" />
|
|
22
|
+
</div>
|
|
23
|
+
<div className="relative flex-1 px-4 pb-10 pt-5 sm:px-6 sm:pt-6 lg:px-8 [&_.list-row]:min-h-[52px] [&_.list-row]:sm:min-h-[44px]">
|
|
24
|
+
{children}
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
}
|