From 1237e15cd4692aa68705c26617e4f8b736e2c8d6 Mon Sep 17 00:00:00 2001 From: Kilian Panot Date: Thu, 15 Aug 2024 18:14:59 +0900 Subject: [PATCH] feat(design): support multi template file --- .../src/style/dark-theme/dark-theme.scss | 110 +++++++++--------- .../style/horizon-theme/horizon-theme.scss | 92 +++++++-------- apps/showcase/src/style/theme.scss | 102 ++++++++-------- .../design/builders/generate-css/index.ts | 19 ++- .../design/builders/generate-css/schema.json | 14 ++- .../design/builders/generate-css/schema.ts | 2 +- .../design-token-template.helper.spec.ts | 60 ++++++++++ .../parsers/design-token-template.helpers.ts | 33 ++++++ .../src/core/design-token/parsers/index.ts | 1 + packages/@o3r/design/tsconfig.spec.json | 3 +- 10 files changed, 276 insertions(+), 160 deletions(-) create mode 100644 packages/@o3r/design/src/core/design-token/parsers/design-token-template.helper.spec.ts create mode 100644 packages/@o3r/design/src/core/design-token/parsers/design-token-template.helpers.ts diff --git a/apps/showcase/src/style/dark-theme/dark-theme.scss b/apps/showcase/src/style/dark-theme/dark-theme.scss index c26ebe206a..9b56786801 100644 --- a/apps/showcase/src/style/dark-theme/dark-theme.scss +++ b/apps/showcase/src/style/dark-theme/dark-theme.scss @@ -2,100 +2,100 @@ :root { /* --- BEGIN THEME Auto-generated --- */ +--bs-body-bg: #000000; +--bs-body-color: #ffffff; +.card { --bs-card-bg: #000000; } +.card { --bs-card-color: #ffffff; } +.nav-pills { --bs-nav-pills-link-active-color: var(--color-primary); } +.navbar-toggler { --bs-navbar-color: var(--bs-primary-bg-subtle); } /* Application Primary color */ --bs-primary: var(--color-primary); --bs-primary-800: var(--color-primary-800); -.nav-pills { --bs-nav-pills-link-active-color: var(--color-primary); } -.navbar-toggler { --bs-navbar-color: var(--bs-primary-bg-subtle); } ---color-primary-50: #e6edfc; ---color-primary-100: #c2d3f7; ---color-primary-200: #99b6f2; ---color-primary-300: #7098ed; ---color-primary-400: #5182e9; ---color-primary-500: #326ce5; ---color-primary-600: #2d64e2; ---color-primary-700: #2659de; ---color-primary-800: #1f4fda; ---color-primary-900: #133dd3; ---color-primary-A700: #839aff; ---color-primary-A400: #9caeff; ---color-primary-A200: #cfd8ff; ---color-primary-A100: #000000; ---color-primary: var(--color-primary-500); ---color-accent-50: #e8f7ff; +--bs-tertiary-bg: #333333; +--bs-tertiary-bg-blue: 51; +--bs-tertiary-bg-green: 51; +--bs-tertiary-bg-red: 51; +--bs-tertiary-bg-rgb: var(--bs-tertiary-bg-red), var(--bs-tertiary-bg-green), var(--bs-tertiary-bg-blue); +--color-accent: var(--color-accent-500); --color-accent-100: #c5eaff; --color-accent-200: #9eddff; --color-accent-300: #77cfff; --color-accent-400: #5ac4ff; +--color-accent-50: #e8f7ff; --color-accent-500: #3dbaff; --color-accent-600: #37b3ff; --color-accent-700: #2fabff; --color-accent-800: #27a3ff; --color-accent-900: #1a94ff; ---color-accent-A700: #b2d8ff; ---color-accent-A400: #cbe5ff; ---color-accent-A200: #feffff; --color-accent-A100: #ffffff; ---color-accent: var(--color-accent-500); ---color-highlight-50: #f4e5ff; +--color-accent-A200: #feffff; +--color-accent-A400: #cbe5ff; +--color-accent-A700: #b2d8ff; +--color-highlight: var(--color-highlight-500); --color-highlight-100: #e4beff; --color-highlight-200: #d292ff; --color-highlight-300: #c066ff; --color-highlight-400: #b346ff; +--color-highlight-50: #f4e5ff; --color-highlight-500: #a525ff; --color-highlight-600: #9d21ff; --color-highlight-700: #931bff; --color-highlight-800: #8a16ff; --color-highlight-900: #790dff; ---color-highlight-A700: #ccaaff; ---color-highlight-A400: #dcc3ff; ---color-highlight-A200: #faf6ff; --color-highlight-A100: #ffffff; ---color-highlight: var(--color-highlight-500); ---color-warn-50: #fdede4; +--color-highlight-A200: #faf6ff; +--color-highlight-A400: #dcc3ff; +--color-highlight-A700: #ccaaff; +--color-neutral-alabaster: #f5f5f7; +--color-neutral-black: #000000; +--color-neutral-gray-dark: #616161; +--color-neutral-gray-light: #eff5f9; +--color-neutral-iron: #dbdff8; +--color-neutral-silver: #f8f8f8; +--color-neutral-white: #ffffff; +--color-out-of-palette-comet: #1b2073; +--color-out-of-palette-green: #067f28; +--color-primary: var(--color-primary-500); +--color-primary-100: #c2d3f7; +--color-primary-200: #99b6f2; +--color-primary-300: #7098ed; +--color-primary-400: #5182e9; +--color-primary-50: #e6edfc; +--color-primary-500: #326ce5; +--color-primary-600: #2d64e2; +--color-primary-700: #2659de; +--color-primary-800: #1f4fda; +--color-primary-900: #133dd3; +--color-primary-A100: #000000; +--color-primary-A200: #cfd8ff; +--color-primary-A400: #9caeff; +--color-primary-A700: #839aff; +--color-warn: var(--color-warn-500); --color-warn-100: #fbd1bb; --color-warn-200: #f8b38e; --color-warn-300: #f59460; --color-warn-400: #f27d3e; +--color-warn-50: #fdede4; --color-warn-500: #f0661c; --color-warn-600: #ee5e19; --color-warn-700: #ec5314; --color-warn-800: #e94911; --color-warn-900: #dc3709; ---color-warn-A700: #ffa28f; ---color-warn-A400: #ffb7a8; ---color-warn-A200: #ffe1db; --color-warn-A100: #ffffff; ---color-warn: var(--color-warn-500); ---color-out-of-palette-green: #067f28; ---color-out-of-palette-comet: #1b2073; ---color-neutral-iron: #dbdff8; ---color-neutral-alabaster: #f5f5f7; ---color-neutral-white: #ffffff; ---color-neutral-silver: #f8f8f8; ---color-neutral-gray-light: #eff5f9; ---color-neutral-black: #000000; ---color-neutral-gray-dark: #616161; +--color-warn-A200: #ffe1db; +--color-warn-A400: #ffb7a8; +--color-warn-A700: #ffa28f; +--radius-10: 10px; +--radius-2: 2px; +--radius-25: 25px; +--radius-5: 5px; --spacing-0: 0px; --spacing-1: 1px; ---spacing-5: 5px; --spacing-10: 10px; --spacing-20: 20px; --spacing-30: 30px; --spacing-40: 40px; ---radius-2: 2px; ---radius-5: 5px; ---radius-10: 10px; ---radius-25: 25px; ---bs-body-bg: #000000; ---bs-body-color: #ffffff; ---bs-tertiary-bg: #333333; ---bs-tertiary-bg-red: 51; ---bs-tertiary-bg-green: 51; ---bs-tertiary-bg-blue: 51; ---bs-tertiary-bg-rgb: var(--bs-tertiary-bg-red), var(--bs-tertiary-bg-green), var(--bs-tertiary-bg-blue); -.card { --bs-card-color: #ffffff; } -.card { --bs-card-bg: #000000; } +--spacing-5: 5px; /* --- END THEME Auto-generated --- */ .nav.nav-pills .nav-link { diff --git a/apps/showcase/src/style/horizon-theme/horizon-theme.scss b/apps/showcase/src/style/horizon-theme/horizon-theme.scss index 49c91c62c7..7bef32f0bf 100644 --- a/apps/showcase/src/style/horizon-theme/horizon-theme.scss +++ b/apps/showcase/src/style/horizon-theme/horizon-theme.scss @@ -1,90 +1,90 @@ :root { /* --- BEGIN THEME Auto-generated --- */ +.nav-pills { --bs-nav-pills-link-active-color: var(--color-primary); } +.navbar-toggler { --bs-navbar-color: var(--bs-primary-bg-subtle); } /* Application Primary color */ --bs-primary: var(--color-primary); --bs-primary-800: var(--color-primary-800); -.nav-pills { --bs-nav-pills-link-active-color: var(--color-primary); } -.navbar-toggler { --bs-navbar-color: var(--bs-primary-bg-subtle); } ---color-primary-50: #f0e4ea; ---color-primary-100: #d8bbca; ---color-primary-200: #bf8da6; ---color-primary-300: #a55f82; ---color-primary-400: #913d68; ---color-primary-500: #7e1b4d; ---color-primary-600: #761846; ---color-primary-700: #6b143d; ---color-primary-800: #611034; ---color-primary-900: #4e0825; ---color-primary-A700: #ff035b; ---color-primary-A400: #ff1d6c; ---color-primary-A200: #ff508d; ---color-primary-A100: #ff83ae; ---color-primary: var(--color-primary-500); ---color-accent-50: #fdf8e6; +--color-accent: var(--color-accent-500); --color-accent-100: #fbedc2; --color-accent-200: #f8e199; --color-accent-300: #f5d470; --color-accent-400: #f3cb51; +--color-accent-50: #fdf8e6; --color-accent-500: #f1c232; --color-accent-600: #efbc2d; --color-accent-700: #edb426; --color-accent-800: #ebac1f; --color-accent-900: #e79f13; ---color-accent-A700: #ffd896; ---color-accent-A400: #ffe2af; ---color-accent-A200: #fff4e2; --color-accent-A100: #ffffff; ---color-accent: var(--color-accent-500); ---color-highlight-50: #e1e1f4; +--color-accent-A200: #fff4e2; +--color-accent-A400: #ffe2af; +--color-accent-A700: #ffd896; +--color-highlight: var(--color-highlight-500); --color-highlight-100: #b5b3e3; --color-highlight-200: #8481d1; --color-highlight-300: #524ebf; --color-highlight-400: #2d28b1; +--color-highlight-50: #e1e1f4; --color-highlight-500: #0802a3; --color-highlight-600: #07029b; --color-highlight-700: #060191; --color-highlight-800: #040188; --color-highlight-900: #020177; ---color-highlight-A700: #2525ff; ---color-highlight-A400: #3f3fff; ---color-highlight-A200: #7272ff; --color-highlight-A100: #a5a5ff; ---color-highlight: var(--color-highlight-500); ---color-warn-50: #fdede4; +--color-highlight-A200: #7272ff; +--color-highlight-A400: #3f3fff; +--color-highlight-A700: #2525ff; +--color-neutral-alabaster: #f5f5f7; +--color-neutral-black: #000000; +--color-neutral-gray-dark: #616161; +--color-neutral-gray-light: #f0f0f0; +--color-neutral-iron: #e4dbd1; +--color-neutral-silver: #f8f8f8; +--color-neutral-white: #ffffff; +--color-out-of-palette-comet: #6a5a48; +--color-out-of-palette-green: #28ad4e; +--color-primary: var(--color-primary-500); +--color-primary-100: #d8bbca; +--color-primary-200: #bf8da6; +--color-primary-300: #a55f82; +--color-primary-400: #913d68; +--color-primary-50: #f0e4ea; +--color-primary-500: #7e1b4d; +--color-primary-600: #761846; +--color-primary-700: #6b143d; +--color-primary-800: #611034; +--color-primary-900: #4e0825; +--color-primary-A100: #ff83ae; +--color-primary-A200: #ff508d; +--color-primary-A400: #ff1d6c; +--color-primary-A700: #ff035b; +--color-warn: var(--color-warn-500); --color-warn-100: #fbd1bb; --color-warn-200: #f8b38e; --color-warn-300: #f59460; --color-warn-400: #f27d3e; +--color-warn-50: #fdede4; --color-warn-500: #f0661c; --color-warn-600: #ee5e19; --color-warn-700: #ec5314; --color-warn-800: #e94911; --color-warn-900: #dc3709; ---color-warn-A700: #ffa28f; ---color-warn-A400: #ffb7a8; ---color-warn-A200: #ffe1db; --color-warn-A100: #ffffff; ---color-warn: var(--color-warn-500); ---color-out-of-palette-green: #28ad4e; ---color-out-of-palette-comet: #6a5a48; ---color-neutral-iron: #e4dbd1; ---color-neutral-alabaster: #f5f5f7; ---color-neutral-white: #ffffff; ---color-neutral-silver: #f8f8f8; ---color-neutral-gray-light: #f0f0f0; ---color-neutral-black: #000000; ---color-neutral-gray-dark: #616161; +--color-warn-A200: #ffe1db; +--color-warn-A400: #ffb7a8; +--color-warn-A700: #ffa28f; +--radius-10: 10px; +--radius-2: 2px; +--radius-25: 25px; +--radius-5: 5px; --spacing-0: 0px; --spacing-1: 1px; ---spacing-5: 5px; --spacing-10: 10px; --spacing-20: 20px; --spacing-30: 30px; --spacing-40: 40px; ---radius-2: 2px; ---radius-5: 5px; ---radius-10: 10px; ---radius-25: 25px; +--spacing-5: 5px; /* --- END THEME Auto-generated --- */ } \ No newline at end of file diff --git a/apps/showcase/src/style/theme.scss b/apps/showcase/src/style/theme.scss index 1c8a27030b..aa29c7d32f 100644 --- a/apps/showcase/src/style/theme.scss +++ b/apps/showcase/src/style/theme.scss @@ -1,95 +1,95 @@ :root { /* --- BEGIN THEME Auto-generated --- */ +.nav-pills { --bs-nav-pills-link-active-color: var(--color-primary); } +.navbar-toggler { --bs-navbar-color: var(--bs-primary-bg-subtle); } /* Application Primary color */ --bs-primary: var(--color-primary); --bs-primary-800: var(--color-primary-800); -.nav-pills { --bs-nav-pills-link-active-color: var(--color-primary); } -.navbar-toggler { --bs-navbar-color: var(--bs-primary-bg-subtle); } ---color-primary-50: #ebf3ff; ---color-primary-100: #c5d5f9; ---color-primary-200: #9fc6ff; ---color-primary-300: #61a2ff; ---color-primary-400: #3a8bff; ---color-primary-500: #0c66e1; ---color-primary-600: #104ea4; ---color-primary-700: #0a2f62; ---color-primary-800: #000835; ---color-primary-900: #000521; ---color-primary-A700: #839aff; ---color-primary-A400: #9caeff; ---color-primary-A200: #cfd8ff; ---color-primary-A100: #ffffff; -/* Primary palette */ ---color-primary: var(--color-primary-500); ---color-accent-50: #e8f7ff; +/* Accent palette */ +--color-accent: var(--color-accent-500); --color-accent-100: #c5eaff; --color-accent-200: #9eddff; --color-accent-300: #77cfff; --color-accent-400: #5ac4ff; +--color-accent-50: #e8f7ff; --color-accent-500: #3dbaff; --color-accent-600: #37b3ff; --color-accent-700: #2fabff; --color-accent-800: #27a3ff; --color-accent-900: #1a94ff; ---color-accent-A700: #b2d8ff; ---color-accent-A400: #cbe5ff; ---color-accent-A200: #feffff; --color-accent-A100: #ffffff; -/* Accent palette */ ---color-accent: var(--color-accent-500); ---color-highlight-50: #f4e5ff; +--color-accent-A200: #feffff; +--color-accent-A400: #cbe5ff; +--color-accent-A700: #b2d8ff; +/* Highlight palette */ +--color-highlight: var(--color-highlight-500); --color-highlight-100: #e4beff; --color-highlight-200: #d292ff; --color-highlight-300: #c066ff; --color-highlight-400: #b346ff; +--color-highlight-50: #f4e5ff; --color-highlight-500: #a525ff; --color-highlight-600: #9d21ff; --color-highlight-700: #931bff; --color-highlight-800: #8a16ff; --color-highlight-900: #790dff; ---color-highlight-A700: #ccaaff; ---color-highlight-A400: #dcc3ff; ---color-highlight-A200: #faf6ff; --color-highlight-A100: #ffffff; -/* Highlight palette */ ---color-highlight: var(--color-highlight-500); ---color-warn-50: #fdede4; +--color-highlight-A200: #faf6ff; +--color-highlight-A400: #dcc3ff; +--color-highlight-A700: #ccaaff; +--color-neutral-alabaster: #f5f5f7; +--color-neutral-black: #000000; +--color-neutral-custom: var(--color-primary); +--color-neutral-gray-dark: #616161; +--color-neutral-gray-light: #eff5f9; +--color-neutral-iron: #dbdff8; +--color-neutral-silver: #f8f8f8; +--color-neutral-white: #ffffff; +--color-out-of-palette-comet: #1b2073; +--color-out-of-palette-green: #067f28; +/* Primary palette */ +--color-primary: var(--color-primary-500); +--color-primary-100: #c5d5f9; +--color-primary-200: #9fc6ff; +--color-primary-300: #61a2ff; +--color-primary-400: #3a8bff; +--color-primary-50: #ebf3ff; +--color-primary-500: #0c66e1; +--color-primary-600: #104ea4; +--color-primary-700: #0a2f62; +--color-primary-800: #000835; +--color-primary-900: #000521; +--color-primary-A100: #ffffff; +--color-primary-A200: #cfd8ff; +--color-primary-A400: #9caeff; +--color-primary-A700: #839aff; +/* Warn palette */ +--color-warn: var(--color-warn-500); --color-warn-100: #fbd1bb; --color-warn-200: #f8b38e; --color-warn-300: #f59460; --color-warn-400: #f27d3e; +--color-warn-50: #fdede4; --color-warn-500: #f0661c; --color-warn-600: #ee5e19; --color-warn-700: #ec5314; --color-warn-800: #e94911; --color-warn-900: #dc3709; ---color-warn-A700: #ffa28f; ---color-warn-A400: #ffb7a8; ---color-warn-A200: #ffe1db; --color-warn-A100: #ffffff; -/* Warn palette */ ---color-warn: var(--color-warn-500); ---color-out-of-palette-green: #067f28; ---color-out-of-palette-comet: #1b2073; ---color-neutral-iron: #dbdff8; ---color-neutral-alabaster: #f5f5f7; ---color-neutral-white: #ffffff; ---color-neutral-silver: #f8f8f8; ---color-neutral-gray-light: #eff5f9; ---color-neutral-black: #000000; ---color-neutral-gray-dark: #616161; ---color-neutral-custom: var(--color-primary); +--color-warn-A200: #ffe1db; +--color-warn-A400: #ffb7a8; +--color-warn-A700: #ffa28f; +--radius-10: 10px; +--radius-2: 2px; +--radius-25: 25px; +--radius-5: 5px; --spacing-0: 0px; --spacing-1: 1px; ---spacing-5: 5px; --spacing-10: 10px; --spacing-20: 20px; --spacing-30: 30px; --spacing-40: 40px; ---radius-2: 2px; ---radius-5: 5px; ---radius-10: 10px; ---radius-25: 25px; +--spacing-5: 5px; /* --- END THEME Auto-generated --- */ } diff --git a/packages/@o3r/design/builders/generate-css/index.ts b/packages/@o3r/design/builders/generate-css/index.ts index c69b59352a..631c5ecdd0 100644 --- a/packages/@o3r/design/builders/generate-css/index.ts +++ b/packages/@o3r/design/builders/generate-css/index.ts @@ -6,13 +6,14 @@ import { getMetadataStyleContentUpdater, getMetadataTokenDefinitionRenderer, getSassTokenDefinitionRenderer, + mergeDesignTokenTemplates, parseDesignTokenFile, renderDesignTokens, tokenVariableNameSassRenderer } from '../../src/public_api'; import type { DesignTokenGroupTemplate, DesignTokenRendererOptions, DesignTokenVariableSet, DesignTokenVariableStructure, TokenKeyRenderer } from '../../src/public_api'; import { resolve } from 'node:path'; -import * as globby from 'globby'; +import { sync } from 'globby'; import { EOL } from 'node:os'; import { readFile } from 'node:fs/promises'; @@ -21,6 +22,9 @@ import { readFile } from 'node:fs/promises'; * @param options */ export default createBuilder(async (options, context): Promise => { + const templateFilePaths = options.templateFile + && (typeof options.templateFile === 'string' ? [options.templateFile] : options.templateFile).map((templateFile) => resolve(context.workspaceRoot, templateFile)) + || undefined; const designTokenFilePatterns = Array.isArray(options.designTokenFilePatterns) ? options.designTokenFilePatterns : [options.designTokenFilePatterns]; const determineFileToUpdate = options.output ? () => resolve(context.workspaceRoot, options.output!) : (token: DesignTokenVariableStructure) => { @@ -69,8 +73,15 @@ export default createBuilder(async (options, contex }; const execute = async (renderDesignTokenOptions: DesignTokenRendererOptions): Promise => { - const template = options.templateFile ? JSON.parse(await readFile(resolve(context.workspaceRoot, options.templateFile), { encoding: 'utf-8' })) as DesignTokenGroupTemplate : undefined; - const files = (await globby(designTokenFilePatterns, { cwd: context.workspaceRoot, absolute: true })); + let template: DesignTokenGroupTemplate | undefined; + if (templateFilePaths) { + const templateFiles = await Promise.all( + templateFilePaths + .map(async (templateFile) => JSON.parse(await readFile(templateFile, { encoding: 'utf-8' })) as DesignTokenGroupTemplate) + ); + template = templateFiles.reduce((acc, cur) => mergeDesignTokenTemplates(acc, cur), {}); + } + const files = sync(designTokenFilePatterns, { cwd: context.workspaceRoot, absolute: true }); const inDependencies = designTokenFilePatterns .filter((pathName) => !pathName.startsWith('.') && !pathName.startsWith('*') && !pathName.startsWith('/')) @@ -137,7 +148,7 @@ export default createBuilder(async (options, contex await import('chokidar') .then((chokidar) => chokidar.watch([ ...designTokenFilePatterns.map((p) => resolve(context.workspaceRoot, p)), - ...(options.templateFile ? [resolve(context.workspaceRoot, options.templateFile)] : []) + ...(templateFilePaths || []) ])) .then((watcher) => watcher.on('all', async () => { const res = await executeMultiRenderer(); diff --git a/packages/@o3r/design/builders/generate-css/schema.json b/packages/@o3r/design/builders/generate-css/schema.json index c9dabe6d9b..8efc5f54c1 100644 --- a/packages/@o3r/design/builders/generate-css/schema.json +++ b/packages/@o3r/design/builders/generate-css/schema.json @@ -60,8 +60,18 @@ "description": "Determine if the process should stop in case of Token duplication" }, "templateFile": { - "type": "string", - "description": "Path to a template file to apply as default configuration to a Design Token extension" + "description": "Path to a template file(s) to apply as default configuration to a Design Token extension. In case of multiple files, the JSON will be deeply merged in the order defined by the list.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] }, "prefix": { "type": "string", diff --git a/packages/@o3r/design/builders/generate-css/schema.ts b/packages/@o3r/design/builders/generate-css/schema.ts index 26e89bd0f6..a3b8a15c05 100644 --- a/packages/@o3r/design/builders/generate-css/schema.ts +++ b/packages/@o3r/design/builders/generate-css/schema.ts @@ -51,5 +51,5 @@ export interface GenerateCssSchematicsSchema extends SchematicOptionObject { variableType?: 'css' | 'sass'; /** Path to a template file to apply as default configuration to a Design Token extension */ - templateFile?: string; + templateFile?: string | string[]; } diff --git a/packages/@o3r/design/src/core/design-token/parsers/design-token-template.helper.spec.ts b/packages/@o3r/design/src/core/design-token/parsers/design-token-template.helper.spec.ts new file mode 100644 index 0000000000..01b4b57632 --- /dev/null +++ b/packages/@o3r/design/src/core/design-token/parsers/design-token-template.helper.spec.ts @@ -0,0 +1,60 @@ +import { mergeDesignTokenTemplates } from './design-token-template.helpers'; +import type { DesignTokenGroupTemplate } from '../..'; + +describe('mergeDesignTokenTemplates function', () => { + + it('should merge object', () => { + const templateA: DesignTokenGroupTemplate = { + field1: { + field2: { + $extensions: { + o3rPrivate: true + } + } + } as DesignTokenGroupTemplate + }; + + const templateB: DesignTokenGroupTemplate = { + field1: { + field3: { + $extensions: { + o3rPrivate: false + } + } + } as DesignTokenGroupTemplate + }; + + const result: any = mergeDesignTokenTemplates(templateA, templateB); + expect(result.field1.field2).toBeDefined(); + expect(result.field1.field3).toBeDefined(); + expect(result.field1.field2.$extensions.o3rPrivate).toBe(true); + expect(result.field1.field3.$extensions.o3rPrivate).toBe(false); + }); + + it('should override data in case on config', () => { + const templateA: DesignTokenGroupTemplate = { + field1: { + field2: { + $extensions: { + o3rPrivate: true + } + } + } as DesignTokenGroupTemplate + }; + + const templateB: DesignTokenGroupTemplate = { + field1: { + field2: { + $extensions: { + o3rPrivate: false + } + } + } as DesignTokenGroupTemplate + }; + + const result: any = mergeDesignTokenTemplates(templateA, templateB); + expect(result.field1.field2).toBeDefined(); + expect(result.field1.field2.$extensions.o3rPrivate).toBe(false); + }); + +}); diff --git a/packages/@o3r/design/src/core/design-token/parsers/design-token-template.helpers.ts b/packages/@o3r/design/src/core/design-token/parsers/design-token-template.helpers.ts new file mode 100644 index 0000000000..faadae8205 --- /dev/null +++ b/packages/@o3r/design/src/core/design-token/parsers/design-token-template.helpers.ts @@ -0,0 +1,33 @@ +import type { DesignTokenGroupExtensions, DesignTokenGroupTemplate } from '../design-token-specification.interface'; + +/** + * Merge TemplateB into TemplateA + * @param templateA + * @param templateB + */ +export const mergeDesignTokenTemplates = < + A extends DesignTokenGroupExtensions = DesignTokenGroupExtensions, + B extends DesignTokenGroupExtensions = DesignTokenGroupExtensions> + (templateA: DesignTokenGroupTemplate, templateB: DesignTokenGroupTemplate): DesignTokenGroupTemplate => { + + const entries = Object.entries(templateB); + const template = { + ...templateA, + ...Object.fromEntries( + entries + .filter(([key]) => key.startsWith('$')) + ) + }; + + return entries + .filter(([key]) => !key.startsWith('$')) + .reduce((acc, [key, value]) => { + const node = acc[key]; + if (node && typeof node === 'object' && typeof value === 'object') { + acc[key] = mergeDesignTokenTemplates(node as DesignTokenGroupTemplate, value as DesignTokenGroupTemplate); + } else { + acc[key] = value; + } + return acc; + }, template as DesignTokenGroupTemplate); +}; diff --git a/packages/@o3r/design/src/core/design-token/parsers/index.ts b/packages/@o3r/design/src/core/design-token/parsers/index.ts index 618a3de3a7..b4e610ba69 100644 --- a/packages/@o3r/design/src/core/design-token/parsers/index.ts +++ b/packages/@o3r/design/src/core/design-token/parsers/index.ts @@ -1,2 +1,3 @@ export * from './design-token-parser.interface'; export * from './design-token.parser'; +export * from './design-token-template.helpers'; diff --git a/packages/@o3r/design/tsconfig.spec.json b/packages/@o3r/design/tsconfig.spec.json index ffc1f54a1d..fa32740a8b 100644 --- a/packages/@o3r/design/tsconfig.spec.json +++ b/packages/@o3r/design/tsconfig.spec.json @@ -7,7 +7,8 @@ }, "include": [ "./src/**/*.spec.ts", - "./schematics/**/*.spec.ts" + "./schematics/**/*.spec.ts", + "./builders/**/*.spec.ts" ], "exclude": [], "references": [