Files
paseo/packages/app/test-stubs/react-native-unistyles.ts
T
Mohamed Boudra d165b07682 fix(plugins): composer commands run on current state, report failures, and reach the overflow sheet
Draft commands read the state captured at render, so a plugin's
`await setProvider(); await setModel()` acted on a stale provider. The draft
form and options are now plain stores that commands read live, and
`setProvider`/`setModel` reject an unavailable provider or a model the loaded
catalog does not list. Draft `options` include the plugin's pending edits.

Shared composer controls own the pending state and show a rejected command as
an error toast, like the built-in controls. Callback payloads are objects
(`{ value }`, `onPress(event)`) so they can grow without breaking plugins.

The overflow sheet's content portals to the app root and lost the composer's
agent, input, toast and query contexts, so plugin items drew nothing in it.
`useComposerSheetBridge` carries them across.
2026-11-05 21:55:10 +01:00

155 lines
4.1 KiB
TypeScript

import { lightHighlightColors } from "@getpaseo/highlight";
const testTheme = {
colorScheme: "light",
colors: {
syntax: lightHighlightColors,
foreground: "#111111",
foregroundMuted: "#666666",
statusSuccess: "#15803d",
statusDanger: "#b91c1c",
statusWarning: "#d97706",
statusMerged: "#7c3aed",
// Same rule as the real theme: the status color at 12%.
statusSuccessTint: "#15803d1f",
statusDangerTint: "#b91c1c1f",
statusWarningTint: "#d977061f",
// The light band's values, so a test can name the colour it expects.
statusDotSuccess: "#299f51",
statusDotDanger: "#f12e2f",
statusDotWarning: "#b37824",
statusDotRunning: "#268ae0",
accent: "#2563eb",
accentForeground: "#ffffff",
destructive: "#dc2626",
destructiveForeground: "#ffffff",
surface1: "#fafafa",
surface2: "#f4f4f5",
surface3: "#e4e4e7",
border: "#e4e4e7",
borderAccent: "#a1a1aa",
palette: {
amber: { 500: "#f59e0b" },
blue: { 300: "#93c5fd", 400: "#60a5fa" },
green: { 400: "#4ade80", 500: "#22c55e" },
yellow: { 400: "#facc15" },
zinc: { 600: "#52525b" },
red: { 300: "#fca5a5" },
white: "#ffffff",
},
},
borderWidth: { 1: 1 },
spacing: [0, 4, 8, 12, 16, 20, 24, 28, 32],
fontSize: {
xs: 12,
sm: 14,
base: 16,
},
fontFamily: {
ui: "sans-serif",
mono: "monospace",
},
fontWeight: {
normal: "400",
medium: "500",
},
borderRadius: {
base: 4,
md: 6,
lg: 8,
xl: 12,
full: 9999,
},
iconSize: { sm: 16, md: 20 },
opacity: { 50: 0.5 },
shadow: {
sm: {
shadowColor: "rgba(0, 0, 0, 0.02)",
shadowOffset: { width: 0, height: 2 },
shadowRadius: 8,
elevation: 2,
},
md: {
shadowColor: "rgba(0, 0, 0, 0.04)",
shadowOffset: { width: 0, height: 4 },
shadowRadius: 16,
elevation: 4,
},
lg: {
shadowColor: "rgba(0, 0, 0, 0.08)",
shadowOffset: { width: 0, height: 8 },
shadowRadius: 24,
elevation: 8,
},
},
};
type StyleFactory<T> = (theme: typeof testTheme) => T;
function isStyleFactory<T>(styles: T | StyleFactory<T>): styles is StyleFactory<T> {
return typeof styles === "function";
}
// The app's breakpoints (src/styles/unistyles.ts), widest first.
const BREAKPOINTS: [string, number][] = [
["xl", 1200],
["lg", 992],
["md", 600],
["sm", 576],
["xs", 0],
];
const BREAKPOINT_NAMES = new Set(BREAKPOINTS.map(([name]) => name));
function isBreakpointValue(value: unknown): value is Record<string, unknown> {
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
const keys = Object.keys(value);
return keys.length > 0 && keys.every((key) => BREAKPOINT_NAMES.has(key));
}
/** Picks a breakpoint value for the window's width, the way Unistyles does at runtime. */
function resolveBreakpointValue(value: Record<string, unknown>): unknown {
const width = typeof window === "undefined" ? 0 : window.innerWidth;
for (const [name, minWidth] of BREAKPOINTS) {
if (width >= minWidth && name in value) return value[name];
}
return undefined;
}
function resolveStyle(style: unknown): unknown {
if (typeof style === "function") {
return (...args: unknown[]) => resolveStyle(style(...args));
}
if (typeof style !== "object" || style === null || Array.isArray(style)) return style;
return Object.fromEntries(
Object.entries(style).map(([key, value]) => [
key,
isBreakpointValue(value) ? resolveBreakpointValue(value) : value,
]),
);
}
function resolveStyles<T>(styles: T): T {
if (typeof styles !== "object" || styles === null) return styles;
return Object.fromEntries(
Object.entries(styles).map(([name, style]) => [name, resolveStyle(style)]),
) as T;
}
export const StyleSheet = {
create: <T>(styles: T | StyleFactory<T>): T =>
resolveStyles(isStyleFactory(styles) ? styles(testTheme) : styles),
};
export const withUnistyles = <T>(Component: T): T => Component;
export const useUnistyles = () => ({
theme: testTheme,
rt: {},
breakpoint: undefined,
});
export const UnistylesRuntime = {
setTheme: () => undefined,
themeName: "light",
};