diff --git a/.changeset/soft-tomatoes-wave.md b/.changeset/soft-tomatoes-wave.md new file mode 100644 index 0000000000..dbf5da239e --- /dev/null +++ b/.changeset/soft-tomatoes-wave.md @@ -0,0 +1,5 @@ +--- +"@lynx-js/a2ui-reactlynx": patch +--- + +feat(a2ui): refactor ui and support theme diff --git a/packages/genui/a2ui-playground/lynx-src/a2ui/App.tsx b/packages/genui/a2ui-playground/lynx-src/a2ui/App.tsx index 379702e162..353858ce99 100644 --- a/packages/genui/a2ui-playground/lynx-src/a2ui/App.tsx +++ b/packages/genui/a2ui-playground/lynx-src/a2ui/App.tsx @@ -71,8 +71,11 @@ interface InitData { actionMocksUrl?: string; actionMocks?: unknown; instant?: boolean; + theme?: 'light' | 'dark'; } +type Theme = 'light' | 'dark'; + type A2uiMessage = Record & { messageId?: string }; type ResponseMessages = A2uiMessage[]; type ActionMocks = Record< @@ -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; } @@ -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, }; } @@ -269,6 +278,11 @@ export function App() { () => effectiveData.instant === true, [effectiveData.instant], ); + const theme = useMemo( + () => effectiveData.theme ?? 'light', + [effectiveData.theme], + ); + const themeClassName = theme === 'dark' ? 'luna-dark' : 'luna-light'; useEffect(() => { let cancelled = false; @@ -334,8 +348,7 @@ export function App() { return ( {error ? ( @@ -353,7 +366,7 @@ export function App() { : null} {store ? ( - + {c}} + wrapSurface={(c) => {c}} renderEmpty={() => ( Loading... diff --git a/packages/genui/a2ui-playground/lynx-src/a2ui/index.css b/packages/genui/a2ui-playground/lynx-src/a2ui/index.css index 047d6a50a4..11650ebfc4 100644 --- a/packages/genui/a2ui-playground/lynx-src/a2ui/index.css +++ b/packages/genui/a2ui-playground/lynx-src/a2ui/index.css @@ -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); + --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 { + display: flex; + flex-direction: column; padding: 10px; box-sizing: border-box; + background-color: var(--a2ui-color-background); + color: var(--a2ui-color-on-background); } diff --git a/packages/genui/a2ui-playground/lynx-src/a2ui/rspeedy-env.d.ts b/packages/genui/a2ui-playground/lynx-src/a2ui/rspeedy-env.d.ts new file mode 100644 index 0000000000..f593d810e3 --- /dev/null +++ b/packages/genui/a2ui-playground/lynx-src/a2ui/rspeedy-env.d.ts @@ -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. + +/// + +declare module '@lynx-js/types' { + interface GlobalProps { + theme?: 'light' | 'dark'; + } +} + +export {}; diff --git a/packages/genui/a2ui-playground/package.json b/packages/genui/a2ui-playground/package.json index f009a1b720..3b82e93f07 100644 --- a/packages/genui/a2ui-playground/package.json +++ b/packages/genui/a2ui-playground/package.json @@ -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:*", diff --git a/packages/genui/a2ui-playground/src/App.tsx b/packages/genui/a2ui-playground/src/App.tsx index 79af869c1e..9b192922d3 100644 --- a/packages/genui/a2ui-playground/src/App.tsx +++ b/packages/genui/a2ui-playground/src/App.tsx @@ -96,6 +96,7 @@ export function App() { useLayoutEffect(() => { document.documentElement.setAttribute('data-theme', theme); + document.documentElement.style.colorScheme = theme; }, [theme]); useEffect(() => { @@ -142,21 +143,29 @@ export function App() { key='examples-detail' protocol={protocol} demoId={route.demoId} + theme={theme} /> ) - : ; + : ( + + ); case 'components': return ( ); default: return ; } - }, [protocol, route.tab, route.componentName, route.demoId]); + }, [protocol, route.tab, route.componentName, route.demoId, theme]); const protocolVersionControl = (
diff --git a/packages/genui/a2ui-playground/src/pages/ComponentsPage.tsx b/packages/genui/a2ui-playground/src/pages/ComponentsPage.tsx index eb6d1c39df..bc8a42a4b8 100644 --- a/packages/genui/a2ui-playground/src/pages/ComponentsPage.tsx +++ b/packages/genui/a2ui-playground/src/pages/ComponentsPage.tsx @@ -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; const [usageJson, setUsageJson] = useState(() => formatJson(comp.usage[protocol.name]) ); @@ -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 (
@@ -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 @@ -252,7 +261,13 @@ export function ComponentsPage(
{selectedComp - ? + ? ( + + ) : }
); diff --git a/packages/genui/a2ui-playground/src/pages/DemosListPage.tsx b/packages/genui/a2ui-playground/src/pages/DemosListPage.tsx index f8b224cd3a..e31f5b19df 100644 --- a/packages/genui/a2ui-playground/src/pages/DemosListPage.tsx +++ b/packages/genui/a2ui-playground/src/pages/DemosListPage.tsx @@ -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( @@ -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, @@ -56,7 +59,7 @@ export function DemosListPage(props: { protocol: Protocol }) { ), ]), ), - [baseUrl, protocol], + [baseUrl, protocol, theme], ); const handleOpenExample = useCallback( diff --git a/packages/genui/a2ui-playground/src/pages/DemosPage.tsx b/packages/genui/a2ui-playground/src/pages/DemosPage.tsx index fd6a1b54a3..5e1fa1809a 100644 --- a/packages/genui/a2ui-playground/src/pages/DemosPage.tsx +++ b/packages/genui/a2ui-playground/src/pages/DemosPage.tsx @@ -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( @@ -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, }, @@ -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, }, @@ -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. @@ -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); @@ -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(() => { diff --git a/packages/genui/a2ui-playground/src/render.tsx b/packages/genui/a2ui-playground/src/render.tsx index a634c5891c..fffadf3c11 100644 --- a/packages/genui/a2ui-playground/src/render.tsx +++ b/packages/genui/a2ui-playground/src/render.tsx @@ -21,6 +21,7 @@ interface InitData { demoUrl?: string; speed?: number; instant?: boolean; + theme?: 'light' | 'dark'; rawText?: string; rawTextUrl?: string; } @@ -60,6 +61,8 @@ function parseInitDataFromQuery(): InitData | null { const actionMocksUrl = params.get('actionMocksUrl'); const demo = params.get('demo'); const instant = params.get('instant'); + const theme = params.get('theme'); + const rawText = params.get('rawText'); const rawTextUrl = params.get('rawTextUrl'); @@ -85,6 +88,9 @@ function parseInitDataFromQuery(): InitData | null { ? speedVal : undefined, instant: instant === '1' ? true : undefined, + theme: theme === 'dark' + ? 'dark' + : (theme === 'light' ? 'light' : undefined), rawText: rawText ?? undefined, rawTextUrl: rawTextUrl ?? undefined, }; @@ -132,6 +138,7 @@ function buildGlobalPropsFromInitData( } if (initData.speed !== undefined) out.speed = initData.speed; if (initData.instant !== undefined) out.instant = initData.instant; + if (initData.theme !== undefined) out.theme = initData.theme; if (initData.rawText !== undefined) out.rawText = initData.rawText; if (initData.rawTextUrl !== undefined) out.rawTextUrl = initData.rawTextUrl; return Object.keys(out).length > 0 ? out : null; diff --git a/packages/genui/a2ui-playground/src/styles.css b/packages/genui/a2ui-playground/src/styles.css index de35ffb6a1..51893afb44 100644 --- a/packages/genui/a2ui-playground/src/styles.css +++ b/packages/genui/a2ui-playground/src/styles.css @@ -1,5 +1,6 @@ /* ── Geist Design System Tokens ── */ :root { + color-scheme: light; --geist-foreground: #000; --geist-background: #fff; --geist-secondary: #666; @@ -30,7 +31,8 @@ "Liberation Mono", monospace; } -[data-theme="dark"] { +html[data-theme="dark"] { + color-scheme: dark; --geist-foreground: #ededed; --geist-background: #0a0a0a; --geist-secondary: #888; @@ -47,6 +49,10 @@ --geist-shadow-lg: 0 8px 30px rgba(0, 0, 0, 0.4); } +html[data-theme="light"] { + color-scheme: light; +} + /* ── Reset & Base ── */ *, *::before, @@ -325,7 +331,7 @@ a { height: 560px; border-radius: 24px; border: 6px solid var(--geist-border); - background: #fff; + background: var(--geist-background); overflow: hidden; box-shadow: var(--geist-shadow-md); } @@ -830,7 +836,11 @@ a { content: ""; position: absolute; inset: 0; - background: linear-gradient(180deg, rgba(255, 255, 255, 0.25), transparent); + background: linear-gradient( + 180deg, + color-mix(in oklab, var(--geist-background) 82%, white), + transparent + ); pointer-events: none; } @@ -895,7 +905,7 @@ a { padding: 2px 6px; border-radius: 999px; border: 1px solid var(--geist-border); - background: rgba(255, 255, 255, 0.7); + background: var(--geist-surface); color: var(--geist-secondary); font-size: 10px; line-height: 1.2; @@ -920,15 +930,9 @@ a { gap: 8px; min-height: 34px; padding: 8px 14px; - border: 1px solid rgba(0, 0, 0, 0.08); + border: 1px solid var(--geist-border); border-radius: 999px; - background: - linear-gradient( - 180deg, - rgba(255, 255, 255, 0.82), - rgba(255, 255, 255, 0.64) - ), - var(--geist-surface); + background: var(--geist-surface); color: var(--geist-foreground); font-size: 13px; font-weight: 600; @@ -945,13 +949,7 @@ a { .detailBackButton:hover { border-color: var(--geist-border-hover); - background: - linear-gradient( - 180deg, - rgba(255, 255, 255, 0.96), - rgba(255, 255, 255, 0.78) - ), - var(--geist-background); + background: var(--geist-background); transform: translateY(-1px); box-shadow: 0 8px 18px rgba(0, 0, 0, 0.08); } @@ -973,7 +971,11 @@ a { flex-direction: column; overflow: hidden; background: - radial-gradient(circle at top right, rgba(0, 0, 0, 0.04), transparent 18%), + radial-gradient( + circle at top right, + var(--a2ui-color-overlay), + transparent 18% + ), var(--geist-background); } diff --git a/packages/genui/a2ui-playground/src/utils/renderUrl.ts b/packages/genui/a2ui-playground/src/utils/renderUrl.ts index 324f05e93a..aa90fa4cb5 100644 --- a/packages/genui/a2ui-playground/src/utils/renderUrl.ts +++ b/packages/genui/a2ui-playground/src/utils/renderUrl.ts @@ -9,6 +9,8 @@ export interface RenderInit { demoUrl: string; messages: unknown; actionMocks?: unknown; + /** Theme forwarded to the preview runtime. */ + theme?: 'light' | 'dark'; /** When set, use a short `?demo=` param instead of inlining the payload. */ demoId?: string; /** Simulation speed multiplier (e.g. 0.5, 1, 2, 4). */ @@ -21,6 +23,9 @@ export function buildRenderUrl(init: RenderInit, baseUrl: string): string { const url = new URL('render.html', baseUrl); url.searchParams.set('protocol', init.protocol.name); url.searchParams.set('demoUrl', init.demoUrl); + if (init.theme) { + url.searchParams.set('theme', init.theme); + } if (init.demoId) { // Known demo: reference static JSON file by ID instead of inlining payload. diff --git a/packages/genui/a2ui/src/catalog/Button/index.tsx b/packages/genui/a2ui/src/catalog/Button/index.tsx old mode 100755 new mode 100644 index 8a7cf25c84..a424292f64 --- a/packages/genui/a2ui/src/catalog/Button/index.tsx +++ b/packages/genui/a2ui/src/catalog/Button/index.tsx @@ -25,7 +25,7 @@ export interface ButtonProps extends GenericComponentProps { export function Button( props: ButtonProps, ): import('@lynx-js/react').ReactNode { - const { action, child, surface, sendAction } = props; + const { action, child, surface, sendAction, variant = 'primary' } = props; const handleClick = () => { if (action) { @@ -38,7 +38,10 @@ export function Button( : undefined; return ( - + {childResource ? : Button} diff --git a/packages/genui/a2ui/src/catalog/Card/index.tsx b/packages/genui/a2ui/src/catalog/Card/index.tsx old mode 100755 new mode 100644 index 4c5ee14863..169495244d --- a/packages/genui/a2ui/src/catalog/Card/index.tsx +++ b/packages/genui/a2ui/src/catalog/Card/index.tsx @@ -11,22 +11,25 @@ import '../../../styles/catalog/Card.css'; */ export interface CardProps extends GenericComponentProps { child: string; + variant?: 'elevated' | 'outlined' | 'filled' | 'ghost'; + weight?: number; } export function Card(props: CardProps): import('@lynx-js/react').ReactNode { const { child: childId, surface, dataContextPath } = props; const childComponent = surface.components.get(childId); const childWithContext = childComponent && dataContextPath - ? { ...childComponent, dataContextPath: dataContextPath } + ? { ...childComponent, dataContextPath } : childComponent; + const variant = props.variant ?? 'elevated'; + const weightClass = typeof props.weight === 'number' + ? 'card-weighted' + : ''; return ( - + {childWithContext && ( - + )} ); diff --git a/packages/genui/a2ui/src/catalog/CheckBox/index.tsx b/packages/genui/a2ui/src/catalog/CheckBox/index.tsx old mode 100755 new mode 100644 index da22c28655..73ada4ae33 --- a/packages/genui/a2ui/src/catalog/CheckBox/index.tsx +++ b/packages/genui/a2ui/src/catalog/CheckBox/index.tsx @@ -24,7 +24,10 @@ export function CheckBox( return ( - + {!!value && } {label as string} diff --git a/packages/genui/a2ui/src/catalog/Column/index.tsx b/packages/genui/a2ui/src/catalog/Column/index.tsx index a235a396a2..58bd723690 100644 --- a/packages/genui/a2ui/src/catalog/Column/index.tsx +++ b/packages/genui/a2ui/src/catalog/Column/index.tsx @@ -34,7 +34,9 @@ export function Column( const explicitChildren = Array.isArray(children) ? children : []; return ( - + {explicitChildren.map((childId: string) => { const child = surface.components.get(childId); if (!child) return null; @@ -46,7 +48,7 @@ export function Column( return ( undefined | (() => void), - deps?: readonly unknown[], -) => void; - /** * @a2uiCatalog Image */ @@ -19,6 +14,7 @@ export interface ImageProps extends GenericComponentProps { /** Image URL or path binding. */ url: string | { path: string }; fit?: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down'; + mode?: 'scaleToFill' | 'aspectFit' | 'aspectFill' | 'center'; variant?: | 'icon' | 'avatar' @@ -26,29 +22,45 @@ export interface ImageProps extends GenericComponentProps { | 'mediumFeature' | 'largeFeature' | 'header'; + weight?: number; } +const fallbackImage = + 'https://lf3-static.bytednsdoc.com/obj/eden-cn/zalzzh-ukj-lapzild-shpjpmmv-eufs/ljhwZthlaukjlkulzlp/built-in-images/logo.png'; + export function Image( props: ImageProps, ): import('@lynx-js/react').ReactNode { - const { id, url } = props; + const src = props.url; + const fit = props.fit ?? 'fit'; + const mode = props.mode ?? (() => { + switch (fit) { + case 'contain': + case 'scale-down': + return 'aspectFit'; + case 'fill': + return 'scaleToFill'; + case 'none': + return 'center'; + default: + return 'aspectFill'; + } + })(); const [hasError, setHasError] = useState(false); - useLynxEffect(() => { + useEffect(() => { setHasError(false); - return undefined; - }, [url]); - - const finalSrc = hasError - ? 'https://lf3-static.bytednsdoc.com/obj/eden-cn/zalzzh-ukj-lapzild-shpjpmmv-eufs/ljhwZthlaukjlkulzlp/built-in-images/logo.png' - : url; + }, [src]); return ( setHasError(true)} /> ); diff --git a/packages/genui/a2ui/src/catalog/List/index.tsx b/packages/genui/a2ui/src/catalog/List/index.tsx index 7eb0425737..e458b76285 100755 --- a/packages/genui/a2ui/src/catalog/List/index.tsx +++ b/packages/genui/a2ui/src/catalog/List/index.tsx @@ -22,7 +22,13 @@ export interface ListProps extends GenericComponentProps { export function List( props: ListProps, ): import('@lynx-js/react').ReactNode { - const { children, surface, dataContextPath, direction = 'vertical' } = props; + const { + children, + surface, + dataContextPath, + direction = 'vertical', + align = 'stretch', + } = props; interface ListItem { key: string; @@ -80,7 +86,7 @@ export function List( return ( + {explicitChildren.map((childId: string) => { const child = surface.components.get(childId); if (!child) return null; @@ -44,7 +46,7 @@ export function Row(props: RowProps): import('@lynx-js/react').ReactNode { return ( = 2) { + weightClass = 'text-weight-2'; + } else if (weight >= 1.5) { + weightClass = 'text-weight-1-5'; + } else { + weightClass = 'text-weight-1'; + } + } return ( - + {text as string} ); diff --git a/packages/genui/a2ui/src/css.d.ts b/packages/genui/a2ui/src/css.d.ts new file mode 100644 index 0000000000..6c89ef3255 --- /dev/null +++ b/packages/genui/a2ui/src/css.d.ts @@ -0,0 +1,4 @@ +// 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. +declare module '*.css'; diff --git a/packages/genui/a2ui/styles/catalog/Button.css b/packages/genui/a2ui/styles/catalog/Button.css index 646ea3ded4..d666bfc13c 100644 --- a/packages/genui/a2ui/styles/catalog/Button.css +++ b/packages/genui/a2ui/styles/catalog/Button.css @@ -1,24 +1,46 @@ -@import "./luna.css"; +@import "../theme.css"; .button { - display: flex; + display: inline-flex; justify-content: center; align-items: center; min-width: 0; - padding-left: 12px; - padding-right: 12px; - padding-top: 8px; - padding-bottom: 8px; - margin: 4px; - border-radius: 9999px; - background-color: var(--primary); + gap: var(--a2ui-spacing-m); + padding: var(--a2ui-spacing-m) calc(var(--a2ui-spacing-m) * 1.75); + margin: calc(var(--a2ui-spacing-xs) * 2) 0; + border-radius: var(--a2ui-border-radius); + background-color: var(--a2ui-color-primary); + color: var(--a2ui-color-on-primary); + transition: + transform 0.12s ease, + opacity 0.12s ease, + background-color 0.12s ease; } .button.ui-active { - background-color: var(--primary-2); + background-color: var(--a2ui-color-primary-hover); } -.button-text { - font-size: 12px; - color: var(--primary-content); +.button-primary { + background-color: var(--a2ui-color-primary); +} + +.button-borderless { + background-color: transparent; + padding-left: 0; + padding-right: 0; + color: var(--a2ui-color-primary); +} + +.button .text-body, +.button .text-caption, +.button .text-h1, +.button .text-h2, +.button .text-h3, +.button .text-h4, +.button .text-h5, +.button .text-price, +.button .text-link, +.button .text-label { + color: inherit; } diff --git a/packages/genui/a2ui/styles/catalog/Card.css b/packages/genui/a2ui/styles/catalog/Card.css index 9276db4cd1..21a5318948 100644 --- a/packages/genui/a2ui/styles/catalog/Card.css +++ b/packages/genui/a2ui/styles/catalog/Card.css @@ -1,36 +1,37 @@ -@import "./luna.css"; +@import "../theme.css"; .card { display: flex; flex-direction: column; - min-height: 0; width: 100%; box-sizing: border-box; flex-shrink: 0; - height: fit-content; + min-width: 0; + min-height: 0; + gap: var(--a2ui-spacing-l); } /* Elevated (Default) */ .card-elevated { - background-color: var(--paper); - border-radius: 12px; - box-shadow: 0 2px 8px var(--backdrop); - padding: 16px; + background-color: var(--a2ui-color-surface); + border-radius: var(--a2ui-border-radius); + box-shadow: 0 8px 24px var(--a2ui-color-overlay); + padding: var(--a2ui-spacing-l); } /* Outlined */ .card-outlined { - background-color: var(--canvas); - border: 1px solid var(--line); - border-radius: 12px; - padding: 16px; + background-color: var(--a2ui-color-background); + border: var(--a2ui-border); + border-radius: var(--a2ui-border-radius); + padding: var(--a2ui-spacing-l); } /* Filled */ .card-filled { - background-color: var(--neutral-film); - border-radius: 12px; - padding: 16px; + background-color: var(--a2ui-color-surface); + border-radius: var(--a2ui-border-radius); + padding: var(--a2ui-spacing-l); } /* Ghost */ @@ -38,3 +39,7 @@ background-color: transparent; padding: 0; } + +.card-weighted { + width: auto; +} diff --git a/packages/genui/a2ui/styles/catalog/CheckBox.css b/packages/genui/a2ui/styles/catalog/CheckBox.css index 8935116bfb..4f0a28bd48 100644 --- a/packages/genui/a2ui/styles/catalog/CheckBox.css +++ b/packages/genui/a2ui/styles/catalog/CheckBox.css @@ -1,24 +1,35 @@ -@import "./luna.css"; +@import "../theme.css"; .checkbox-row { display: flex; flex-direction: row; align-items: center; - justify-content: start; - gap: 10px; + justify-content: flex-start; + gap: 12px; width: 100%; + min-width: 0; } .checkbox-input { + display: flex; + align-items: center; + justify-content: center; width: 20px; height: 20px; border-radius: 4px; - border-width: 1.5px; - border-color: var(--line); - background-color: var(--canvas); + border: 1.5px solid var(--a2ui-color-border); + background-color: var(--a2ui-color-background); + color: var(--a2ui-color-on-primary); + flex-shrink: 0; +} + +.checkbox-input-checked { + background-color: var(--a2ui-color-primary); + border-color: var(--a2ui-color-primary); } .checkbox-label { - font-size: 16px; - color: var(--content-2); + font-size: 14px; + line-height: 1.5; + color: var(--a2ui-color-on-background); } diff --git a/packages/genui/a2ui/styles/catalog/Column.css b/packages/genui/a2ui/styles/catalog/Column.css index 6edac4feb1..64bb21856c 100644 --- a/packages/genui/a2ui/styles/catalog/Column.css +++ b/packages/genui/a2ui/styles/catalog/Column.css @@ -1,8 +1,15 @@ -@import "./luna.css"; +@import "../theme.css"; .column { display: flex; flex-direction: column; + gap: var(--a2ui-spacing-l); + width: 100%; + min-width: 0; +} + +.column-weighted-item { + min-height: 0; } /* Alignment */ @@ -51,32 +58,32 @@ /* Section: Large sections of content */ .col-hint-section { - padding: 24px; - gap: 24px; + padding: var(--a2ui-spacing-xl); + gap: var(--a2ui-spacing-xl); } /* Group: Standard logical grouping */ .col-hint-group { - gap: 16px; + gap: var(--a2ui-spacing-l); } /* Compact: Tighter grouping */ .col-hint-compact { - gap: 8px; + gap: var(--a2ui-spacing-m); } /* Screen: Full screen container */ .col-hint-screen { width: 100%; height: 100%; - background-color: var(--canvas); + background-color: var(--a2ui-color-background); } /* Root Column: Default root container */ .col-hint-root-column { width: 100%; min-height: 100%; - padding: 16px; - gap: 16px; - background-color: var(--canvas-ambient); + padding: var(--a2ui-spacing-l); + gap: var(--a2ui-spacing-l); + background-color: var(--a2ui-color-background-ambient); } diff --git a/packages/genui/a2ui/styles/catalog/Divider.css b/packages/genui/a2ui/styles/catalog/Divider.css index 481d754146..0b040cd499 100644 --- a/packages/genui/a2ui/styles/catalog/Divider.css +++ b/packages/genui/a2ui/styles/catalog/Divider.css @@ -1,10 +1,10 @@ -@import "./luna.css"; +@import "../theme.css"; .divider { display: block; min-height: 0; - overflow: auto; - background-color: var(--rule); + overflow: hidden; + background-color: var(--a2ui-color-border-strong); border: none; } diff --git a/packages/genui/a2ui/styles/catalog/Image.css b/packages/genui/a2ui/styles/catalog/Image.css index 27c61d5ede..c2e11c0281 100644 --- a/packages/genui/a2ui/styles/catalog/Image.css +++ b/packages/genui/a2ui/styles/catalog/Image.css @@ -1,40 +1,51 @@ -.icon { +@import "../theme.css"; + +.image-variant-icon { width: 24px; height: 24px; + border-radius: 0; } -.avatar { +.image-variant-avatar { width: 40px; height: 40px; border-radius: 50%; } -.smallFeature { +.image-variant-smallFeature { width: 120px; height: 80px; + max-width: var(--a2ui-image-small-feature-size, 120px); border-radius: 8px; } -.mediumFeature { +.image-variant-mediumFeature { width: 240px; height: 160px; border-radius: 8px; } -.largeFeature { +.image-variant-largeFeature { width: 100%; height: 180px; border-radius: 12px; + max-height: var(--a2ui-image-large-feature-size, 400px); } -.header { +.image-variant-header { width: 100%; height: 200px; + border-radius: 12px; } .a2ui-image { flex-shrink: 0; padding: 8px; + overflow: hidden; +} + +.image-weighted { + flex: 1 1 0; } .row-weighted-item .a2ui-image, diff --git a/packages/genui/a2ui/styles/catalog/List.css b/packages/genui/a2ui/styles/catalog/List.css index 492fa3e05b..fea9f7ed2b 100644 --- a/packages/genui/a2ui/styles/catalog/List.css +++ b/packages/genui/a2ui/styles/catalog/List.css @@ -1,4 +1,38 @@ +@import "../theme.css"; + .list { width: 100%; height: 400px; + display: flex; + flex-direction: column; + gap: calc(var(--a2ui-spacing-m) * 1.5); + min-width: 0; + padding: 0; +} + +.list.list-horizontal { + flex-direction: row; + align-items: center; + overflow-x: auto; +} + +.list.list-vertical { + flex-direction: column; + overflow-y: auto; +} + +.list-align-start { + align-items: flex-start; +} + +.list-align-center { + align-items: center; +} + +.list-align-end { + align-items: flex-end; +} + +.list-align-stretch { + align-items: stretch; } diff --git a/packages/genui/a2ui/styles/catalog/RadioGroup.css b/packages/genui/a2ui/styles/catalog/RadioGroup.css index b4657669f9..2fee667c77 100644 --- a/packages/genui/a2ui/styles/catalog/RadioGroup.css +++ b/packages/genui/a2ui/styles/catalog/RadioGroup.css @@ -1,4 +1,4 @@ -@import "./luna.css"; +@import "../theme.css"; /* ========================= * Radio Group @@ -8,12 +8,13 @@ display: flex; flex-direction: column; width: 100%; + min-width: 0; } .radio-group-container { display: flex; flex-direction: column; - gap: 16px; + gap: 14px; } /* Usage Hints */ @@ -35,13 +36,13 @@ /* Card: Options look like cards */ .radio-group-card .radio-option { padding: 16px; - border: 1px solid var(--line); + border: var(--a2ui-border); border-radius: 8px; - background-color: var(--paper); + background-color: var(--a2ui-color-surface); } .radio-group-card .radio-option:active { - background-color: var(--neutral-film); + background-color: var(--a2ui-color-surface-muted); } /* ========================= @@ -54,11 +55,12 @@ align-items: center; gap: 12px; cursor: pointer; + min-width: 0; } .label { - font-size: 16px; - color: var(--content); + font-size: 14px; + color: var(--a2ui-color-on-surface); line-height: 1.5; } @@ -70,9 +72,7 @@ width: 20px; height: 20px; border-radius: 50%; - border-width: 1.5px; - border-style: solid; - border-color: var(--line); + border: 1.5px solid var(--a2ui-color-border); display: flex; align-items: center; justify-content: center; @@ -85,12 +85,12 @@ } .radio-item.ui-checked { - border-color: var(--primary); - background-color: var(--primary); + border-color: var(--a2ui-color-primary); + background-color: var(--a2ui-color-primary); } .radio-item.ui-active { - border-color: var(--primary-2); + border-color: var(--a2ui-color-primary-hover); } /* Indicator Dot */ @@ -106,5 +106,5 @@ width: 8px; height: 8px; border-radius: 50%; - background-color: var(--primary-content); + background-color: var(--a2ui-color-on-primary); } diff --git a/packages/genui/a2ui/styles/catalog/Row.css b/packages/genui/a2ui/styles/catalog/Row.css index 16d63a4c59..893be54922 100644 --- a/packages/genui/a2ui/styles/catalog/Row.css +++ b/packages/genui/a2ui/styles/catalog/Row.css @@ -1,9 +1,16 @@ -@import "./luna.css"; +@import "../theme.css"; .row { display: flex; flex-direction: row; - flex-wrap: wrap; /* Allow wrapping */ + flex-wrap: nowrap; + gap: var(--a2ui-spacing-l); + width: 100%; + min-width: 0; +} + +.row-weighted-item { + min-width: 0; } /* Alignment */ @@ -52,12 +59,12 @@ /* Group: Standard horizontal grouping */ .row-hint-group { - gap: 16px; + gap: var(--a2ui-spacing-l); } /* Compact: Tight grouping (e.g., icon + text) */ .row-hint-compact { - gap: 8px; + gap: var(--a2ui-spacing-m); } /* Balanced: Good for space-between layouts */ @@ -69,8 +76,8 @@ .row-hint-toolbar { width: 100%; height: 56px; - padding: 0 16px; + padding: 0 var(--a2ui-spacing-l); align-items: center; - background-color: var(--paper); - border-bottom: 1px solid var(--line); + background-color: var(--a2ui-color-surface); + border-bottom: var(--a2ui-border); } diff --git a/packages/genui/a2ui/styles/catalog/Text.css b/packages/genui/a2ui/styles/catalog/Text.css index d3430fb568..b218a63921 100644 --- a/packages/genui/a2ui/styles/catalog/Text.css +++ b/packages/genui/a2ui/styles/catalog/Text.css @@ -1,86 +1,110 @@ -@import "./luna.css"; +@import "../theme.css"; + +.text-body, +.text-caption, +.text-h1, +.text-h2, +.text-h3, +.text-h4, +.text-h5, +.text-price, +.text-link, +.text-label { + flex-shrink: 0; +} .text-h1 { - font-size: 24px; + font-size: var(--a2ui-font-size-2xl); font-weight: bold; - color: var(--content); - line-height: 1.2; + color: var(--a2ui-color-on-surface); + line-height: var(--a2ui-line-height-headings); flex-shrink: 0; } .text-h2 { - font-size: 20px; + font-size: var(--a2ui-font-size-xl); font-weight: bold; - color: var(--content); - line-height: 1.3; + color: var(--a2ui-color-on-surface); + line-height: var(--a2ui-line-height-headings); flex-shrink: 0; } .text-h3 { - font-size: 18px; + font-size: var(--a2ui-font-size-l); font-weight: bold; - color: var(--content); - line-height: 1.3; + color: var(--a2ui-color-on-surface); + line-height: var(--a2ui-line-height-headings); flex-shrink: 0; } .text-h4 { - font-size: 16px; + font-size: var(--a2ui-font-size-m); font-weight: bold; - color: var(--content); - line-height: 1.4; + color: var(--a2ui-color-on-surface); + line-height: var(--a2ui-line-height-headings); flex-shrink: 0; } .text-h5 { - font-size: 14px; + font-size: var(--a2ui-font-size-s); font-weight: bold; - color: var(--content); - line-height: 1.4; + color: var(--a2ui-color-on-surface); + line-height: var(--a2ui-line-height-headings); flex-shrink: 0; } .text-caption { - font-size: 12px; - color: var(--content-muted); - line-height: 1.4; + font-size: var(--a2ui-font-size-xs); + color: var(--a2ui-color-text-muted); + line-height: var(--a2ui-line-height-body); flex-shrink: 0; } .text-body { - flex-shrink: 0; - font-size: 14px; + font-size: var(--a2ui-font-size-m); font-weight: normal; - color: var(--content); - line-height: 1.5; + color: var(--a2ui-color-on-surface); + line-height: var(--a2ui-line-height-body); +} + +.text-weight-1 { + font-weight: 500; +} + +.text-weight-1-5 { + font-weight: 600; +} + +.text-weight-2 { + font-weight: 700; } /* New Usage Hints */ .text-price { flex-shrink: 0; - font-size: 16px; + font-size: var(--a2ui-font-size-m); font-weight: bold; - color: #ff4d4f; /* Typical price color, or use variable if available */ - line-height: 1.4; + color: var(--a2ui-color-primary); + line-height: var(--a2ui-line-height-body); } .text-link { flex-shrink: 0; - font-size: 14px; - color: #1890ff; /* Link blue */ + font-size: var(--a2ui-font-size-m); + color: var(--a2ui-color-primary-hover); text-decoration: underline; - line-height: 1.5; + line-height: var(--a2ui-line-height-body); } .text-label { flex-shrink: 0; - font-size: 10px; + font-size: var(--a2ui-font-size-xs); font-weight: bold; - color: var(--content-muted-2); + color: var(--a2ui-color-text-subtle); text-transform: uppercase; letter-spacing: 0.5px; - line-height: 1.4; + line-height: var(--a2ui-line-height-body); } .button .text-body, @@ -93,5 +117,5 @@ .button .text-price, .button .text-link, .button .text-label { - color: var(--primary-content); + color: var(--a2ui-color-on-primary); } diff --git a/packages/genui/a2ui/styles/catalog/luna-dark.css b/packages/genui/a2ui/styles/catalog/luna-dark.css deleted file mode 100644 index 7376a27e56..0000000000 --- a/packages/genui/a2ui/styles/catalog/luna-dark.css +++ /dev/null @@ -1,37 +0,0 @@ -.luna-dark { - --canvas: #0d0d0d; - --canvas-ambient: #000000; - --backdrop: rgba(0, 0, 0, 0.7); - --content: #f8f8f8; - --content-2: #d9d9d9; - --content-muted: #b3b3b3; - --content-muted-2: #959595; - --content-subtle: #6e6e6e; - --content-faint: #4f4f4f; - --paper: #1a1a1a; - --paper-clear: #232323; - --primary: #e0e0e0; - --primary-2: #a4a4a4; - --primary-muted: #7a7a7a; - --primary-content: #010101; - --primary-content-faded: rgba(0, 0, 0, 0.3); - --secondary: #5b5b5b; - --secondary-2: #333333; - --secondary-content: #e0e0e0; - --neutral: #f8f8f8; - --neutral-2: #d0d0d0; - --neutral-subtle: #656565; - --neutral-faint: #505050; - --neutral-ambient: #2c2c2c; - --neutral-content: #010101; - --neutral-veil: rgba(248, 248, 248, 0.5); - --neutral-film: rgba(248, 248, 248, 0.08); - --line: rgba(248, 248, 248, 0.32); - --rule: #333333; - --gradient-a: #a7a7a7; - --gradient-b: #a5a5a5; - --gradient-c: #a1a1a1; - --gradient-d: #bababa; - --gradient-content: #ffffff; - --gradient-content-faded: rgba(255, 255, 255, 0.68); -} diff --git a/packages/genui/a2ui/styles/catalog/luna-light.css b/packages/genui/a2ui/styles/catalog/luna-light.css deleted file mode 100644 index 31f3e63486..0000000000 --- a/packages/genui/a2ui/styles/catalog/luna-light.css +++ /dev/null @@ -1,37 +0,0 @@ -.luna-light { - --canvas: #ffffff; - --canvas-ambient: #f4f4f4; - --backdrop: rgba(0, 0, 0, 0.1); - --content: #010101; - --content-2: #333333; - --content-muted: #5d5d5d; - --content-muted-2: #707070; - --content-subtle: #9e9e9e; - --content-faint: #d0d0d0; - --paper: #f8f8f8; - --paper-clear: #ffffff; - --primary: #1a1a1a; - --primary-2: #545454; - --primary-muted: #9b9b9b; - --primary-content: #fafafa; - --primary-content-faded: rgba(255, 255, 255, 0.4); - --secondary: #c0c0c0; - --secondary-2: #d9d9d9; - --secondary-content: #3c3c3c; - --neutral: #010101; - --neutral-2: #333333; - --neutral-subtle: #b3b3b3; - --neutral-faint: #d9d9d9; - --neutral-ambient: #eeeeee; - --neutral-content: #f8f8f8; - --neutral-veil: rgba(0, 0, 0, 0.24); - --neutral-film: rgba(0, 0, 0, 0.05); - --line: rgba(0, 0, 0, 0.17); - --rule: #e5e5e5; - --gradient-a: #939393; - --gradient-b: #a0a0a0; - --gradient-c: #a1a1a1; - --gradient-d: #bfbfbf; - --gradient-content: #ffffff; - --gradient-content-faded: rgba(255, 255, 255, 0.68); -} diff --git a/packages/genui/a2ui/styles/catalog/luna.css b/packages/genui/a2ui/styles/catalog/luna.css deleted file mode 100644 index 0d5f51f4ce..0000000000 --- a/packages/genui/a2ui/styles/catalog/luna.css +++ /dev/null @@ -1,4 +0,0 @@ -@import "./luna-dark.css"; -@import "./luna-light.css"; -@import "./lunaris-dark.css"; -@import "./lunaris-light.css"; diff --git a/packages/genui/a2ui/styles/catalog/lunaris-dark.css b/packages/genui/a2ui/styles/catalog/lunaris-dark.css deleted file mode 100644 index 667ff311d9..0000000000 --- a/packages/genui/a2ui/styles/catalog/lunaris-dark.css +++ /dev/null @@ -1,37 +0,0 @@ -.lunaris-dark { - --canvas: #0d0d0d; - --canvas-ambient: #000000; - --backdrop: rgba(0, 0, 0, 0.7); - --content: #f8f8f8; - --content-2: #d9d9d9; - --content-muted: #b3b3b3; - --content-muted-2: #959595; - --content-subtle: #6e6e6e; - --content-faint: #4f4f4f; - --paper: #1a1a1a; - --paper-clear: #232323; - --primary: #ff8ab5; - --primary-2: #ff4f8f; - --primary-muted: #d04377; - --primary-content: #010101; - --primary-content-faded: rgba(0, 0, 0, 0.3); - --secondary: #534363; - --secondary-2: #362e46; - --secondary-content: #eee5f6; - --neutral: #f8f8f8; - --neutral-2: #d0d0d0; - --neutral-subtle: #656565; - --neutral-faint: #505050; - --neutral-ambient: #2c2c2c; - --neutral-content: #010101; - --neutral-veil: rgba(248, 248, 248, 0.5); - --neutral-film: rgba(248, 248, 248, 0.08); - --line: rgba(248, 248, 248, 0.32); - --rule: #333333; - --gradient-a: #ff7385; - --gradient-b: #fe69a1; - --gradient-c: #a998bf; - --gradient-d: #00d0f1; - --gradient-content: #ffffff; - --gradient-content-faded: rgba(255, 255, 255, 0.68); -} diff --git a/packages/genui/a2ui/styles/catalog/lunaris-light.css b/packages/genui/a2ui/styles/catalog/lunaris-light.css deleted file mode 100644 index 73b3dd34c5..0000000000 --- a/packages/genui/a2ui/styles/catalog/lunaris-light.css +++ /dev/null @@ -1,37 +0,0 @@ -.lunaris-light { - --canvas: #ffffff; - --canvas-ambient: #f4f4f4; - --backdrop: rgba(0, 0, 0, 0.1); - --content: #010101; - --content-2: #333333; - --content-muted: #5d5d5d; - --content-muted-2: #707070; - --content-subtle: #9e9e9e; - --content-faint: #d0d0d0; - --paper: #f8f8f8; - --paper-clear: #ffffff; - --primary: #ff1a6e; - --primary-2: #ff558e; - --primary-muted: #f992b1; - --primary-content: #ffffff; - --primary-content-faded: rgba(255, 255, 255, 0.4); - --secondary: #fbcfdc; - --secondary-2: #fce5ed; - --secondary-content: #a92d5a; - --neutral: #010101; - --neutral-2: #333333; - --neutral-subtle: #b3b3b3; - --neutral-faint: #d9d9d9; - --neutral-ambient: #eeeeee; - --neutral-content: #f8f8f8; - --neutral-veil: rgba(0, 0, 0, 0.24); - --neutral-film: rgba(0, 0, 0, 0.05); - --line: rgba(0, 0, 0, 0.17); - --rule: #e5e5e5; - --gradient-a: #ff3d63; - --gradient-b: #ff5d99; - --gradient-c: #a998bf; - --gradient-d: #3dd5e9; - --gradient-content: #ffffff; - --gradient-content-faded: rgba(255, 255, 255, 0.68); -} diff --git a/packages/genui/a2ui/styles/theme.css b/packages/genui/a2ui/styles/theme.css new file mode 100644 index 0000000000..945aa4ae83 --- /dev/null +++ b/packages/genui/a2ui/styles/theme.css @@ -0,0 +1,92 @@ +:root { + --a2ui-color-background: light-dark(#eee, #111); + --a2ui-color-on-background: light-dark(#333, #eee); + + --a2ui-color-surface: light-dark( + color-mix(in oklab, var(--a2ui-color-background) 85%, white), + color-mix(in oklab, var(--a2ui-color-background) 95%, white) + ); + --a2ui-color-on-surface: light-dark(#333, #eee); + + --a2ui-color-primary: #17e; + --a2ui-color-primary-light: color-mix( + in oklab, + var(--a2ui-color-primary) 85%, + white + ); + --a2ui-color-primary-dark: color-mix( + in oklab, + var(--a2ui-color-primary) 85%, + black + ); + --a2ui-color-primary-hover: light-dark( + var(--a2ui-color-primary-dark), + var(--a2ui-color-primary-light) + ); + --a2ui-color-on-primary: #fff; + + --a2ui-color-secondary: light-dark(#ddd, #333); + --a2ui-color-secondary-light: color-mix( + in oklab, + var(--a2ui-color-secondary) 85%, + white + ); + --a2ui-color-secondary-dark: color-mix( + in oklab, + var(--a2ui-color-secondary) 95%, + black + ); + --a2ui-color-secondary-hover: light-dark( + var(--a2ui-color-secondary-dark), + var(--a2ui-color-secondary-light) + ); + --a2ui-color-on-secondary: light-dark(#333, #eee); + + --a2ui-border-radius: 0.25rem; + --a2ui-color-border: light-dark(#ccc, #444); + --a2ui-border-width: 1px; + --a2ui-border: 1px solid var(--a2ui-color-border, #ccc); + + --a2ui-font-family-title: inherit; + --a2ui-font-family-monospace: monospace; + --a2ui-color-input: light-dark(#fff, #2a2a2a); + --a2ui-color-on-input: light-dark(#333, #eee); + + --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: light-dark(#ddd, #000); + --a2ui-color-surface-muted: light-dark( + color-mix(in oklab, var(--a2ui-color-background) 90%, black), + color-mix(in oklab, var(--a2ui-color-background) 90%, white) + ); + --a2ui-color-surface-strong: light-dark(#fff, #232323); + --a2ui-color-border-strong: light-dark(#e5e5e5, #333); + --a2ui-color-text: var(--a2ui-color-on-background); + --a2ui-color-text-muted: light-dark(#5d5d5d, #b3b3b3); + --a2ui-color-text-subtle: light-dark(#9e9e9e, #6e6e6e); + --a2ui-color-primary-content-muted: light-dark( + rgba(255, 255, 255, 0.4), + rgba(0, 0, 0, 0.3) + ); + --a2ui-color-overlay: light-dark( + rgba(0, 0, 0, 0.05), + rgba(248, 248, 248, 0.08) + ); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dbdca21b4a..ad8531f3e9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -626,6 +626,9 @@ importers: '@lynx-js/a2ui-reactlynx': specifier: workspace:* version: link:../a2ui + '@lynx-js/luna-styles': + specifier: 0.1.0 + version: 0.1.0 '@lynx-js/lynx-core': specifier: 0.1.3 version: 0.1.3 @@ -3724,6 +3727,9 @@ packages: '@lynx-js/internal-preact@10.29.1-20260424024911-12b794f': resolution: {integrity: sha512-MQ+xjPL2f1P9/eCAdkT2h9cJRXl1qqhSJDX9GkPETt3UAepLo5N+HbGB8Qr3IUzqGDWMr3Mj76my1P0pLkKVDg==} + '@lynx-js/luna-styles@0.1.0': + resolution: {integrity: sha512-1DVx/mLmsnfdrTF1E9CWhl39/CWmR3nal38nY6QJ6sOImSC77HFDuE4Xr1g0V0hcX/8lAmTzgNwnqPEJE1a6JQ==} + '@lynx-js/lynx-core@0.1.3': resolution: {integrity: sha512-uWzKKYJUK4Q09ZRZxWSAFINnmZb9piWPbvWF9SkLn+3snBl9u/BJa4ekPRcKWAhBmpbtxWH1x27fxe3Q3p5l3Q==} @@ -12816,6 +12822,8 @@ snapshots: '@lynx-js/internal-preact@10.29.1-20260424024911-12b794f': {} + '@lynx-js/luna-styles@0.1.0': {} + '@lynx-js/lynx-core@0.1.3': {} '@lynx-js/lynx-ui-button@3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.5))(react@19.2.5)':