Source ThemeEditor
Source ThemeEditor
Cette page est générée automatiquement à partir du dépôt local au moment de la génération de la documentation.
Fichiers inclus
packages/common/src/lib/widgets/theme-editor/theme-editor.declaration.tspackages/common/src/lib/widgets/theme-editor/theme-editor.config.tspackages/common/src/lib/widgets/theme-editor/ThemeEditor.svelte
packages/common/src/lib/widgets/theme-editor/theme-editor.declaration.ts
import { widgetFactorySvelte, type WidgetProps } from '$lib/api/managers/widget';import { type ThemeEditorConfig, themeEditorConfig } from './theme-editor.config';import type { WidgetDeclaration } from '$lib/api/managers/widget/widget-declaration';
export const declaration = { factory: () => import('./ThemeEditor.svelte').then((ThemeEditor) => widgetFactorySvelte(ThemeEditor)), schema: () => themeEditorConfig,} satisfies WidgetDeclaration;
export type ThemeEditorProps = WidgetProps<ThemeEditorConfig>;packages/common/src/lib/widgets/theme-editor/theme-editor.config.ts
import { inToolbarSchemaFrom } from '$lib/api/managers/configuration/models/widget/widget-in-toolbar.schema';import { i18nSchemaFrom } from '$lib/api/managers/i18n/i18n.schema';import type { z } from 'zod';import { defineWidgetConfig } from '$lib/api/managers/configuration/models/widget/widget-configuration.schema';
const i18n = i18nSchemaFrom({ 'test-label': { fr: 'Texte', nl: 'Test', }, 'test-text': { fr: 'Test', nl: 'Test', }, 'color-primary': { fr: 'Primaire', nl: 'NL - Primaire', }, 'color-secondary': { fr: 'Secondaire', nl: 'NL - Secondaire', }, 'color-surface': { fr: 'Surface', nl: 'NL - Surface', }, 'color-accent': { fr: 'Accentué', nl: 'NL - Accentué', }, 'color-muted': { fr: 'Muet', nl: 'NL - Muet', }, 'color-destructive': { fr: 'Suppression', nl: 'NL - Suppression', }, 'color-destructive-text': { fr: 'Supprimer', nl: 'NL - Supprimer', }, rounded: { fr: 'Arrondi', nl: 'NL - Arrondi', }, 'rounded-placeholder': { fr: 'Arrondi', nl: 'NL - Arrondi', },});
export const themeEditorConfig = defineWidgetConfig({ title: { fr: 'Éditeur de thème', nl: 'NL - Éditeur de thème', }, icon: { lucide: 'Palette', }, inToolbar: inToolbarSchemaFrom({ type: 'button', label: { fr: 'Éditeur de thème', nl: 'NL - Éditeur de thème', }, icon: { lucide: 'Palette', }, }), i18n: i18n,});
export type ThemeEditorConfig = z.infer<typeof themeEditorConfig>;packages/common/src/lib/widgets/theme-editor/ThemeEditor.svelte
<script lang="ts"> import { getI18n } from '$lib/api/managers/i18n'; import { getLayoutManager } from '$lib/api/managers/layout'; import { hexToShad, shadToHex } from '$lib/api/utils/color.utils'; import { ApiSelect, type ApiSelectItem } from '$lib/components/api-select/index'; import { Button } from '$lib/components/shadcn/ui/button'; import { Label } from '$lib/components/shadcn/ui/label'; import { Textarea } from '$lib/components/shadcn/ui/textarea'; import type { ThemeEditorProps } from './theme-editor.declaration';
const { fullConfig }: ThemeEditorProps = $props();
const layoutManager = getLayoutManager(); const i18n = getI18n(fullConfig.i18n);
function getCssVar(name: string) { const viewerRoot = layoutManager.layout.root!; return getComputedStyle(viewerRoot).getPropertyValue(name); }
function setCssVar(name: string, value: string) { layoutManager.layout.root!.style.setProperty(name, value); }
const theme = $state({ primary: shadToHex(getCssVar('--gv-color-primary')), onPrimary: shadToHex(getCssVar('--gv-color-on-primary')),
accent: shadToHex(getCssVar('--gv-color-accent')), onAccent: shadToHex(getCssVar('--gv-color-on-accent')),
secondary: shadToHex(getCssVar('--gv-color-secondary')), onSecondary: shadToHex(getCssVar('--gv-color-on-secondary')),
surface: shadToHex(getCssVar('--gv-color-surface')), onSurface: shadToHex(getCssVar('--gv-color-on-surface')),
muted: shadToHex(getCssVar('--gv-color-muted')), onMuted: shadToHex(getCssVar('--gv-color-on-muted')),
destructive: shadToHex(getCssVar('--gv-color-destructive')), onDestructive: shadToHex(getCssVar('--gv-color-on-destructive')),
rounded: getCssVar('--gv-rounded'), });
const roundedOptions = [ { value: '0', label: '0' }, { value: '0.25rem', label: '0.25rem' }, { value: '0.5rem', label: '0.5rem' }, { value: '1rem', label: '1rem' }, { value: '2rem', label: '2rem' }, ] as ApiSelectItem<string>[];
// Push current theme value if not in the list const currentInOptions = !!roundedOptions.find((option) => option.value === theme.rounded); if (!currentInOptions && theme.rounded) { const item = { value: theme.rounded, label: theme.rounded }; roundedOptions.push(item); }
function applyTheme() { setCssVar('--gv-color-primary', hexToShad(theme.primary)); setCssVar('--gv-color-on-primary', hexToShad(theme.onPrimary));
setCssVar('--gv-color-secondary', hexToShad(theme.secondary)); setCssVar('--gv-color-on-secondary', hexToShad(theme.onSecondary));
setCssVar('--gv-color-surface', hexToShad(theme.surface)); setCssVar('--gv-color-on-surface', hexToShad(theme.onSurface));
setCssVar('--gv-color-accent', hexToShad(theme.accent)); setCssVar('--gv-color-on-accent', hexToShad(theme.onAccent));
setCssVar('--gv-color-muted', hexToShad(theme.muted)); setCssVar('--gv-color-on-muted', hexToShad(theme.onMuted));
setCssVar('--gv-color-destructive', hexToShad(theme.destructive)); setCssVar('--gv-color-on-destructive', hexToShad(theme.onDestructive));
setCssVar('--gv-rounded', theme.rounded); }
const formattedTheme = $derived(`--gv-color-primary: ${hexToShad(theme.primary)}; /*${theme.primary}*/--gv-color-on-primary: ${hexToShad(theme.onPrimary)}; /*${theme.onPrimary}*/
--gv-color-secondary: ${hexToShad(theme.secondary)}; /*${theme.secondary}*/--gv-color-on-secondary: ${hexToShad(theme.onSecondary)}; /*${theme.onSecondary}*/
--gv-color-surface: ${hexToShad(theme.surface)}; /*${theme.surface}*/--gv-color-on-surface: ${hexToShad(theme.onSurface)}; /*${theme.onSurface}*/
--gv-color-accent: ${hexToShad(theme.accent)}; /*${theme.accent}*/--gv-color-on-accent: ${hexToShad(theme.onAccent)}; /*${theme.onAccent}*/
--gv-color-muted: ${hexToShad(theme.muted)}; /*${theme.muted}*/--gv-color-on-muted: ${hexToShad(theme.onMuted)}; /*${theme.onMuted}*/
--gv-color-destructive: ${hexToShad(theme.destructive)}; /*${theme.destructive}*/--gv-color-on-destructive: ${hexToShad(theme.onDestructive)}; /*${theme.onDestructive}*/
--gv-rounded: ${theme.rounded};`);</script>
<div class="gv-flex gv-flex-col gv-gap-2"> <div class="gv-p-2 gv-grid gv-grid-cols-[90px_80px_50px_80px_auto] gv-gap-1"> <!--Primary--> <Label class="gv-content-center">{i18n('color-primary')}</Label> <input type="color" bind:value={theme.primary} class="gv-w-full" /> <Label class="gv-content-center">{i18n('test-label')}</Label> <input type="color" bind:value={theme.onPrimary} class="gv-w-full" /> <div class="gv-text-center" style:background-color={theme.primary} style:color={theme.onPrimary}> {i18n('test-text')} </div>
<!--Secondary--> <Label class="gv-content-center">{i18n('color-secondary')}</Label> <input type="color" bind:value={theme.secondary} class="gv-w-full" /> <Label class="gv-content-center">{i18n('test-label')}</Label> <input type="color" bind:value={theme.onSecondary} class="gv-w-full" /> <div class="gv-text-center" style:background-color={theme.secondary} style:color={theme.onSecondary}> {i18n('test-text')} </div>
<!--Surface--> <Label class="gv-content-center">{i18n('color-surface')}</Label> <input type="color" bind:value={theme.surface} class="gv-w-full" /> <Label class="gv-content-center">{i18n('test-label')}</Label> <input type="color" bind:value={theme.onSurface} class="gv-w-full" /> <div class="gv-text-center" style:background-color={theme.surface} style:color={theme.onSurface}> {i18n('test-text')} </div>
<!--Accent--> <Label class="gv-content-center">{i18n('color-accent')}</Label> <input type="color" bind:value={theme.accent} class="gv-w-full" /> <Label class="gv-content-center">{i18n('test-label')}</Label> <input type="color" bind:value={theme.onAccent} class="gv-w-full" /> <div class="gv-flex gv-justify-center gv-items-center" style:background-color={theme.surface}> <span class="gv-px-3" style:background-color={theme.accent} style:color={theme.onAccent}> Test </span> </div>
<!--Muted--> <Label class="gv-content-center">{i18n('color-muted')}</Label> <input type="color" bind:value={theme.muted} class="gv-w-full" /> <Label class="gv-content-center">{i18n('test-label')}</Label> <input type="color" bind:value={theme.onMuted} class="gv-w-full" /> <div class="gv-text-center" style:background-color={theme.muted} style:color={theme.onMuted}> {i18n('test-text')} </div>
<!--Destructive--> <Label class="gv-content-center">{i18n('color-destructive')}</Label> <input type="color" bind:value={theme.destructive} class="gv-w-full" /> <Label class="gv-content-center">{i18n('test-label')}</Label> <input type="color" bind:value={theme.onDestructive} class="gv-w-full" /> <div class="gv-text-center" style:background-color={theme.destructive} style:color={theme.onDestructive}> {i18n('color-destructive-text')} </div>
<!--Rounded--> <Label class="gv-content-center">{i18n('rounded')}</Label> <ApiSelect options={roundedOptions} class="gv-col-span-2 gv-p-1 gv-h-5" placeholder={i18n('rounded-placeholder')} bind:value={theme.rounded} /> <div class="gv-text-center gv-col-end-6" style:background-color={theme.primary} style:color={theme.onPrimary} style:border-radius={theme.rounded} > {i18n('test-text')} </div> </div>
<div class="gv-px-2 gv-flex gv-justify-end gv-border-t gv-pt-1"> <Button onclick={applyTheme} class="gv-py-1">{i18n('common.apply')}</Button> </div>
<div class="gv-px-2"> <Textarea rows={10} class="gv-w-full gv-overflow-y-scroll" value={formattedTheme} readonly /> </div></div>