GrConfigProvider

  • core
  • utilities
  • API · 9 props

Provides global defaults (control size, per-component props, i18n) to nested components — imperative dialogs included.

import { GrConfigProvider } from '@feugene/granularity/components/GrConfigProvider'

Overview

When to take it

  • the sizes of the controls are uniform across the application — size once instead of a prop on every field;
  • a component has its own defaults in this project — componentDefaults changes them without touching the places of use;
  • a translation is being connected — the t adapter and locale reach every component of the package;
  • a subtree lives in a mode of its own or in another build theme — a dark panel inside a light application without a global switch;
  • overlays have to lie above someone else’s — zIndexBase shifts the whole layer scale of the package.

When to take something else

NeedTake
The value is needed by a single componenta prop on the component itself
The palette changes, not the defaultspackages/granularity-tokens/docs/themes.md
Translations are needed, not their connectionpackages/granularity/docs/localization.md

It renders transparently (display: contents) and works through provide/inject, so it does not change the layout and may stand anywhere — including several times nested.

Playground controls come from the API, not from hand

Stage

Loading…

0 changed· state is in the URL

Examples 4 each with a live demo and its code

  • One toolbar, two densities

    The back office is dense, the warehouse tablet is used with gloves — the same toolbar, wrapped in a provider with size="sm" and size="lg". Nothing inside repeats size, so the field, the select and the button keep one height; the row that ends up a step off is the one sized by hand.

    Back office · size «sm»Mouse and keyboard, many rows on screen
    Warehouse tablet · size «lg»Touch, at arm's length
  • Quiet buttons in a list

    component-defaults makes every GrButton in the list a small neutral ghost, so twelve actions do not drown the amounts. The one action that matters — «Remind» on an overdue invoice — says variant="primary" on itself and wins: a default is only what is left unsaid.

    Sent invoices
    Northwind TradersINV-0418 · €1,284.50
    Tailspin ToysINV-0417 · €2,150.00
    Contoso LtdINV-0415 · €432.00
  • A mode for a preview, not for the page

    mode puts the mode’s pair in data-theme on the subtree: the admin picks the portal’s mode and sees it, while the admin page stays in its own. Panels that fly out into body take the mode of their owner, not of the page they left.

    Customer portal mode
    INV-0418Due in 3 daysAmount€1,284.50
  • A Spanish island in an English admin

    i18n hands a translation adapter to the subtree: the upload area, the pagination labels and «21–40 de 236» speak Spanish, the admin around stays English. The package ships the catalogue (@feugene/granularity/i18n); a ten-line adapter with t and te reads it, and until it loads the components keep their English fallback.

    Portal de clientes · Iberia Muebles S.L.Built-in strings of the components come from the provider's adapter
    Drag files here or click to select
    21–40 of 236
    Page 2 of 12

API generated from the package · cannot drift

Props

PropTypedefaultDescription
size"xs" | "sm" | "md" | "lg" | undefinedundefinedThe default size of the controls for the nested components.
componentDefaultsGrComponentDefaults | undefinedundefinedDefault props per component: `{ GrButton: { variant: 'secondary' } }`.
i18nGranularityI18nAdapter | null | undefinedundefinedThe translation adapter (fint-i18n compatible). It is passed to the nested components.
localestring | undefinedundefinedA request to the adapter to switch the language (`syncLocale`, on a fint-i18n instance — `setLocale`). The source of truth remains the adapter itself — the provider only passes on the intent.
modestring | undefinedundefinedThe mode of the subtree — an island: `dark` travels into `data-theme` as the pair of the document's current theme (`slate-dark`). If the theme has no such mode — the first one of the same scheme, otherwise its default mode. The mode of the **document** is the job of `useTheme`/`initThemeEarly`.
themestring | undefinedundefinedAn island of another build theme: `theme="clay" mode="dark"` writes `data-theme="clay-dark"`. The theme must be in `themes` of the granum config — otherwise its pairs are not in the CSS. The island loads the additional theme's sheet itself by the address in the manifest; a page that always shows the theme imports it in advance (`virtual:granum/themes/<id>.css`) — then there is no frame without values, and the loader recognises the sheet and does not download it again. Without `mode` — the document's mode if the theme has it, otherwise one of the same scheme.
portalTargetstring | HTMLElement | undefinedundefinedWhere to mount the overlays of the subtree. By default — the shared `#gr-portal` in `body`. A value of your own is needed where the application lives in a container: a micro-frontend, a shadow DOM, CSS scoping under a particular root.
zIndexBasenumber | undefinedundefinedThe base of the layer scale. All seven `--gr-z-*` are recomputed from it with the theme's steps — from the bottom navigation (−150) to the toasts (+200) — and set on `<html>`: panels teleport into `body`, and the subtree's variables never reach them.
tagstring | undefined"div"The tag of the wrapper. By default a transparent `<div style="display:contents">`.

Slots

SlotTypeDescription
defaultanyThe subtree the settings are addressed to.

Types

Every exported type of the subpath, grouped by the file it lives in. Names, generics and bodies come from the package’s declaration files and cannot drift.

Import
import type { GrComponentSize, GrConfigurableComponent, GrComponentDefaults } from '@feugene/granularity/components/GrConfigProvider'

Filedist/types/components/shared/configContext.d.ts

GrComponentSizetype
type GrComponentSize = typeof GR_COMPONENT_SIZES[number];
GrConfigurableComponenttype
type GrConfigurableComponent = keyof GrComponentDefaultsRegistry;
GrComponentDefaultstype
type GrComponentDefaults = {
    [TComponent in GrConfigurableComponent]?: Partial<GrComponentDefaultsRegistry[TComponent]>;
};
GrConfigContextinterface
interface GrConfigContext {
    size: ComputedRef<GrComponentSize | undefined>;
    componentDefaults: ComputedRef<GrComponentDefaults>;
    themeDefaults?: ComputedRef<GrComponentDefaults | undefined>;
    overrideDefaults?: ComputedRef<GrComponentDefaults>;
    theme?: ComputedRef<GrThemeId | undefined>;
    mode?: ComputedRef<GrModeName | undefined>;
    portalTarget?: ComputedRef<string | HTMLElement | undefined>;
}
themeComponentDefaultsfunction
function themeComponentDefaults(manifest: GrThemeManifest | null | undefined, themeId?: GrThemeId): GrComponentDefaults | undefined;
useGrConfigfunction
function useGrConfig(): GrConfigContext;
GrConfigSourcetype
type GrConfigSource = GrConfigContext | {
    provides?: Record<symbol | string, unknown>;
};
UseGrComponentSizeOptionstype
type UseGrComponentSizeOptions<TSize extends string> = {
    component?: GrConfigurableComponent;
    fallback?: TSize;
    supported?: readonly TSize[];
};
useGrComponentSizefunction
function useGrComponentSize<TSize extends string = GrComponentSize>(localSize: () => TSize | undefined, options?: UseGrComponentSizeOptions<TSize>): ComputedRef<TSize>;
export declare function useGrComponentDefaults<TComponent extends GrConfigurableComponent>(name: TComponent): ComputedRef<GrDefaultsOf<TComponent>>;
export declare function useGrComponentProp<TComponent extends GrConfigurableComponent, TProp extends keyof GrDefaultsOf<TComponent>>(component: TComponent, prop: TProp, localValue: () => GrDefaultsOf<TComponent>[TProp], fallback: NonNullable<GrDefaultsOf<TComponent>[TProp]>): ComputedRef<NonNullable<GrDefaultsOf<TComponent>[TProp]>>;
export declare function useGrThemeAttrs(): ComputedRef<Record<string, string>>;
useGrComponentDefaultsfunction
function useGrComponentDefaults<TComponent extends GrConfigurableComponent>(name: TComponent): ComputedRef<GrDefaultsOf<TComponent>>;
useGrComponentPropfunction
function useGrComponentProp<TComponent extends GrConfigurableComponent, TProp extends keyof GrDefaultsOf<TComponent>>(component: TComponent, prop: TProp, localValue: () => GrDefaultsOf<TComponent>[TProp], fallback: NonNullable<GrDefaultsOf<TComponent>[TProp]>): ComputedRef<NonNullable<GrDefaultsOf<TComponent>[TProp]>>;
export declare function useGrThemeAttrs(): ComputedRef<Record<string, string>>;
useGrThemeAttrsfunction
function useGrThemeAttrs(): ComputedRef<Record<string, string>>;

Filedist/types/composables/useGrComponentConfig.d.ts

GrComponentDefaultsRegistryinterface
interface GrComponentDefaultsRegistry {
}

Filedist/types/components/GrConfigProvider/config.d.ts

grConfigProviderConfigconst
const grConfigProviderConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrConfigProvider">;

Filedist/types/components/GrConfigProvider/GrConfigProvider.vue.d.ts

GrConfigProviderPropsinterface
interface GrConfigProviderProps {
    size?: GrComponentSize;
    componentDefaults?: GrComponentDefaults;
    i18n?: GranularityI18nAdapter | null;
    locale?: string;
    mode?: GrModeName;
    theme?: GrThemeId;
    portalTarget?: string | HTMLElement;
    zIndexBase?: number;
    tag?: string;
}

Usage from the library prose

The resolution order

The local prop of the component → componentDefaults[Component] → the global size → the component’s own default. Hence the rule for the authors of components: a prop configurable through the provider is declared with an undefined default, otherwise Vue substitutes its own value before the component looks into the config.

The componentDefaults of the top provider lie on top of two layers that need no provider: the default props of the document’s current theme (extensions.granularity.componentDefaults of its entry in the virtual:granum/themes manifest, if it was handed to granularityThemePlugin) and of the application (createGranularity({ componentDefaults })). They also apply where there is no provider in the tree at all. The theme layer follows useTheme().setTheme: after a theme change the components take its props.

A provider with theme — a theme island — replaces the document theme’s layer with the default props of its own theme or removes it if the theme has none of its own: the props of one theme on the components of another would look foreign. The application and the providers above the island stay on top.

The effective config can be read from the application with useGrConfig() — it is public.

Two size scales

The size of the provider is about controls (xs | sm | md | lg). Overlays have a scale of their own (sm | md | lg | xl | full), and the global size does not touch them: xs means nothing for a modal window. The size of a window is set pointwise:

vue
<GrConfigProvider :component-defaults="{ GrModal: { size: 'lg' } }">

That is how GrModal, GrDialog, GrConfirmDialog, GrPromptDialog, GrCommandPalette and GrDrawer are configured.

i18n

The adapter is handed down always and through a facade rather than by value: an adapter created asynchronously (the usual loading of a locale) will reach the children when it appears, and swapping the adapter on a change of language redraws the strings. If the prop is not set, the facade delegates to the adapter found higher up the tree — installation through app.use() keeps working.

locale is a request to the adapter to switch: the provider calls syncLocale, and on an @feugene/fint-i18n instance, which has no such method, setLocale. The source of truth remains the adapter itself: the provider neither stores the locale nor replaces it.

The mode and the theme of a subtree

mode puts the pair of the mode of the document’s current theme into data-theme on the wrapper: mode="dark" gives slate-dark. Every mode is declared on its own pair — [data-theme="slate-light"] alongside :root, and [data-theme="slate-dark"] — so an island works both ways with no extra styles: a dark one inside a light page and a light one inside a dark page. Together with the roles the island gets the color-scheme of its mode, and the derived formulas (--gr-primary-hover and its neighbours) and the references of per-component themes are recomputed on it rather than inherited already computed from the page.

theme is an island of another build theme: theme="clay" mode="dark" writes data-theme="clay-dark". The theme must be in themes of the granum config — otherwise none of the sheets has its pairs. The island loads the additional theme’s sheet itself by the address in the manifest; while it loads, the subtree shows the inherited values, and a failed load leaves them. A page that always shows the theme had better import the sheet statically — virtual:granum/themes/clay.css: then there is no frame without values, and the loader recognises such a sheet and does not download it again. The island’s mode is its mode, otherwise the document’s mode; if the theme has no such mode — the first one of the same scheme, otherwise its default mode (the same rule as setTheme), and in a dev build the provider warns that the theme lacks the given mode. A nested provider with mode alone changes the island’s mode. A mode name in theme (theme="dark") is a common mistake: a dev build warns about it too, as well as about a sheet that failed to load.

The pairs come from the theme manifest handed to granularityThemePlugin (or initThemeEarly) — together with the attribute name, if the build changed it.

The application’s own tokens that refer to roles (--app-accent: var(--gr-primary)) are best declared on the same boundary — :root, [data-theme]: a var() value is substituted where the property is declared, and one declared only on :root is inherited by the island already computed.

The panels of overlays are teleported into body, that is, in the DOM they live outside the wrapper — but in the component tree they stay inside, so inject reaches them and they set the pair for themselves. That covers the modal, the drawer, the dropdown, the popover, the tooltip, the selects, the toaster and the image viewer.

The theme and the mode of the document are the job of useTheme/initThemeEarly. The props of the provider are precisely about islands; there are no two mechanisms for the same thing in the package.

The layer scale

zIndexBase recomputes all seven layers of the scale from the base (bottom-nav −150, navbar −100, dropdown +0, tooltip +50, modal +100, loading +150, toast +200 — the same steps as in the theme) and sets them on <html>, restoring the previous values on unmount. The whole scale moves: a layer left in place would end up under somebody else’s modal or above its toasts.

On :root rather than on the wrapper for exactly the reason the theme is set by the panels themselves: a panel moves into body and does not see the variables of the subtree. “Layers by subtree” would be a false promise, so the scale is one per document — a second provider with a different base warns about the conflict in a dev build.

The same result is achieved with seven lines of CSS; the prop is needed where the base comes from the runtime (a micro-frontend inside someone else’s application).

The mounting point of the overlays

portalTarget names the container the overlays of the subtree move into: modals, select panels, toasts, imperative dialogs. By default that is the shared #gr-portal in body, which the package creates itself on the first opening.

vue
<GrConfigProvider portal-target="#my-app-portal">
  <App />
</GrConfigProvider>

It is needed where the application lives in a container of its own: a micro-frontend inside someone else’s page, a shadow DOM, CSS scoping under a particular root. The value is inherited by nested providers, and an individual component may override it with the teleportTo prop.

The provider only names the target — it does not create the DOM: the container has to exist by the time an overlay opens. There is a single requirement on it, but a strict one: no transform, filter, contain, perspective or will-change — they create a containing block for position: fixed, and floating panels will start computing their position from the container rather than from the viewport. The details — ../z-index.md.