Skip to content

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.ts

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

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

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>

Aller plus loin