Files
supabase/apps/studio/components/interfaces/App/MonacoThemeProvider.tsx
Alaister YoungandAlaister Young f8cc6c21bd [FE-2075] feat(studio): bump graphiql to v5 and use prebuilt component (#45404)
Adds `graphiql@5.2.2` and switches from our heavily-customised rebuild
(which used `@graphiql/react` + `@graphiql/toolkit` directly) to the
prebuilt component, restyled to match the dashboard. Role impersonation
re-added as a sidebar plugin.

This is a deliberately simpler setup than what we had – we lose some
layout customisation (sidebar is forced to the left, role impersonation
moves into the sidebar) but future upgrades become much easier since
we're no longer maintaining a fork-by-rewrite.

**Removed:**
- `apps/studio/components/interfaces/GraphQL/GraphiQL.tsx` – custom
rebuild
- `apps/studio/components/interfaces/GraphQL/graphiql.module.css` –
custom styles

**Changed:**
- Added `graphiql` ^5.2.2 (we previously didn't have the top-level
package, just the subpackages)
- `@graphiql/react` ^0.19.4 → ^0.37.3 (now Monaco-based; v0.19 was still
on CodeMirror 5)
- `@graphiql/toolkit` ^0.9.1 → ^0.11.3
- `GraphiQLTab.tsx` now wires up the prebuilt `<GraphiQL />` with worker
setup, theme bridge, and plugins
- New `graphiql.module.css` scopes restyling via `:global(...)` since we
can't add hashed classes to the library's DOM
- `RoleImpersonationSelector` gained an `orientation: 'horizontal' |
'vertical'` prop (default `horizontal`) so it fits in the sidebar pane –
all existing call sites unchanged
- `MonacoThemeProvider` exports `getTheme` so the GraphQL Monaco
instance can reuse Studio's theme

**Added:**
- Theme bridge: `supabase-graphql-dark` / `supabase-graphql-light`
Monaco themes synced with `next-themes` via `forcedTheme`
- Role impersonation sidebar plugin (gated on `field.jwt_secret` read
permission, same as before)

### Notes / tradeoffs

- We don't share Studio's monaco instance – Studio loads it via AMD/CDN,
GraphiQL bundles it as ESM. Both end up on `monaco-editor@0.52.2` but in
different module systems. Sharing would require ripping out Studio's CDN
loader (Studio-wide refactor, out of scope). GraphiQL's monaco is
dynamically imported and only loads when the GraphQL tab opens.
- The dark/light response panel uses different `--graphiql-response-bg`
tokens because the editor sits at very different baseline lightness in
each theme; a single token can't lift it meaningfully in both
directions.
- Session header (tabs row) is hidden – we don't expose multi-tab
workflows.

## To test

- Open `/project/<ref>/api/graphiql` in both light and dark themes –
editor + response panel backgrounds, sidebar borders, button radii
should all match the dashboard
- Run a query and confirm syntax highlighting works (GraphQL-specific
token `argument.identifier.gql` is purple)
- Open the doc explorer and history sidebar plugins
- As a user with `field.jwt_secret` read permission: open the Role
Impersonation sidebar plugin, pick a role, confirm subsequent queries
hit the API with the impersonated JWT
- As a user without that permission: confirm the Role Impersonation
plugin doesn't appear, history still does
- Toggle theme while GraphiQL is open – Monaco theme should swap without
a reload

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Vertical layout option for the role impersonation selector; radios can
expand to full width.

* **Improvements**
* Revamped GraphiQL integration with updated upstream package, plugins,
and editor theming for improved consistency and UX.
* New GraphiQL styling and layout for clearer pane separation and
polished controls.
* Role selector radios now support a full-width mode for improved
responsiveness.

* **Chores**
  * Updated GraphiQL-related dependencies.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
2026-05-01 16:16:26 +08:00

45 lines
1.7 KiB
TypeScript

import { useMonaco } from '@monaco-editor/react'
import { useTheme } from 'next-themes'
import { useMemo } from 'react'
export const getTheme = (theme: string) => {
const isDarkMode = theme.includes('dark')
// [TODO] Probably need better theming for light mode
return {
base: isDarkMode ? ('vs-dark' as const) : ('vs' as const), // can also be vs-dark or hc-black
inherit: true, // can also be false to completely replace the builtin rules
rules: [
{ token: '', background: isDarkMode ? '1f1f1f' : 'f0f0f0' },
{
token: '',
background: isDarkMode ? '1f1f1f' : 'f0f0f0',
foreground: isDarkMode ? 'd4d4d4' : '444444',
},
{ token: 'string.sql', foreground: '24b47e' },
{ token: 'comment', foreground: '666666' },
{ token: 'predefined.sql', foreground: isDarkMode ? 'D4D4D4' : '444444' },
],
colors: { 'editor.background': isDarkMode ? '#1f1f1f' : '#f0f0f0' },
}
}
/**
* This component is used to set the theme for the Monaco editor. This would be a hook but it needs to be placed between
* ThemeProvider and the layout page so a component is the most convenient way to do this.
*/
export const MonacoThemeProvider = () => {
const monaco = useMonaco()
const { resolvedTheme } = useTheme()
// Define the supabase theme for Monaco before anything is rendered. Using useEffect would sometime load the theme
// after the editor was loaded, so it looked off. useMemo will always be run before rendering
useMemo(() => {
if (monaco && resolvedTheme) {
const mode = getTheme(resolvedTheme)
monaco.editor.defineTheme('supabase', mode)
}
}, [resolvedTheme, monaco])
return null
}