Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/soft-tomatoes-wave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@lynx-js/a2ui-reactlynx": patch
---

feat(a2ui): refactor ui and support theme
21 changes: 17 additions & 4 deletions packages/genui/a2ui-playground/lynx-src/a2ui/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,11 @@ interface InitData {
actionMocksUrl?: string;
actionMocks?: unknown;
instant?: boolean;
theme?: 'light' | 'dark';
}

type Theme = 'light' | 'dark';

type A2uiMessage = Record<string, unknown> & { messageId?: string };
type ResponseMessages = A2uiMessage[];
type ActionMocks = Record<
Expand Down Expand Up @@ -141,6 +144,11 @@ function normalizeInitDataLike(raw: unknown): InitData {
out.instant = instant === true || instant === '1' || instant === 1;
}

const theme = obj.theme;
if (theme === 'light' || theme === 'dark') {
out.theme = theme;
}

return out;
}

Expand All @@ -151,6 +159,7 @@ function mergeInitDataPreferLeft(a: InitData, b: InitData): InitData {
actionMocksUrl: a.actionMocksUrl ?? b.actionMocksUrl,
actionMocks: a.actionMocks ?? b.actionMocks,
instant: a.instant ?? b.instant,
theme: a.theme ?? b.theme,
};
}

Expand Down Expand Up @@ -269,6 +278,11 @@ export function App() {
() => effectiveData.instant === true,
[effectiveData.instant],
);
const theme = useMemo<Theme>(
() => effectiveData.theme ?? 'light',
[effectiveData.theme],
);
const themeClassName = theme === 'dark' ? 'luna-dark' : 'luna-light';

useEffect(() => {
let cancelled = false;
Expand Down Expand Up @@ -334,8 +348,7 @@ export function App() {

return (
<view
className='luna-light'
style={{ width: '100%', height: '100%', backgroundColor: '#fff' }}
className={`page ${themeClassName}`}
>
{error
? (
Expand All @@ -353,7 +366,7 @@ export function App() {
: null}
{store
? (
<scroll-view scroll-y style={{ height: '100%' }}>
<scroll-view scroll-y style={{ flex: 1, minHeight: 0 }}>
<A2UI
messageStore={store}
catalogs={ALL_BUILTINS}
Expand All @@ -362,7 +375,7 @@ export function App() {
// canned response messages back into the same store.
void agentRef.current?.onAction(action);
}}
wrapSurface={(c) => <view className='luna-light'>{c}</view>}
wrapSurface={(c) => <view className={themeClassName}>{c}</view>}
renderEmpty={() => (
<view style={{ padding: '12px' }}>
<text>Loading...</text>
Expand Down
77 changes: 77 additions & 0 deletions packages/genui/a2ui-playground/lynx-src/a2ui/index.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,81 @@
@import "@lynx-js/luna-styles/index.css";

:root {
background-color: var(--canvas);
}

.page {
width: 100%;
height: 100%;
background-color: var(--canvas);
}

/* Override A2UI tokens with Luna tokens for the playground preview. */
.luna-light,
.luna-dark {
--a2ui-color-background: var(--canvas);
--a2ui-color-on-background: var(--content);

--a2ui-color-surface: var(--paper);
--a2ui-color-on-surface: var(--content);

--a2ui-color-primary: var(--primary);
--a2ui-color-primary-light: var(--primary-2);
--a2ui-color-primary-dark: var(--primary-2);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
--a2ui-color-primary-hover: var(--primary-2);
--a2ui-color-on-primary: var(--primary-content);

--a2ui-color-secondary: var(--secondary);
--a2ui-color-secondary-light: var(--secondary-2);
--a2ui-color-secondary-dark: var(--secondary-2);
--a2ui-color-secondary-hover: var(--secondary-2);
--a2ui-color-on-secondary: var(--secondary-content);

--a2ui-border-radius: 0.25rem;
--a2ui-color-border: var(--line);
--a2ui-border-width: 1px;
--a2ui-border: var(--a2ui-border-width) solid var(--a2ui-color-border);

--a2ui-font-family-title: inherit;
--a2ui-font-family-monospace: monospace;
--a2ui-color-input: var(--paper-clear);
--a2ui-color-on-input: var(--content);

--a2ui-grid-base: 0.5rem;
--a2ui-spacing-xs: calc(var(--a2ui-spacing-s) / 2);
--a2ui-spacing-s: calc(var(--a2ui-spacing-m) / 2);
--a2ui-spacing-m: var(--a2ui-grid-base);
--a2ui-spacing-l: calc(var(--a2ui-spacing-m) * 2);
--a2ui-spacing-xl: calc(var(--a2ui-spacing-l) * 2);

--a2ui-font-size: 1rem;
--a2ui-font-scale: 1.2;
--a2ui-font-size-xs: calc(var(--a2ui-font-size-s) / var(--a2ui-font-scale));
--a2ui-font-size-s: calc(var(--a2ui-font-size-m) / var(--a2ui-font-scale));
--a2ui-font-size-m: var(--a2ui-font-size);
--a2ui-font-size-l: calc(var(--a2ui-font-size-m) * var(--a2ui-font-scale));
--a2ui-font-size-xl: calc(var(--a2ui-font-size-l) * var(--a2ui-font-scale));
--a2ui-font-size-2xl: calc(var(--a2ui-font-size-xl) * var(--a2ui-font-scale));

--a2ui-line-height-headings: 1.2;
--a2ui-line-height-body: 1.5;

--a2ui-color-background-ambient: var(--canvas-ambient);
--a2ui-color-surface-muted: var(--neutral-film);
--a2ui-color-surface-strong: var(--paper-clear);
--a2ui-color-border-strong: var(--rule);
--a2ui-color-text: var(--content);
--a2ui-color-text-muted: var(--content-muted);
--a2ui-color-text-subtle: var(--content-muted-2);
--a2ui-color-primary-content-muted: var(--primary-content-faded);
--a2ui-color-overlay: var(--neutral-film);
}

page {
Comment thread
HuJean marked this conversation as resolved.
display: flex;
flex-direction: column;
padding: 10px;
box-sizing: border-box;
background-color: var(--a2ui-color-background);
color: var(--a2ui-color-on-background);
}
13 changes: 13 additions & 0 deletions packages/genui/a2ui-playground/lynx-src/a2ui/rspeedy-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright 2026 The Lynx Authors. All rights reserved.
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.

/// <reference types="@lynx-js/rspeedy/client" />

declare module '@lynx-js/types' {
interface GlobalProps {
theme?: 'light' | 'dark';
}
}

export {};
1 change: 1 addition & 0 deletions packages/genui/a2ui-playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"dependencies": {
"@codemirror/lang-json": "^6.0.2",
"@lynx-js/a2ui-reactlynx": "workspace:*",
"@lynx-js/luna-styles": "0.1.0",
"@lynx-js/lynx-core": "0.1.3",
"@lynx-js/openui-reactlynx": "workspace:*",
"@lynx-js/react": "workspace:*",
Expand Down
13 changes: 11 additions & 2 deletions packages/genui/a2ui-playground/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export function App() {

useLayoutEffect(() => {
document.documentElement.setAttribute('data-theme', theme);
document.documentElement.style.colorScheme = theme;
}, [theme]);

useEffect(() => {
Expand Down Expand Up @@ -142,21 +143,29 @@ export function App() {
key='examples-detail'
protocol={protocol}
demoId={route.demoId}
theme={theme}
/>
)
: <DemosListPage key='examples-index' protocol={protocol} />;
: (
<DemosListPage
key='examples-index'
protocol={protocol}
theme={theme}
/>
);
case 'components':
return (
<ComponentsPage
key='components'
protocol={protocol}
componentName={route.componentName}
theme={theme}
/>
);
default:
return <AIChatPage key='create' protocol={protocol} />;
}
}, [protocol, route.tab, route.componentName, route.demoId]);
}, [protocol, route.tab, route.componentName, route.demoId, theme]);

const protocolVersionControl = (
<div className='protocolControl'>
Expand Down
27 changes: 21 additions & 6 deletions packages/genui/a2ui-playground/src/pages/ComponentsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,13 @@ function createComponentPreviewMessages(
}

function ComponentDetail(
props: { comp: ComponentDoc; protocol: Protocol },
props: {
comp: ComponentDoc;
protocol: Protocol;
theme: 'light' | 'dark';
},
) {
const { comp, protocol } = props;
const { comp, protocol, theme } = props;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
const [usageJson, setUsageJson] = useState(() =>
formatJson(comp.usage[protocol.name])
);
Expand All @@ -65,10 +69,11 @@ function ComponentDetail(
protocol,
demoUrl: DEFAULT_A2UI_DEMO_URL,
messages: createComponentPreviewMessages(comp, parsedUsage.value),
theme,
},
baseUrl,
);
}, [comp, parsedUsage, protocol]);
}, [comp, parsedUsage, protocol, theme]);

return (
<div className='compContent'>
Expand Down Expand Up @@ -200,9 +205,13 @@ function ComponentGrid(props: { protocol: Protocol }) {
}

export function ComponentsPage(
props: { protocol: Protocol; componentName?: string },
props: {
protocol: Protocol;
componentName?: string;
theme: 'light' | 'dark';
},
) {
const { protocol, componentName } = props;
const { protocol, componentName, theme } = props;

const selectedComp = useMemo(
() => (componentName
Expand Down Expand Up @@ -252,7 +261,13 @@ export function ComponentsPage(
</div>

{selectedComp
? <ComponentDetail comp={selectedComp} protocol={protocol} />
? (
<ComponentDetail
comp={selectedComp}
protocol={protocol}
theme={theme}
/>
)
: <ComponentGrid protocol={protocol} />}
</div>
);
Expand Down
9 changes: 6 additions & 3 deletions packages/genui/a2ui-playground/src/pages/DemosListPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ const STATIC_DEMO_IDS = new Set(
[...OFFICIAL_STATIC_DEMOS, ...EXTENDED_STATIC_DEMOS].map((d) => d.id),
);

export function DemosListPage(props: { protocol: Protocol }) {
const { protocol } = props;
export function DemosListPage(
props: { protocol: Protocol; theme: 'light' | 'dark' },
) {
const { protocol, theme } = props;
const baseUrl = window.location.href.replace(/#.*$/, '');

const previewUrls = useMemo(
Expand All @@ -47,6 +49,7 @@ export function DemosListPage(props: { protocol: Protocol }) {
protocol,
demoUrl: DEFAULT_A2UI_DEMO_URL,
messages: scenario.messages,
theme,
demoId: STATIC_DEMO_IDS.has(scenario.id)
? scenario.id
: undefined,
Expand All @@ -56,7 +59,7 @@ export function DemosListPage(props: { protocol: Protocol }) {
),
]),
),
[baseUrl, protocol],
[baseUrl, protocol, theme],
);

const handleOpenExample = useCallback(
Expand Down
14 changes: 11 additions & 3 deletions packages/genui/a2ui-playground/src/pages/DemosPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,12 @@ function findScenarioById(id?: string): Scenario | undefined {
return ALL_SCENARIOS.find((s) => s.id === id);
}

export function DemosPage(props: { protocol: Protocol; demoId?: string }) {
const { protocol, demoId } = props;
export function DemosPage(props: {
protocol: Protocol;
demoId?: string;
theme: 'light' | 'dark';
}) {
const { protocol, demoId, theme } = props;
const initialScenario = findScenarioById(demoId) ?? ALL_SCENARIOS[0];

const [scenarioId, setScenarioId] = useState<string>(
Expand Down Expand Up @@ -195,6 +199,7 @@ export function DemosPage(props: { protocol: Protocol; demoId?: string }) {
demoUrl: DEFAULT_A2UI_DEMO_URL,
messages: parsed,
actionMocks,
theme,
demoId: isKnownDemo ? scenario!.id : undefined,
speed,
},
Expand All @@ -208,6 +213,7 @@ export function DemosPage(props: { protocol: Protocol; demoId?: string }) {
demoUrl: DEFAULT_A2UI_DEMO_URL,
messages: parsed,
actionMocks,
theme,
demoId: isKnownDemo ? scenario!.id : undefined,
speed,
},
Expand Down Expand Up @@ -247,6 +253,7 @@ export function DemosPage(props: { protocol: Protocol; demoId?: string }) {
if (speed !== 1) {
uInline.searchParams.set('speed', String(speed));
}
uInline.searchParams.set('theme', theme);
if (isKnownDemo) {
// Known demo: point to the static JSON served by the rsbuild dev server.
// Native Lynx supports fetch, so App.tsx will load it via messagesUrl.
Expand Down Expand Up @@ -321,6 +328,7 @@ export function DemosPage(props: { protocol: Protocol; demoId?: string }) {
const r = new URL('render.html', targetBaseUrl);
r.searchParams.set('protocol', protocol.name);
r.searchParams.set('demoUrl', DEFAULT_A2UI_DEMO_URL);
r.searchParams.set('theme', theme);
r.searchParams.set('messagesUrl', messagesUrlAbs);
if (actionMocksUrlAbs && actionMocks) {
r.searchParams.set('actionMocksUrl', actionMocksUrlAbs);
Expand All @@ -336,7 +344,7 @@ export function DemosPage(props: { protocol: Protocol; demoId?: string }) {
}
})();
},
[baseUrl, jsonEdited, protocol, rspeedyDevUrl, shareBaseUrl, speed],
[baseUrl, jsonEdited, protocol, rspeedyDevUrl, shareBaseUrl, speed, theme],
);

useEffect(() => {
Expand Down
Loading
Loading