@sybilion/uilib 1.0.25 → 1.0.26
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/esm/mini-app/MiniAppRoot.js +18 -3
- package/dist/esm/mini-app/MiniAppRoot.styl.js +2 -2
- package/dist/esm/types/src/docs/pages/MiniAppRootPage.d.ts +1 -0
- package/dist/esm/types/src/mini-app/MiniAppRoot.d.ts +2 -1
- package/package.json +1 -1
- package/src/docs/pages/MiniAppRootPage.tsx +53 -0
- package/src/docs/registry.ts +6 -0
- package/src/mini-app/MiniAppRoot.styl +11 -2
- package/src/mini-app/MiniAppRoot.styl.d.ts +8 -2
- package/src/mini-app/MiniAppRoot.tsx +28 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import cn from 'classnames';
|
|
2
3
|
import { createContext, useContext, useState, useRef, useCallback, useEffect, useMemo } from 'react';
|
|
3
4
|
import { Scroll } from '@homecode/ui';
|
|
4
5
|
import S from './MiniAppRoot.styl.js';
|
|
@@ -8,6 +9,18 @@ const defaultTheme = {
|
|
|
8
9
|
mode: 'light',
|
|
9
10
|
isDarkMode: false,
|
|
10
11
|
};
|
|
12
|
+
function isEmbeddedMiniApp() {
|
|
13
|
+
return typeof window !== 'undefined' && window.parent !== window;
|
|
14
|
+
}
|
|
15
|
+
function themeFromDocument() {
|
|
16
|
+
if (typeof document === 'undefined')
|
|
17
|
+
return defaultTheme;
|
|
18
|
+
const isDark = document.documentElement.classList.contains('dark');
|
|
19
|
+
return {
|
|
20
|
+
mode: isDark ? 'dark' : 'light',
|
|
21
|
+
isDarkMode: isDark,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
11
24
|
const MiniAppShellContext = createContext(null);
|
|
12
25
|
function useMiniAppShellTheme() {
|
|
13
26
|
const v = useContext(MiniAppShellContext);
|
|
@@ -32,8 +45,8 @@ function isTrustedParentMessage(event) {
|
|
|
32
45
|
return false;
|
|
33
46
|
return true;
|
|
34
47
|
}
|
|
35
|
-
function MiniAppRoot({ children, appId, onThemeChange, }) {
|
|
36
|
-
const [theme, setTheme] = useState(defaultTheme);
|
|
48
|
+
function MiniAppRoot({ children, className, appId, onThemeChange, }) {
|
|
49
|
+
const [theme, setTheme] = useState(() => isEmbeddedMiniApp() ? defaultTheme : themeFromDocument());
|
|
37
50
|
const onThemeChangeRef = useRef(onThemeChange);
|
|
38
51
|
onThemeChangeRef.current = onThemeChange;
|
|
39
52
|
const sendReady = useCallback(() => {
|
|
@@ -50,6 +63,8 @@ function MiniAppRoot({ children, appId, onThemeChange, }) {
|
|
|
50
63
|
}
|
|
51
64
|
}, [appId]);
|
|
52
65
|
useEffect(() => {
|
|
66
|
+
if (!isEmbeddedMiniApp())
|
|
67
|
+
return;
|
|
53
68
|
applyThemeToDocument(theme.mode);
|
|
54
69
|
}, [theme.mode]);
|
|
55
70
|
useEffect(() => {
|
|
@@ -73,7 +88,7 @@ function MiniAppRoot({ children, appId, onThemeChange, }) {
|
|
|
73
88
|
return () => window.removeEventListener('load', sendReady);
|
|
74
89
|
}, [sendReady]);
|
|
75
90
|
const ctx = useMemo(() => ({ theme }), [theme]);
|
|
76
|
-
return (jsx(MiniAppShellContext.Provider, { value: ctx, children: jsx(Scroll, { y: true, fadeSize: "l", className: S.root, children: children }) }));
|
|
91
|
+
return (jsx(MiniAppShellContext.Provider, { value: ctx, children: jsx(Scroll, { y: true, fadeSize: "l", className: cn(S.root, className), innerClassName: S.inner, offset: { y: { before: 50, after: 50 } }, autoHide: true, children: children }) }));
|
|
77
92
|
}
|
|
78
93
|
|
|
79
94
|
export { MiniAppRoot, useMiniAppShellTheme };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import styleInject from 'style-inject';
|
|
2
2
|
|
|
3
|
-
var css_248z = "@media (max-width:768px){:root{--page-x-padding:var(--p-6);--page-y-padding:var(--p-6)}}.MiniAppRoot_root__UVklz{
|
|
4
|
-
var S = {"root":"MiniAppRoot_root__UVklz"};
|
|
3
|
+
var css_248z = "@media (max-width:768px){:root{--page-x-padding:var(--p-6);--page-y-padding:var(--p-6)}}.MiniAppRoot_root__UVklz{height:100vh;min-height:0;overflow:hidden;position:relative;width:100%}@media (max-width:768px){.MiniAppRoot_root__UVklz{flex:1;height:auto;min-height:0}}.MiniAppRoot_inner__1ZFfl{box-sizing:border-box;max-height:100%;padding:var(--page-y-padding) var(--page-x-padding);width:100%}";
|
|
4
|
+
var S = {"root":"MiniAppRoot_root__UVklz","inner":"MiniAppRoot_inner__1ZFfl"};
|
|
5
5
|
styleInject(css_248z);
|
|
6
6
|
|
|
7
7
|
export { S as default };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function MiniAppRootPage(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -7,8 +7,9 @@ export type MiniAppShellContextValue = {
|
|
|
7
7
|
export declare function useMiniAppShellTheme(): MiniAppShellContextValue;
|
|
8
8
|
export type MiniAppRootProps = {
|
|
9
9
|
children: ReactNode;
|
|
10
|
+
className?: string;
|
|
10
11
|
/** Included in READY payload when set. */
|
|
11
12
|
appId?: string;
|
|
12
13
|
onThemeChange?: (theme: ThemeSyncPayload) => void;
|
|
13
14
|
};
|
|
14
|
-
export declare function MiniAppRoot({ children, appId, onThemeChange, }: MiniAppRootProps): React.ReactElement;
|
|
15
|
+
export declare function MiniAppRoot({ children, className, appId, onThemeChange, }: MiniAppRootProps): React.ReactElement;
|
package/package.json
CHANGED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { PageContentSection } from '#uilib/components/ui/Page';
|
|
2
|
+
import { MiniAppRoot, useMiniAppShellTheme } from '#uilib/mini-app';
|
|
3
|
+
|
|
4
|
+
import { AppPageHeader } from '../components/AppPageHeader/AppPageHeader';
|
|
5
|
+
import { DocsHeaderActions } from '../docsHeaderActions';
|
|
6
|
+
|
|
7
|
+
function MiniAppThemeDemo() {
|
|
8
|
+
const { theme } = useMiniAppShellTheme();
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<>
|
|
12
|
+
<p style={{ margin: 0 }}>
|
|
13
|
+
Context theme: <code>{theme.mode}</code>, isDarkMode:{' '}
|
|
14
|
+
<code>{String(theme.isDarkMode)}</code>
|
|
15
|
+
</p>
|
|
16
|
+
<p style={{ margin: 0, color: 'var(--muted-foreground)' }}>
|
|
17
|
+
Long content below — scroll should stay inside the mini-app shell (not
|
|
18
|
+
only the docs page), matching iframe workspace apps.
|
|
19
|
+
</p>
|
|
20
|
+
{Array.from({ length: 36 }, (_, i) => (
|
|
21
|
+
<p key={i} style={{ margin: 0, lineHeight: 1.6 }}>
|
|
22
|
+
Block {i + 1}: Lorem ipsum dolor sit amet, consectetur adipiscing
|
|
23
|
+
elit. Sed do eiusmod tempor incididunt ut labore et dolore magna
|
|
24
|
+
aliqua.
|
|
25
|
+
</p>
|
|
26
|
+
))}
|
|
27
|
+
</>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default function MiniAppRootPage() {
|
|
32
|
+
return (
|
|
33
|
+
<>
|
|
34
|
+
<AppPageHeader
|
|
35
|
+
breadcrumbs={[{ label: 'MiniAppRoot' }]}
|
|
36
|
+
title="MiniAppRoot"
|
|
37
|
+
subheader="Workspace mini-app shell: syncs document light/dark from parent THEME_SYNC postMessage, exposes theme via useMiniAppShellTheme. When embedded in Sybilion, child posts READY to parent; on this top-level docs page READY is skipped (no iframe parent)."
|
|
38
|
+
actions={<DocsHeaderActions />}
|
|
39
|
+
/>
|
|
40
|
+
<style>{`
|
|
41
|
+
.mini-app-root-page {
|
|
42
|
+
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
|
|
43
|
+
border-radius: var(--p-4);
|
|
44
|
+
}
|
|
45
|
+
`}</style>
|
|
46
|
+
<PageContentSection style={{ maxHeight: '500px' }}>
|
|
47
|
+
<MiniAppRoot appId="uilib-docs" className="mini-app-root-page">
|
|
48
|
+
<MiniAppThemeDemo />
|
|
49
|
+
</MiniAppRoot>
|
|
50
|
+
</PageContentSection>
|
|
51
|
+
</>
|
|
52
|
+
);
|
|
53
|
+
}
|
package/src/docs/registry.ts
CHANGED
|
@@ -156,6 +156,12 @@ export const DOC_REGISTRY: DocEntry[] = [
|
|
|
156
156
|
section: 'Layout',
|
|
157
157
|
load: () => import('./pages/InteractiveContentPage'),
|
|
158
158
|
},
|
|
159
|
+
{
|
|
160
|
+
slug: 'mini-app-root',
|
|
161
|
+
title: 'MiniAppRoot',
|
|
162
|
+
section: 'Layout',
|
|
163
|
+
load: () => import('./pages/MiniAppRootPage'),
|
|
164
|
+
},
|
|
159
165
|
{
|
|
160
166
|
slug: 'label',
|
|
161
167
|
title: 'Label',
|
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
@import 'lib/theme.styl'
|
|
2
2
|
|
|
3
|
+
// Scroll root must stay flex row: [scrollable inner | y-bar]. column stacks inner
|
|
4
|
+
// + scrollbar → inner grows with content → overflow-y never engages (clips via overflow:hidden).
|
|
3
5
|
.root
|
|
4
6
|
overflow hidden
|
|
5
7
|
|
|
6
8
|
position relative
|
|
7
|
-
display flex
|
|
8
9
|
width 100%
|
|
9
10
|
height 100vh
|
|
10
|
-
|
|
11
|
+
min-height 0
|
|
11
12
|
|
|
12
13
|
@media (max-width MOBILE)
|
|
13
14
|
height auto
|
|
14
15
|
flex 1
|
|
15
16
|
min-height 0
|
|
17
|
+
|
|
18
|
+
// Match PageScroll: constrain inner so homecode Scroll max-height:100% + overflow-y work
|
|
19
|
+
.inner
|
|
20
|
+
width 100%
|
|
21
|
+
// min-height 0
|
|
22
|
+
max-height 100%
|
|
23
|
+
box-sizing border-box
|
|
24
|
+
padding var(--page-y-padding) var(--page-x-padding)
|
|
@@ -1,2 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// This file is automatically generated.
|
|
2
|
+
// Please do not change this file!
|
|
3
|
+
interface CssExports {
|
|
4
|
+
'inner': string;
|
|
5
|
+
'root': string;
|
|
6
|
+
}
|
|
7
|
+
export const cssExports: CssExports;
|
|
8
|
+
export default cssExports;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import cn from 'classnames';
|
|
1
2
|
import type { ReactNode } from 'react';
|
|
2
3
|
import React, {
|
|
3
4
|
createContext,
|
|
@@ -25,6 +26,19 @@ const defaultTheme: ThemeSyncPayload = {
|
|
|
25
26
|
isDarkMode: false,
|
|
26
27
|
};
|
|
27
28
|
|
|
29
|
+
function isEmbeddedMiniApp(): boolean {
|
|
30
|
+
return typeof window !== 'undefined' && window.parent !== window;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function themeFromDocument(): ThemeSyncPayload {
|
|
34
|
+
if (typeof document === 'undefined') return defaultTheme;
|
|
35
|
+
const isDark = document.documentElement.classList.contains('dark');
|
|
36
|
+
return {
|
|
37
|
+
mode: isDark ? 'dark' : 'light',
|
|
38
|
+
isDarkMode: isDark,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
28
42
|
export type MiniAppShellContextValue = {
|
|
29
43
|
theme: ThemeSyncPayload;
|
|
30
44
|
};
|
|
@@ -58,6 +72,7 @@ function isTrustedParentMessage(event: MessageEvent): boolean {
|
|
|
58
72
|
|
|
59
73
|
export type MiniAppRootProps = {
|
|
60
74
|
children: ReactNode;
|
|
75
|
+
className?: string;
|
|
61
76
|
/** Included in READY payload when set. */
|
|
62
77
|
appId?: string;
|
|
63
78
|
onThemeChange?: (theme: ThemeSyncPayload) => void;
|
|
@@ -65,10 +80,13 @@ export type MiniAppRootProps = {
|
|
|
65
80
|
|
|
66
81
|
export function MiniAppRoot({
|
|
67
82
|
children,
|
|
83
|
+
className,
|
|
68
84
|
appId,
|
|
69
85
|
onThemeChange,
|
|
70
86
|
}: MiniAppRootProps): React.ReactElement {
|
|
71
|
-
const [theme, setTheme] = useState<ThemeSyncPayload>(
|
|
87
|
+
const [theme, setTheme] = useState<ThemeSyncPayload>(() =>
|
|
88
|
+
isEmbeddedMiniApp() ? defaultTheme : themeFromDocument(),
|
|
89
|
+
);
|
|
72
90
|
const onThemeChangeRef = useRef(onThemeChange);
|
|
73
91
|
onThemeChangeRef.current = onThemeChange;
|
|
74
92
|
|
|
@@ -86,6 +104,7 @@ export function MiniAppRoot({
|
|
|
86
104
|
}, [appId]);
|
|
87
105
|
|
|
88
106
|
useEffect(() => {
|
|
107
|
+
if (!isEmbeddedMiniApp()) return;
|
|
89
108
|
applyThemeToDocument(theme.mode);
|
|
90
109
|
}, [theme.mode]);
|
|
91
110
|
|
|
@@ -112,7 +131,14 @@ export function MiniAppRoot({
|
|
|
112
131
|
|
|
113
132
|
return (
|
|
114
133
|
<MiniAppShellContext.Provider value={ctx}>
|
|
115
|
-
<Scroll
|
|
134
|
+
<Scroll
|
|
135
|
+
y
|
|
136
|
+
fadeSize="l"
|
|
137
|
+
className={cn(S.root, className)}
|
|
138
|
+
innerClassName={S.inner}
|
|
139
|
+
offset={{ y: { before: 50, after: 50 } }}
|
|
140
|
+
autoHide
|
|
141
|
+
>
|
|
116
142
|
{children}
|
|
117
143
|
</Scroll>
|
|
118
144
|
</MiniAppShellContext.Provider>
|