@mastra/playground-ui 24.0.0-alpha.3 → 24.0.0-alpha.5

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/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # @mastra/playground-ui
2
2
 
3
+ ## 24.0.0-alpha.5
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [[`28caa5b`](https://github.com/mastra-ai/mastra/commit/28caa5b032358545af2589ed90636eccb4dd9d2f), [`7d056b6`](https://github.com/mastra-ai/mastra/commit/7d056b6ecf603cacaa0f663ff1df025ed885b6c1), [`26f1f94`](https://github.com/mastra-ai/mastra/commit/26f1f9490574b864ba1ecedf2c9632e0767a23bd)]:
8
+ - @mastra/core@1.29.0-alpha.5
9
+ - @mastra/client-js@1.15.0-alpha.5
10
+ - @mastra/react@0.2.30-alpha.5
11
+
12
+ ## 24.0.0-alpha.4
13
+
14
+ ### Minor Changes
15
+
16
+ - Added shared `ThemeProvider`, `useTheme`, and `ThemeToggle` to unify theme management. ([#15838](https://github.com/mastra-ai/mastra/pull/15838))
17
+
18
+ **Added**
19
+ - `ThemeProvider` applies the resolved theme class to `<html>` and persists the choice under the shared `mastra-theme` localStorage key, with a one-time migration from previously stored preferences.
20
+ - `useTheme()` works without a `<ThemeProvider>` ancestor: it returns a read-only fallback that tracks the OS color scheme and exposes a no-op `setTheme`, so theme-aware leaf components (e.g. `CodeDiff`, `CodeEditor`) keep working when embedded standalone.
21
+ - `ThemeToggle` renders a system/light/dark pill and supports both controlled and uncontrolled usage.
22
+
23
+ ### Patch Changes
24
+
25
+ - Updated dependencies [[`8a71261`](https://github.com/mastra-ai/mastra/commit/8a71261e3954ae617c6f8e25767b951f99438ab2), [`021a60f`](https://github.com/mastra-ai/mastra/commit/021a60f1f3e0135a70ef23c58be7a9b3aaffe6b4)]:
26
+ - @mastra/core@1.29.0-alpha.4
27
+ - @mastra/client-js@1.15.0-alpha.4
28
+ - @mastra/react@0.2.30-alpha.4
29
+
3
30
  ## 24.0.0-alpha.3
4
31
 
5
32
  ### Patch Changes
package/dist/index.cjs.js CHANGED
@@ -18,8 +18,6 @@ const codemirrorThemeDracula = require('@uiw/codemirror-theme-dracula');
18
18
  const ReactCodeMirror = require('@uiw/react-codemirror');
19
19
  const autocomplete = require('@codemirror/autocomplete');
20
20
  const sonner = require('sonner');
21
- const zustand = require('zustand');
22
- const middleware = require('zustand/middleware');
23
21
  const react = require('@mastra/react');
24
22
  const AlertDialogPrimitive = require('@radix-ui/react-alert-dialog');
25
23
  const CheckboxPrimitive = require('@radix-ui/react-checkbox');
@@ -50,6 +48,8 @@ const isToday = require('date-fns/isToday');
50
48
  const search = require('@codemirror/search');
51
49
  const recharts = require('recharts');
52
50
  const reactResizablePanels = require('react-resizable-panels');
51
+ const zustand = require('zustand');
52
+ const middleware = require('zustand/middleware');
53
53
  const reactQuery = require('@tanstack/react-query');
54
54
  const observability = require('@mastra/core/observability');
55
55
 
@@ -5180,33 +5180,117 @@ function CopyButton({
5180
5180
  ] });
5181
5181
  }
5182
5182
 
5183
- const usePlaygroundStore = zustand.create()(
5184
- middleware.persist(
5185
- (set) => ({
5186
- requestContext: {},
5187
- theme: "dark",
5188
- setRequestContext: (requestContext) => set({ requestContext }),
5189
- setTheme: (theme) => set({ theme })
5190
- }),
5191
- {
5192
- name: "mastra-playground-store"
5183
+ const DEFAULT_KEY = "mastra-theme";
5184
+ const LEGACY_ZUSTAND_KEY = "mastra-playground-store";
5185
+ const isTheme = (value) => value === "dark" || value === "light" || value === "system";
5186
+ const readLegacyZustand = (storage) => {
5187
+ try {
5188
+ const raw = storage.getItem(LEGACY_ZUSTAND_KEY);
5189
+ if (!raw) return void 0;
5190
+ const parsed = JSON.parse(raw);
5191
+ const theme = parsed?.state?.theme;
5192
+ return isTheme(theme) ? theme : void 0;
5193
+ } catch {
5194
+ return void 0;
5195
+ }
5196
+ };
5197
+ const createLocalStorageAdapter = (key = DEFAULT_KEY) => ({
5198
+ get() {
5199
+ if (typeof window === "undefined") return null;
5200
+ try {
5201
+ const raw = window.localStorage.getItem(key);
5202
+ if (isTheme(raw)) return raw;
5203
+ const migrated = readLegacyZustand(window.localStorage);
5204
+ if (migrated) {
5205
+ window.localStorage.setItem(key, migrated);
5206
+ return migrated;
5207
+ }
5208
+ return null;
5209
+ } catch {
5210
+ return null;
5193
5211
  }
5194
- )
5195
- );
5196
- const darkQuery = typeof window !== "undefined" ? window.matchMedia("(prefers-color-scheme: dark)") : null;
5197
- function subscribeToColorScheme(callback) {
5198
- darkQuery?.addEventListener("change", callback);
5199
- return () => darkQuery?.removeEventListener("change", callback);
5200
- }
5201
- function getSystemIsDark() {
5202
- return darkQuery?.matches ?? true;
5203
- }
5204
- function useIsDarkMode() {
5205
- const storeTheme = usePlaygroundStore((s) => s.theme);
5206
- const systemIsDark = React.useSyncExternalStore(subscribeToColorScheme, getSystemIsDark, () => true);
5207
- if (storeTheme === "system") return systemIsDark;
5208
- return storeTheme === "dark";
5209
- }
5212
+ },
5213
+ set(value) {
5214
+ if (typeof window === "undefined") return;
5215
+ try {
5216
+ window.localStorage.setItem(key, value);
5217
+ } catch {
5218
+ }
5219
+ },
5220
+ subscribe(listener) {
5221
+ if (typeof window === "undefined") return () => {
5222
+ };
5223
+ const handler = (event) => {
5224
+ if (event.key !== key) return;
5225
+ if (event.newValue === null) {
5226
+ listener(null);
5227
+ return;
5228
+ }
5229
+ if (isTheme(event.newValue)) listener(event.newValue);
5230
+ };
5231
+ window.addEventListener("storage", handler);
5232
+ return () => window.removeEventListener("storage", handler);
5233
+ }
5234
+ });
5235
+
5236
+ const ThemeContext = React.createContext(null);
5237
+
5238
+ const SYSTEM_QUERY = "(prefers-color-scheme: dark)";
5239
+ const useIsomorphicLayoutEffect = typeof window === "undefined" ? React.useEffect : React.useLayoutEffect;
5240
+ const getSystemTheme = () => {
5241
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return "dark";
5242
+ return window.matchMedia(SYSTEM_QUERY).matches ? "dark" : "light";
5243
+ };
5244
+ const subscribeSystemTheme = (callback) => {
5245
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return () => {
5246
+ };
5247
+ const query = window.matchMedia(SYSTEM_QUERY);
5248
+ query.addEventListener("change", callback);
5249
+ return () => query.removeEventListener("change", callback);
5250
+ };
5251
+ const applyThemeClass = (target, resolved) => {
5252
+ target.classList.remove(resolved === "dark" ? "light" : "dark");
5253
+ target.classList.add(resolved);
5254
+ };
5255
+ const ThemeProvider = ({ children, defaultTheme = "system", storageKey, target }) => {
5256
+ const adapter = React.useMemo(() => createLocalStorageAdapter(storageKey), [storageKey]);
5257
+ const previousTargetRef = React.useRef(null);
5258
+ const [theme, setThemeState] = React.useState(() => adapter.get() ?? defaultTheme);
5259
+ const systemTheme = React.useSyncExternalStore(subscribeSystemTheme, getSystemTheme, () => "dark");
5260
+ React.useEffect(() => adapter.subscribe((next) => setThemeState(next ?? defaultTheme)), [adapter, defaultTheme]);
5261
+ const resolvedTheme = theme === "system" ? systemTheme : theme;
5262
+ useIsomorphicLayoutEffect(() => {
5263
+ const el = target ?? (typeof window === "undefined" ? null : window.document.documentElement);
5264
+ if (previousTargetRef.current && previousTargetRef.current !== el) {
5265
+ previousTargetRef.current.classList.remove("dark", "light");
5266
+ }
5267
+ if (el) applyThemeClass(el, resolvedTheme);
5268
+ previousTargetRef.current = el;
5269
+ }, [resolvedTheme, target]);
5270
+ const value = React.useMemo(
5271
+ () => ({
5272
+ theme,
5273
+ resolvedTheme,
5274
+ systemTheme,
5275
+ setTheme: (next) => {
5276
+ setThemeState(next);
5277
+ adapter.set(next);
5278
+ }
5279
+ }),
5280
+ [theme, resolvedTheme, systemTheme, adapter]
5281
+ );
5282
+ return /* @__PURE__ */ jsxRuntime.jsx(ThemeContext.Provider, { value, children });
5283
+ };
5284
+ const noop = () => {
5285
+ };
5286
+ const useTheme = () => {
5287
+ const ctx = React.useContext(ThemeContext);
5288
+ const systemTheme = React.useSyncExternalStore(subscribeSystemTheme, getSystemTheme, () => "dark");
5289
+ return React.useMemo(
5290
+ () => ctx ?? { theme: "system", resolvedTheme: systemTheme, systemTheme, setTheme: noop },
5291
+ [ctx, systemTheme]
5292
+ );
5293
+ };
5210
5294
 
5211
5295
  function buildDarkTheme$2() {
5212
5296
  const baseTheme = codemirrorThemeDracula.draculaInit({
@@ -5397,7 +5481,7 @@ function buildLightTheme$2() {
5397
5481
  return [editorTheme, language.syntaxHighlighting(highlightStyle)];
5398
5482
  }
5399
5483
  const useCodemirrorTheme$3 = () => {
5400
- const isDark = useIsDarkMode();
5484
+ const isDark = useTheme().resolvedTheme === "dark";
5401
5485
  return React.useMemo(() => isDark ? buildDarkTheme$2() : buildLightTheme$2(), [isDark]);
5402
5486
  };
5403
5487
  const CodeEditor = React.forwardRef(
@@ -8408,6 +8492,81 @@ const Switch = React__namespace.forwardRef(({ className, ...props }, ref) => /*
8408
8492
  ));
8409
8493
  Switch.displayName = SwitchPrimitives__namespace.Root.displayName;
8410
8494
 
8495
+ const DEFAULT_OPTIONS = [
8496
+ { value: "system", label: "System", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Monitor, {}) },
8497
+ { value: "light", label: "Light", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sun, {}) },
8498
+ { value: "dark", label: "Dark", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Moon, {}) }
8499
+ ];
8500
+ const ITEM_WIDTH = 28;
8501
+ const ITEM_GAP = 2;
8502
+ const ThemeToggle = ({
8503
+ value,
8504
+ onChange,
8505
+ options = DEFAULT_OPTIONS,
8506
+ className,
8507
+ "aria-label": ariaLabel = "Theme",
8508
+ ...rest
8509
+ }) => {
8510
+ const { theme, setTheme } = useTheme();
8511
+ const current = value ?? theme;
8512
+ const commit = onChange ?? setTheme;
8513
+ const effectiveCurrent = options.some((option) => option.value === current) ? current : options[0]?.value ?? "system";
8514
+ const handleChange = (next) => {
8515
+ const match = options.find((opt) => opt.value === next);
8516
+ if (match) commit(match.value);
8517
+ };
8518
+ const activeIndex = Math.max(
8519
+ 0,
8520
+ options.findIndex((option) => option.value === effectiveCurrent)
8521
+ );
8522
+ const indicatorOffset = activeIndex * (ITEM_WIDTH + ITEM_GAP);
8523
+ return /* @__PURE__ */ jsxRuntime.jsxs(
8524
+ RadioGroupPrimitive__namespace.Root,
8525
+ {
8526
+ ...rest,
8527
+ value: effectiveCurrent,
8528
+ onValueChange: handleChange,
8529
+ orientation: "horizontal",
8530
+ "aria-label": ariaLabel,
8531
+ className: cn(
8532
+ "relative inline-flex w-fit items-center gap-0.5 rounded-full border border-border1 bg-surface3 p-0.5",
8533
+ className
8534
+ ),
8535
+ children: [
8536
+ /* @__PURE__ */ jsxRuntime.jsx(
8537
+ "span",
8538
+ {
8539
+ "aria-hidden": "true",
8540
+ className: cn(
8541
+ "pointer-events-none absolute inset-y-0.5 left-0.5 rounded-full bg-surface5 motion-reduce:transition-none",
8542
+ transitions.transform
8543
+ ),
8544
+ style: { width: ITEM_WIDTH, transform: `translateX(${indicatorOffset}px)` }
8545
+ }
8546
+ ),
8547
+ options.map((option) => /* @__PURE__ */ jsxRuntime.jsx(
8548
+ RadioGroupPrimitive__namespace.Item,
8549
+ {
8550
+ value: option.value,
8551
+ "aria-label": option.label,
8552
+ style: { width: ITEM_WIDTH },
8553
+ className: cn(
8554
+ "relative inline-flex h-6 cursor-pointer items-center justify-center rounded-full",
8555
+ "[&_svg]:size-3.5 text-icon3 hover:text-icon6 data-[state=checked]:text-icon6",
8556
+ "focus-visible:outline-hidden",
8557
+ "active:scale-90 motion-reduce:transition-none",
8558
+ transitions.colors,
8559
+ transitions.transform
8560
+ ),
8561
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", className: "pointer-events-none inline-flex items-center justify-center", children: option.icon })
8562
+ },
8563
+ option.value
8564
+ ))
8565
+ ]
8566
+ }
8567
+ );
8568
+ };
8569
+
8411
8570
  function Truncate({
8412
8571
  children,
8413
8572
  untilChar,
@@ -10295,7 +10454,7 @@ function buildSideDialogLightTheme() {
10295
10454
  return [editorTheme, language.syntaxHighlighting(highlightStyle)];
10296
10455
  }
10297
10456
  const useCodemirrorTheme$2 = () => {
10298
- const isDark = useIsDarkMode();
10457
+ const isDark = useTheme().resolvedTheme === "dark";
10299
10458
  return React.useMemo(() => isDark ? buildSideDialogDarkTheme() : buildSideDialogLightTheme(), [isDark]);
10300
10459
  };
10301
10460
  function SideDialogCodeSection({ codeStr = "", title, icon, simplified = false }) {
@@ -10935,7 +11094,7 @@ function buildDiffLightTheme() {
10935
11094
  function CodeDiff({ codeA, codeB }) {
10936
11095
  const containerRef = React.useRef(null);
10937
11096
  const viewRef = React.useRef(null);
10938
- const isDark = useIsDarkMode();
11097
+ const isDark = useTheme().resolvedTheme === "dark";
10939
11098
  const theme = React.useMemo(() => isDark ? buildDiffDarkTheme() : buildDiffLightTheme(), [isDark]);
10940
11099
  React.useEffect(() => {
10941
11100
  if (!containerRef.current) return;
@@ -12925,7 +13084,7 @@ function buildLightTheme$1() {
12925
13084
  return [editorTheme, language.syntaxHighlighting(highlightStyle)];
12926
13085
  }
12927
13086
  const useCodemirrorTheme$1 = () => {
12928
- const isDark = useIsDarkMode();
13087
+ const isDark = useTheme().resolvedTheme === "dark";
12929
13088
  return React.useMemo(() => isDark ? buildDarkTheme$1() : buildLightTheme$1(), [isDark]);
12930
13089
  };
12931
13090
  function DataCodeSection({
@@ -13240,7 +13399,7 @@ function buildLightTheme() {
13240
13399
  return [editorTheme, language.syntaxHighlighting(highlightStyle)];
13241
13400
  }
13242
13401
  const useCodemirrorTheme = () => {
13243
- const isDark = useIsDarkMode();
13402
+ const isDark = useTheme().resolvedTheme === "dark";
13244
13403
  return React.useMemo(() => isDark ? buildDarkTheme() : buildLightTheme(), [isDark]);
13245
13404
  };
13246
13405
  function DataDetailsPanelCodeSection({
@@ -15006,6 +15165,18 @@ function generateDefaultValues(schema) {
15006
15165
  return generateObjectDefaults(schema.properties, 0);
15007
15166
  }
15008
15167
 
15168
+ const usePlaygroundStore = zustand.create()(
15169
+ middleware.persist(
15170
+ (set) => ({
15171
+ requestContext: {},
15172
+ setRequestContext: (requestContext) => set({ requestContext })
15173
+ }),
15174
+ {
15175
+ name: "mastra-playground-store"
15176
+ }
15177
+ )
15178
+ );
15179
+
15009
15180
  const DATE_PRESETS = [
15010
15181
  { label: "Last 24 hours", value: "24h" },
15011
15182
  { label: "Last 3 days", value: "3d" },
@@ -19447,6 +19618,8 @@ exports.TextFieldBlock = TextFieldBlock;
19447
19618
  exports.Textarea = Textarea;
19448
19619
  exports.Th = Th;
19449
19620
  exports.Thead = Thead;
19621
+ exports.ThemeProvider = ThemeProvider;
19622
+ exports.ThemeToggle = ThemeToggle;
19450
19623
  exports.ThreadDeleteButton = ThreadDeleteButton;
19451
19624
  exports.ThreadItem = ThreadItem;
19452
19625
  exports.ThreadLink = ThreadLink;
@@ -19582,7 +19755,6 @@ exports.useCopyToClipboard = useCopyToClipboard;
19582
19755
  exports.useEntityNames = useEntityNames;
19583
19756
  exports.useEnvironments = useEnvironments;
19584
19757
  exports.useInView = useInView;
19585
- exports.useIsDarkMode = useIsDarkMode;
19586
19758
  exports.useJSONSchemaForm = useJSONSchemaForm;
19587
19759
  exports.useJSONSchemaFormField = useJSONSchemaFormField;
19588
19760
  exports.useJSONSchemaFormNestedContext = useJSONSchemaFormNestedContext;
@@ -19603,6 +19775,7 @@ exports.useServiceNames = useServiceNames;
19603
19775
  exports.useSpanDetail = useSpanDetail;
19604
19776
  exports.useTableKeyboardNavigation = useTableKeyboardNavigation;
19605
19777
  exports.useTags = useTags;
19778
+ exports.useTheme = useTheme;
19606
19779
  exports.useTokenUsageByAgentMetrics = useTokenUsageByAgentMetrics;
19607
19780
  exports.useTotalTokensKpiMetrics = useTotalTokensKpiMetrics;
19608
19781
  exports.useTraceFilterPersistence = useTraceFilterPersistence;