DevSettingsWidget
A development-only floating panel that lists your twilight.json settings and home-component fields with defaults, and previews edited values in vite dev.
import { DevSettingsWidget, DevSettingsWidgetProps } from '@salla.sa/twilight-theme-engine/dev';In plain words
While you develop, your theme's setting values come from the merchant's dashboard, which makes it slow to try "what if this switch were off?". DevSettingsWidget is a small panel for that. A gear button appears in the bottom-left corner; open it to see every setting and home-component field your twilight.json declares, filled with its default value.
Change a value and press Apply: during vite dev the page re-renders as if the merchant had chosen it. Nothing is sent to Salla; your choices stay in this browser.
It is a development tool, so mount it only when import.meta.env.DEV is true. Vite sets that value to true in vite dev and to false in a build, so the widget's code never reaches shoppers.
Signature
function DevSettingsWidget({ schema }: DevSettingsWidgetProps): ReactElement | null
interface DevSettingsWidgetProps {
schema: DevSchema; // normally: import devSchema from 'virtual:twilight/schema'
}Try it live
useTheme().Try this: switch on Empty schema: that is what a production build passes, and the gear button disappears.Open the gear button in the bottom-left corner of the page, change a value, press Apply.
This page was built with import.meta.env.DEV = false: Apply saves the panel, and the theme ignores it.
useTheme().settings.playground_show_bannerundefineduseTheme().settings.playground_banner_textundefineduseTheme().settings.playground_image_fitundefined
import { lazy, Suspense } from 'react';
import devSchema from 'virtual:twilight/schema';
// Vite replaces import.meta.env.DEV with true in `vite dev` and false in a build.
const DevSettingsWidget = import.meta.env.DEV
? lazy(() =>
import('@salla.sa/twilight-theme-engine/dev').then((m) => ({ default: m.DevSettingsWidget }))
)
: null;
/** Render once in app/routes/__root.tsx, inside <body> after <TwilightProvider>. */
export function DevTools() {
if (!DevSettingsWidget) return null;
return (
<Suspense fallback={null}>
<DevSettingsWidget schema={devSchema} />
</Suspense>
);
}
Example
import { lazy, Suspense } from 'react';
import devSchema from 'virtual:twilight/schema';
// Vite replaces import.meta.env.DEV with true in `vite dev` and false in a build.
// With false this is just `null`: the import() and the widget's chunk are dropped.
const DevSettingsWidget = import.meta.env.DEV
? lazy(() =>
import('@salla.sa/twilight-theme-engine/dev').then((m) => ({ default: m.DevSettingsWidget }))
)
: null;
/** Render once in app/routes/__root.tsx, inside <body> after <TwilightProvider>. */
export function DevTools() {
if (!DevSettingsWidget) return null;
return (
<Suspense fallback={null}>
<DevSettingsWidget schema={devSchema} />
</Suspense>
);
}
How it behaves
The two halves of the mount pattern do different jobs.
import.meta.env.DEVis a literal the bundler folds, so a production build keepsnulland never emits the widget.lazy(() => import(...))puts the widget in a chunk of its own, fetched when it first renders, so even in development it stays out of the main bundle./devis a separate subpath for the same reason: nothing in the engine's main entry imports the widget.It renders nothing on the server, before it has mounted, and when the schema has no settings and no components, which is what
virtual:twilight/schemaholds in every production build. Once mounted it portals todocument.body, styles itself inline (no theme CSS needed) and sits fixed in the bottom-left corner above everything (z-index 2147483000).The panel shows a checkbox for
togglefields, a select forselect, a text box fortext, and the reason areadonlyfield cannot be edited (such asremote (products)orcollection/collection). Components are collapsible sections. Reset to twilight.json defaults restores every value and leaves Apply as it was.Apply seeds every setting you have not edited with its
twilight.jsondefault and switches the overrides on; turning it off keeps your edits for next time. The state is saved inlocalStorageundertwilight:dev-settings, in a store shared throughglobalThis, so the widget chunk and the engine read the same values.What Apply changes, in
vite devonly:TwilightProvidershallow-merges the setting values into the theme settings after hydration, so useTheme().settingsreturns them; the engine home page merges component-field values onto the home block whosepathmatches (home.<name>or<name>).Only
settingsandcomponentsfromtwilight.jsonappear. Collections, variable lists, numbers and dropdowns whose options come from Salla (source) are read-only, andstaticentries are skipped. The field shapes are the DevSchema types.
Gotchas
In a production build Apply changes nothing, even when you hand the widget a schema: the merge in
TwilightProviderand in the home page returns early unlessimport.meta.env.DEVis true (useDevSettingsOverridesinsrc/dev/dev-settings-store.ts). The live demo says which kind of build you are looking at.Apply overrides the store's real values for every editable setting, not only the ones you touched, so a merchant value that differs from the default is hidden while Apply is on. It also fills keys a real store may not have saved at all: code that forgets its own fallback works under Apply and breaks on a real store. Fix: check your pages with Apply off as well.
Boolean defaults come from
value, never fromselected. Raed'stopnav_is_darkhas"value": true, "selected": false, so the widget previews it as on.Component values reach only the engine's own home page. A home route you wrote yourself, or any other page, never sees them. Setting values reach every component that reads
useTheme().
Related
The simplified shape of twilight.json settings and components that virtual:twilight/schema exports and DevSettingsWidget draws.
useThemeReads the merchant's theme colors, font, theme settings and whether the page reads right-to-left.
twilightReactThe one Vite plugin call in a theme's vite.config.ts: server rendering, a route for every storefront page, translations and build checks.
registerHomeComponentsTells the engine which component draws each home page block, by the block path the Salla API sends. Call it once at startup.