Vue d'ensemble du système de thèmes
Le système de thèmes de lh-ui est à double source, c’est ce qu’il faut clarifier d’abord :
| Source | Contenu | Mode de consommation |
|---|---|---|
styles/theme/common/var.scss + var-dark.scss |
Les propriétés personnalisées CSS (--lh-*) |
var(--lh-*) dans les SCSS des composants, évaluées à l’exécution |
styles/theme/common/variables.scss |
Les variables SCSS ($lh-*) |
Cuites à la compilation en valeurs concrètes, utilisées pour les calculs (les fonctions de couleur, la composition des ombres, etc.) |
src/theme/themes/index.ts |
Le mappage vars des 5 préréglages |
Injection <style> à l’exécution (ConfigProvider / ThemeStudio) |
Les composants ne connaissent que les variables CSS. La raison d’être du système de thèmes côté JS est : injecter à l’exécution un système de jetons de conception dans un sous-arbre du DOM, afin que les composants changent de peau sans recompilation.
Les deux voies
Section intitulée « Les deux voies »1. Les thèmes préréglés (clés en main)
Section intitulée « 1. Les thèmes préréglés (clés en main) »Les cinq préréglages intégrés : 青花瓷 (porcelaine bleue et blanche) / 松花绿 (vert pin) / 朱砂 (cinabre) / 水墨 (encre de Chine) / 藤黄 (gamboge). La couche UI utilise buildPresetStyleText(vars) pour générer le texte <style> à écrire dans le DOM.
import { LH_THEME_PRESETS, buildPresetStyleText, LH_PRESET_THEME_STYLE_ID } from '@lh-ui/share/theme'
const preset = LH_THEME_PRESETS[0] // porcelaine bleue et blanche (cerblue)const styleText = buildPresetStyleText(preset.vars) // Assemblé en texte CSS// → injecté dans <style id="lh-preset-theme">Voir Préréglages de thème.
2. La personnalisation à l’exécution (themeVars)
Section intitulée « 2. La personnalisation à l’exécution (themeVars) »Le ConfigProvider reçoit themeVars, dont les clés sont abrégées (le préfixe --lh- est ajouté automatiquement) :
<lh-config-provider :theme-vars="{ 'primary-color': '#1677ff' }"> <App /></lh-config-provider>Voir Personnalisation à l’exécution.
Les cinq primitives d’exécution
Section intitulée « Les cinq primitives d’exécution »Les exports de src/theme/index.ts (l’implémentation est dans src/theme/themes/index.ts) :
| Primitive | Signature | Usage |
|---|---|---|
LH_THEME_PRESETS |
LhThemePreset[] |
Le tableau de tous les préréglages, parcouru par la documentation et le ConfigProvider |
registerTheme(preset) |
(preset: LhThemePreset) => void |
Enregistrer ou surcharger un préréglage (surcharge du même nom ; les préréglages intégrés peuvent aussi être surchargés) |
getThemePreset(name) |
(name: LhThemePresetName) => LhThemePreset | undefined |
Obtenir un préréglage par son nom |
getThemeVars(name) |
(name: LhThemePresetName) => LhThemeVars | undefined |
Obtenir ses vars par son nom (renvoie undefined si non enregistré) |
buildPresetStyleText(vars) |
(vars: Record<string, string>) => string |
Assembler les vars en un texte <style> injectable |
S’y ajoutent les primitives de calcul de contraste (utilisées par verify:theme et ThemeStudio) :
| Primitive | Signature | Usage |
|---|---|---|
getRelativeLuminance(color) |
(color: string | undefined) => number | undefined |
La luminance relative WCAG (renvoie undefined si la valeur n’est pas du hex) |
getContrastRatio(a, b) |
(a, b: string | undefined) => number | undefined |
Le contraste WCAG (de 1 à 21) |
buildPresetDarkVars(vars) |
(vars: Record<string, string>) => Record<string, string> |
Déduit les variables du bloc sombre à partir des vars du préréglage |
LH_PRESET_THEME_STYLE_ID |
'lh-preset-theme' |
L’id fixe de l’élément injecté |
Les types
Section intitulée « Les types »export type LhThemePresetName = | 'cerblue' | 'songhua' | 'zhusha' | 'shuimo' | 'golden' | (string & {}) // ← ouvre les noms personnalisés tout en gardant l'autocomplétion des cinq littérales
export interface LhThemePreset { name: LhThemePresetName label: string // Le nom en chinois labelEn: string // Le nom en anglais description: string // La description du thème vars: Record<string, string> // Le mappage des variables CSS}
export type LhThemeVars = Record<string, string>Le (string & {}) final de LhThemePresetName est une astuce TS : conserver l’autocomplétion des cinq premières littérales tout en autorisant registerTheme({ name: 'my-brand', ... }) à passer n’importe quelle chaîne. Les noms non enregistrés renvoient undefined dans getThemePreset / getThemeVars.
Contrainte d’injection : uniquement des <style>, jamais de style inline
Section intitulée « Contrainte d’injection : uniquement des <style>, jamais de style inline »C’est la contrainte la plus importante du système de thèmes, on s’y est déjà cassé les dents :
// ✅ Correct : écrire un élément <style>, la spécificité est 0,1,0const el = document.createElement('style')el.id = LH_PRESET_THEME_STYLE_IDel.textContent = buildPresetStyleText(vars)document.head.appendChild(el)
// ❌ Faux : écrire un style inline sur documentElement, la spécificité est 1,0,0,0Object.assign(document.documentElement.style, vars)Pourquoi : la spécificité d’un style inline est supérieure à n’importe quelle feuille de styles ; elle écrase tout le bloc de déclarations des variables sombres de var-dark.scss ([data-theme='dark'], .dark { --lh-*: ... }), ce qui fausse la palette du mode sombre — les variables sombres sont écrasées par la valeur claire inline.
La structure du texte généré par buildPresetStyleText :
:root { --lh-primary-color: #2B4C7C; /* ... toutes les vars du préréglage */}
[data-theme='dark'],.dark { --lh-primary-color-bg: color-mix(in srgb, var(--lh-primary-color) 25%, transparent); --lh-selected-color: color-mix(in srgb, var(--lh-primary-color) 40%, #fff); /* Les préréglages à primaire profonde éclaircissent la couleur primaire en supplément */}Le bloc sombre vient après le bloc :root ; à spécificité égale le second gagne — la valeur claire de --lh-selected-color déclarée dans :root par un préréglage à primaire claire ne fuit donc pas dans le mode sombre. Le contenu du bloc sombre est déduit par buildPresetDarkVars.
La dérivation sombre de buildPresetDarkVars
Section intitulée « La dérivation sombre de buildPresetDarkVars »Trois règles :
-
Redéfinir sans condition deux variables (par référence à
var(), évaluées sur l’élément où elles s’appliquent, suivant la couleur primaire courante) :--lh-primary-color-bg: color-mix(in srgb, var(--lh-primary-color) 25%, transparent)--lh-selected-color: color-mix(in srgb, var(--lh-primary-color) 40%, #fff)
-
L’éclaircissement des primaires profondes : si le contraste de
--lh-primary-colorpar rapport au fond de page sombre#1A1A2E(DARK_PAGE_BG) est inférieur à 3 (DARK_PRIMARY_CONTRAST_THRESHOLD), la couleur primaire est jugée profonde et éclaircie en supplément :--lh-primary-color: color-mix(in srgb, <la valeur littérale> 55%, #fff)— substitution directe de la valeur littérale, carcolor-mix(var(--lh-primary-color) …)créerait une boucle d’auto-référence CSS--lh-primary-color-hover: color-mix(in srgb, var(--lh-primary-color) 70%, #fff)--lh-primary-color-active: color-mix(in srgb, var(--lh-primary-color) 85%, #000)--lh-color-on-primary: #101410(INK_ON_LIGHTENED_PRIMARY) — une fois la couleur primaire éclaircie, le texte blanc n’atteint plus 3:1 de contraste, on passe au texte encre
-
Le calcul du contraste utilise
getContrastRatio(getRelativeLuminance(primary), getRelativeLuminance(DARK_PAGE_BG)). Les valeurs non hex (y compris les valeurs dynamiques contenantvar()) renvoientundefined, traitées comme « impossible à juger » — la couleur primaire des vars d’un préréglage doit donc être un hex littéral.
La synchronisation double-source
Section intitulée « La synchronisation double-source »Quand on modifie la couleur primaire d’un thème intégré, les deux côtés doivent être modifiés ensemble :
- Le
vars['--lh-primary-color']du préréglage danssrc/theme/themes/index.ts(pour l’injection JS à l’exécution) - Le
$lh-primary-colordestyles/theme/common/variables.scss(pour les calculs des SCSS des composants)
tests/unit/theme.spec.ts contient une régression de cohérence ; lancer pnpm test intercepte toute désynchronisation.
Ce que le thème ne peut pas faire
Section intitulée « Ce que le thème ne peut pas faire »- Il ne peut pas modifier la hauteur des contrôles : les jetons
--lh-control-height-*ne sont pas encore en place (les hauteurs des contrôles ne sont pas unifiées, inscrites dans le « chantier des jetons de hauteur » en attente d’un changeset ultérieur). - Il ne peut pas modifier la structure des composants : le thème ne fait que changer des valeurs de jetons ; la mise en page et la structure DOM restent inchangées.
- Les motifs sont un système indépendant : voir Motifs style chinois.
L’aperçu des préréglages
Section intitulée « L’aperçu des préréglages »cerblue · Ceramic Blue青花瓷釉下彩,清雅素净
--lh-primary-color#2B4C7C--lh-primary-color-hover#3D649E--lh-primary-color-active#1F3A5F--lh-primary-color-bg#E8EDF3--lh-primary-color-border#D4DCE8
songhua · Pine Green竹林修竹,生机勃勃
--lh-primary-color#5B8C5A--lh-primary-color-hover#7BB079--lh-primary-color-active#4F8F4C--lh-primary-color-bg#E8F5E8--lh-primary-color-border#D0E8CF--lh-color-on-primary#101410--lh-selected-color#456F44
zhusha · Vermilion传统印章朱砂红,喜庆庄重
--lh-primary-color#B23A3A--lh-primary-color-hover#C45050--lh-primary-color-active#9A3030--lh-primary-color-bg#FDF0F0--lh-primary-color-border#F0D8D8
shuimo · Ink Wash水墨丹青,素雅极简
--lh-primary-color#2C2C2C--lh-primary-color-hover#5A5A5A--lh-primary-color-active#1A1A1A--lh-primary-color-bg#F5F5F5--lh-primary-color-border#E0E0E0
golden · Golden Autumn深秋枫黄,温暖金黄
--lh-primary-color#B8860B--lh-primary-color-hover#D4A843--lh-primary-color-active#A87F0A--lh-primary-color-bg#FBF6EA--lh-primary-color-border#F0E8D0--lh-color-on-primary#1E1A16--lh-selected-color#82600A
Documentation connexe
Section intitulée « Documentation connexe »- Préréglages de thème — le détail des 5 préréglages intégrés
- Jetons de conception — la table complète des 185 jetons clairs + 118 sombres
- Personnalisation à l’exécution — themeVars / componentTheme / patternComponents
- Mode sombre — le mécanisme et le piège de la cuisson à la compilation
- Motifs style chinois — les 6 textures intégrées
Copyright © 2024-2026 FengMo WenXue