diff --git a/core/package.json b/core/package.json index 7869748e203..bd60895f0fe 100644 --- a/core/package.json +++ b/core/package.json @@ -76,11 +76,12 @@ "stylelint-order": "^4.1.0" }, "scripts": { - "build": "npm run clean && npm run build.css && stencil build --es5 --docs-json dist/docs.json", + "build": "npm run clean && npm run build.css && npm run build.newTokens && stencil build --es5 --docs-json dist/docs.json", "build.css": "npm run css.sass && npm run css.minify", "build.debug": "npm run clean && stencil build --debug", "build.docs.json": "stencil build --docs-json dist/docs.json", "build.tokens": "npx build.tokens --config scripts/tokens/index.mjs && npm run prettier.tokens", + "build.newTokens": "esbuild src/themes/*/*.tokens.ts --bundle --format=esm --platform=browser --target=es2017 --outdir=www/themes --outbase=src/themes && esbuild src/utils/theme.ts --bundle --format=esm --platform=browser --target=es2017 --outfile=www/themes/utils/theme.js", "clean": "node scripts/clean.js", "css.minify": "cleancss -O2 -o ./css/ionic.bundle.css ./css/ionic.bundle.css", "css.sass": "sass --embed-sources --style compressed src/css:./css", @@ -94,7 +95,7 @@ "prerender.e2e": "node scripts/testing/prerender.js", "prettier": "prettier \"./src/**/*.{html,ts,tsx,js,jsx,scss}\"", "prettier.tokens": "prettier \"./src/foundations/*.{scss, html}\" --write --cache", - "start": "npm run build.css && stencil build --dev --watch --serve", + "start": "npm run build.css && npm run build.newTokens && stencil build --dev --watch --serve", "test": "npm run test.spec && npm run test.e2e", "test.spec": "stencil test --spec --max-workers=2", "test.e2e": "npx playwright test", diff --git a/core/scripts/testing/scripts.js b/core/scripts/testing/scripts.js index 1fe2acdf717..ae5355edfae 100644 --- a/core/scripts/testing/scripts.js +++ b/core/scripts/testing/scripts.js @@ -15,28 +15,15 @@ } /** - * The term `palette` is used to as a param to match the - * Ionic docs, plus here is already a `ionic:theme` query being - * used for `md`, `ios`, and `ionic` themes. - */ - const palette = window.location.search.match(/palette=([a-z]+)/); - if (palette && palette[1] !== 'light') { - const linkTag = document.createElement('link'); - linkTag.setAttribute('rel', 'stylesheet'); - linkTag.setAttribute('type', 'text/css'); - linkTag.setAttribute('href', `/css/palettes/${palette[1]}.always.css`); - document.head.appendChild(linkTag); - } - - /** - * The `ionic` theme uses a different stylesheet than the `iOS` and `md` themes. - * This is to ensure that the `ionic` theme is loaded when the `ionic:theme=ionic` - * or when the HTML tag has the `theme="ionic"` attribute. This is useful for - * the snapshot tests, where the `ionic` theme is not loaded by default. + * The `theme` query param is used to load a specific theme. + * This can be `ionic`, `ios`, or `md`. Default to `md` for tests. */ const themeQuery = window.location.search.match(/ionic:theme=([a-z]+)/); const themeAttr = document.documentElement.getAttribute('theme'); + const themeName = themeQuery?.[1] || themeAttr || 'md'; + // TODO(): Remove this when the tokens are working for all components + // and the themes all use the same bundle if ((themeQuery && themeQuery[1] === 'ionic') || themeAttr === 'ionic') { const ionicThemeLinkTag = document.querySelector('link[href*="css/ionic/bundle.ionic.css"]'); @@ -54,6 +41,51 @@ } } + /** + * The `palette` query param is used to load a specific palette + * for the theme. This can be `light`, `dark`, `high-contrast`, + * or `high-contrast-dark`. Default to `light` for tests. + */ + const paletteQuery = window.location.search.match(/palette=([a-z]+)/); + const paletteName = paletteQuery?.[1] || 'light'; + + // Load theme tokens dynamically instead of stylesheets + if (themeName && ['ionic', 'ios', 'md'].includes(themeName)) { + loadThemeTokens(themeName, paletteName); + } + + async function loadThemeTokens(themeName, paletteName) { + try { + // Dynamically import the theme tokens + const defaultTokens = await import(`/www/themes/${themeName}/default.tokens.js`); + const theme = defaultTokens.defaultTheme; + + // If a specific palette is requested, modify the palette structure + // to set the enabled property to 'always' + if (paletteName === 'dark' && theme.palette?.dark) { + theme.palette.dark.enabled = 'always'; + } + + // Apply the theme tokens to Ionic config + window.Ionic = window.Ionic || {}; + window.Ionic.config = window.Ionic.config || {}; + window.Ionic.config.customTheme = theme; + + // Re-apply the global theme + if (window.Ionic.config.get && window.Ionic.config.set) { + import('/www/themes/utils/theme.js').then(themeModule => { + themeModule.applyGlobalTheme(theme); + }).catch(() => { + console.info('Could not reapply theme - theme module not found'); + }); + } + + console.info(`Loaded ${themeName} theme with palette ${paletteName}:`, theme); + } catch (error) { + console.info(`Failed to load theme tokens for ${themeName}:`, error); + } + } + window.Ionic = window.Ionic || {}; window.Ionic.config = window.Ionic.config || {}; diff --git a/core/scripts/testing/styles.css b/core/scripts/testing/styles.css index 295303dbd1c..708a47e0680 100644 --- a/core/scripts/testing/styles.css +++ b/core/scripts/testing/styles.css @@ -36,31 +36,21 @@ --ion-dynamic-font: var(--ion-default-dynamic-font); } +/* TODO(FW-6745): Remove this after adding the md tokens */ :root, html, -html.md.md { +html.md.md:not(.ionic) { --ion-font-family: Roboto, "mdTestingFont", sans-serif; font-family: Roboto, "mdTestingFont", sans-serif; } +/* TODO(FW-6744): Remove this after adding the ios tokens */ :root.ios.ios, html.ios.ios { --ion-font-family: -apple-system, BlinkMacSystemFont, "iosTestingFont", sans-serif; font-family: -apple-system, BlinkMacSystemFont, "iosTestingFont", sans-serif; } -/* Override above styles for testing scopes */ -:root.ionic, -:root.ionic.ios, -:root.ionic.md, -html.ionic, -html.ionic.ios, -html.ionic.md { - /* TODO: remove this with the ionic theme updates */ - --ion-background-color: var(--background); - --ion-font-family: initial; -} - /** * Button styles should only be applied * to native buttons that are not part of the diff --git a/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts b/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts index 0a2cc851910..30b2fecb292 100644 --- a/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts +++ b/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts @@ -9,8 +9,8 @@ configs({ directions: ['ltr'], modes: ['ionic-md'] }).forEach(({ config, screens ` @@ -47,8 +47,8 @@ configs({ directions: ['ltr'], modes: ['ionic-md'] }).forEach(({ config, screens ` @@ -87,8 +87,8 @@ configs({ directions: ['ltr'], modes: ['ionic-md'] }).forEach(({ config, screens ` @@ -125,8 +125,8 @@ configs({ directions: ['ltr'], modes: ['ionic-md'] }).forEach(({ config, screens ` diff --git a/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts b/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts index e3a3f774717..d325a38af5b 100644 --- a/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts +++ b/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts @@ -9,8 +9,8 @@ configs({ directions: ['ltr'], modes: ['ionic-md'] }).forEach(({ config, screens ` diff --git a/core/src/components/accordion/test/shape/accordion.e2e.ts b/core/src/components/accordion/test/shape/accordion.e2e.ts index 66d4604a4fc..5dcf6b7d589 100644 --- a/core/src/components/accordion/test/shape/accordion.e2e.ts +++ b/core/src/components/accordion/test/shape/accordion.e2e.ts @@ -9,8 +9,8 @@ configs({ directions: ['ltr'], modes: ['ionic-md'] }).forEach(({ config, screens ` diff --git a/core/src/components/progress-bar/test/shape/progress-bar.e2e.ts b/core/src/components/progress-bar/test/shape/progress-bar.e2e.ts index f4490902119..4d3c3ab47fb 100644 --- a/core/src/components/progress-bar/test/shape/progress-bar.e2e.ts +++ b/core/src/components/progress-bar/test/shape/progress-bar.e2e.ts @@ -7,8 +7,8 @@ configs({ directions: ['ltr'], modes: ['ionic-md'] }).forEach(({ title, screensh await page.setContent( ` diff --git a/core/src/components/tab-bar/test/basic/tab-bar.e2e.ts b/core/src/components/tab-bar/test/basic/tab-bar.e2e.ts index 2a710770a8c..9069cbb8e85 100644 --- a/core/src/components/tab-bar/test/basic/tab-bar.e2e.ts +++ b/core/src/components/tab-bar/test/basic/tab-bar.e2e.ts @@ -10,8 +10,8 @@ configs({ modes: ['ionic-md', 'md', 'ios'] }).forEach(({ title, screenshot, conf await page.setContent( ` diff --git a/core/src/components/tab-bar/test/shape/tab-bar.e2e.ts b/core/src/components/tab-bar/test/shape/tab-bar.e2e.ts index 70e2bbc135d..765744bd761 100644 --- a/core/src/components/tab-bar/test/shape/tab-bar.e2e.ts +++ b/core/src/components/tab-bar/test/shape/tab-bar.e2e.ts @@ -12,8 +12,8 @@ configs({ directions: ['ltr'], modes: ['ionic-md'] }).forEach(({ title, screensh await page.setContent( ` @@ -22,12 +22,12 @@ configs({ directions: ['ltr'], modes: ['ionic-md'] }).forEach(({ title, screensh Label - + Label - + Label @@ -48,8 +48,8 @@ configs({ directions: ['ltr'], modes: ['ionic-md'] }).forEach(({ title, screensh await page.setContent( ` @@ -58,12 +58,12 @@ configs({ directions: ['ltr'], modes: ['ionic-md'] }).forEach(({ title, screensh Label - + Label - + Label @@ -84,8 +84,8 @@ configs({ directions: ['ltr'], modes: ['ionic-md'] }).forEach(({ title, screensh await page.setContent( ` @@ -94,12 +94,12 @@ configs({ directions: ['ltr'], modes: ['ionic-md'] }).forEach(({ title, screensh Label - + Label - + Label diff --git a/core/src/global/ionic-global.ts b/core/src/global/ionic-global.ts index 507262f3a72..4d57b33a22d 100644 --- a/core/src/global/ionic-global.ts +++ b/core/src/global/ionic-global.ts @@ -4,7 +4,6 @@ import { applyGlobalTheme, getCustomTheme } from '@utils/theme'; import type { IonicConfig, Mode, Theme } from '../interface'; import { defaultTheme as baseTheme } from '../themes/base/default.tokens'; -import { defaultTheme as ionicTheme } from '../themes/ionic/default.tokens'; import type { BaseTheme } from '../themes/themes.interfaces'; import { shouldUseCloseWatcher } from '../utils/hardware-back-button'; import { isPlatform, setupPlatforms } from '../utils/platform'; @@ -156,11 +155,7 @@ export const initialize = (userConfig: IonicConfig = {}) => { config.set('customTheme', combinedTheme); } else { applyGlobalTheme(baseTheme); - } - - // TODO(): remove this when we update the ionic theme - if (defaultTheme === 'ionic') { - applyGlobalTheme(ionicTheme); + config.set('customTheme', baseTheme); } if (config.getBoolean('_testing')) { diff --git a/core/src/themes/base/dark.tokens.ts b/core/src/themes/base/dark.tokens.ts index 0a185044154..1b611610c66 100644 --- a/core/src/themes/base/dark.tokens.ts +++ b/core/src/themes/base/dark.tokens.ts @@ -161,4 +161,62 @@ export const darkTheme: DarkTheme = { }, }, }, + + backgroundColor: '#000000', + backgroundColorRgb: '0, 0, 0', + textColor: '#ffffff', + textColorRgb: '255, 255, 255', + + backgroundColorStep: { + 50: '#0d0d0d', + 100: '#1a1a1a', + 150: '#262626', + 200: '#333333', + 250: '#404040', + 300: '#4d4d4d', + 350: '#595959', + 400: '#666666', + 450: '#737373', + 500: '#898989', + 550: '#8c8c8c', + 600: '#999999', + 650: '#a6a6a6', + 700: '#b3b3b3', + 750: '#bfbfbf', + 800: '#cccccc', + 850: '#d9d9d9', + 900: '#e6e6e6', + 950: '#f2f2f2', + }, + + textColorStep: { + 50: '#f2f2f2', + 100: '#e6e6e6', + 150: '#d9d9d9', + 200: '#cccccc', + 250: '#bfbfbf', + 300: '#b3b3b3', + 350: '#a6a6a6', + 400: '#999999', + 450: '#8c8c8c', + 500: '#808080', + 550: '#737373', + 600: '#666666', + 650: '#595959', + 700: '#4d4d4d', + 750: '#404040', + 800: '#333333', + 850: '#262626', + 900: '#1a1a1a', + 950: '#0d0d0d', + }, + + components: { + IonCard: { + background: '#1c1c1d', + }, + IonItem: { + background: '#000000', + }, + }, }; diff --git a/core/src/themes/base/default.tokens.ts b/core/src/themes/base/default.tokens.ts index e6009c9c4e4..c2420a7ea4b 100644 --- a/core/src/themes/base/default.tokens.ts +++ b/core/src/themes/base/default.tokens.ts @@ -4,24 +4,49 @@ import { darkTheme } from './dark.tokens'; import { lightTheme } from './light.tokens'; export const defaultTheme: DefaultTheme = { + name: 'base', + palette: { light: lightTheme, dark: darkTheme, }, + formHighlight: false, + rippleEffect: false, + spacing: { - none: '0', - xxs: '4px', - xs: '6px', - sm: '8px', - md: '12px', - lg: '16px', - xl: '24px', - xxl: '32px', + 0: '0px', + 50: '2px', + 100: '4px', + 150: '6px', + 200: '8px', + 250: '10px', + 300: '12px', + 350: '14px', + 400: '16px', + 450: '18px', + 500: '20px', + 550: '22px', + 600: '24px', + 650: '26px', + 700: '28px', + 750: '30px', + 800: '32px', + 850: '34px', + 900: '36px', + 950: '38px', + 1000: '40px', + 1050: '42px', + 1100: '44px', + 1150: '46px', + 1200: '48px', }, scaling: { - 0: '0', + 0: '0px', + 25: '1px', + 50: '2px', + 75: '3px', 100: '4px', 150: '6px', 200: '8px', @@ -39,28 +64,52 @@ export const defaultTheme: DefaultTheme = { 800: '32px', 850: '34px', 900: '36px', + 950: '38px', + 1000: '40px', + 1050: '42px', + 1100: '44px', + 1150: '46px', + 1200: '48px', + 1400: '56px', + 1600: '64px', + 1800: '72px', + 2000: '80px', + 2400: '96px', + 2800: '112px', + 3200: '128px', + 3400: '136px', + 3600: '144px', + 4000: '160px', + 5000: '200px', + 6200: '248px', + 7400: '296px', + 9000: '360px', }, borderWidth: { - none: '0', - xxs: '1px', - xs: '2px', - sm: '4px', - md: '6px', - lg: '8px', - xl: '10px', - xxl: '12px', + 25: '1px', + 50: '2px', + 75: '3px', + 100: '4px', + 150: '6px', + 200: '8px', + 250: '10px', + 300: '12px', + 350: '14px', + 400: '16px', }, radii: { - none: '0', - xxs: '1px', - xs: '2px', - sm: '4px', - md: '8px', - lg: '12px', - xl: '16px', - xxl: '32px', + 0: '0px', + 25: '2px', + 100: '4px', + 200: '8px', + 300: '12px', + 400: '16px', + 500: '20px', + 800: '32px', + 1000: '40px', + full: '999px', }, dynamicFont: '-apple-system-body', diff --git a/core/src/themes/ionic/dark.tokens.ts b/core/src/themes/ionic/dark.tokens.ts index be0b93d4947..8e7222b581a 100644 --- a/core/src/themes/ionic/dark.tokens.ts +++ b/core/src/themes/ionic/dark.tokens.ts @@ -1,5 +1,6 @@ +import { darkTheme as baseDarkTheme } from '../base/dark.tokens'; import type { DarkTheme } from '../themes.interfaces'; export const darkTheme: DarkTheme = { - enabled: 'never', + ...baseDarkTheme, }; diff --git a/core/src/themes/ionic/default.tokens.ts b/core/src/themes/ionic/default.tokens.ts index 15df8d0c2c1..3325f7aaef7 100644 --- a/core/src/themes/ionic/default.tokens.ts +++ b/core/src/themes/ionic/default.tokens.ts @@ -1,11 +1,117 @@ +import { defaultTheme as baseDefaultTheme } from '../base/default.tokens'; import type { DefaultTheme } from '../themes.interfaces'; import { darkTheme } from './dark.tokens'; import { lightTheme } from './light.tokens'; export const defaultTheme: DefaultTheme = { + ...baseDefaultTheme, + + name: 'ionic', + palette: { light: lightTheme, dark: darkTheme, }, + + formHighlight: false, + rippleEffect: false, + + // TODO(FW-6745): see if we can remove this after the md tokens are added + fontFamily: 'initial', + + spacing: { + 0: '0px', + 50: '2px', + 100: '4px', + 150: '6px', + 200: '8px', + 250: '10px', + 300: '12px', + 350: '14px', + 400: '16px', + 450: '18px', + 500: '20px', + 550: '22px', + 600: '24px', + 650: '26px', + 700: '28px', + 750: '30px', + 800: '32px', + 850: '34px', + 900: '36px', + 950: '38px', + 1000: '40px', + 1050: '42px', + 1100: '44px', + 1150: '46px', + 1200: '48px', + }, + + scaling: { + 0: '0px', + 25: '1px', + 50: '2px', + 75: '3px', + 100: '4px', + 150: '6px', + 200: '8px', + 250: '10px', + 300: '12px', + 350: '14px', + 400: '16px', + 450: '18px', + 500: '20px', + 550: '22px', + 600: '24px', + 650: '26px', + 700: '28px', + 750: '30px', + 800: '32px', + 850: '34px', + 900: '36px', + 950: '38px', + 1000: '40px', + 1050: '42px', + 1100: '44px', + 1150: '46px', + 1200: '48px', + 1400: '56px', + 1600: '64px', + 1800: '72px', + 2000: '80px', + 2400: '96px', + 2800: '112px', + 3200: '128px', + 3400: '136px', + 3600: '144px', + 4000: '160px', + 5000: '200px', + 6200: '248px', + 7400: '296px', + 9000: '360px', + }, + + radii: { + 0: '0px', + 25: '2px', + 100: '4px', + 200: '8px', + 300: '12px', + 400: '16px', + 500: '20px', + 800: '32px', + 1000: '40px', + full: '999px', + }, + + borderWidth: { + 0: '0', + 25: '1px', + 50: '2px', + 75: '3px', + 100: '4px', + 150: '6px', + 200: '8px', + }, }; diff --git a/core/src/themes/ionic/light.tokens.ts b/core/src/themes/ionic/light.tokens.ts index 2ef8696a576..94c96baeb90 100644 --- a/core/src/themes/ionic/light.tokens.ts +++ b/core/src/themes/ionic/light.tokens.ts @@ -1,151 +1,23 @@ import type { LightTheme } from '../themes.interfaces'; -// TODO(): this should be removed when we update the ionic theme export const lightTheme: LightTheme = { - color: { - primary: { - bold: { - base: '#105cef', // $ion-bg-primary-base-default - contrast: '#ffffff', // $ion-text-inverse - shade: '#0d4bc3', // $ion-bg-primary-base-press - tint: '#6986f2', // $ion-semantics-primary-600 - foreground: '#0d4bc3', // $ion-text-primary - }, - subtle: { - base: '#f2f4fd', // $ion-bg-primary-subtle-default - contrast: '#0d4bc3', // $ion-text-primary - shade: '#d0d7fa', // $ion-bg-primary-subtle-press - tint: '#e9ecfc', // $ion-semantics-primary-200 - foreground: '#0d4bc3', // $ion-text-primary - }, - }, - secondary: { - bold: { - base: '#0d4bc3', // $ion-bg-info-base-default - contrast: '#ffffff', // $ion-text-inverse - shade: '#09358a', // $ion-bg-info-base-press - tint: '#105cef', // $ion-semantics-info-700 - foreground: '#0d4bc3', // $ion-text-info - }, - subtle: { - base: '#f2f4fd', // $ion-bg-info-subtle-default - contrast: '#0d4bc3', // $ion-text-info - shade: '#d0d7fa', // $ion-bg-info-subtle-press - tint: '#e9ecfc', // $ion-semantics-info-200 - foreground: '#0d4bc3', // $ion-text-info - }, - }, - tertiary: { - bold: { - base: '#7c20f2', // $ion-primitives-violet-700 - contrast: '#ffffff', // $ion-text-inverse - shade: '#711ddd', // $ion-primitives-violet-800 - tint: '#9a6cf4', // $ion-primitives-violet-600 - foreground: '#7c20f2', // $ion-primitives-violet-700 - }, - subtle: { - base: '#f5f2fe', // $ion-primitives-violet-100 - contrast: '#7c20f2', // $ion-primitives-violet-700 - shade: '#dcd1fb', // $ion-primitives-violet-300 - tint: '#eee9fd', // $ion-primitives-violet-200 - foreground: '#7c20f2', // $ion-primitives-violet-700 - }, - }, - success: { - bold: { - base: '#126f23', // $ion-bg-success-base-default - contrast: '#ffffff', // $ion-text-inverse - shade: '#093811', // $ion-bg-success-base-press - tint: '#178a2b', // $ion-semantics-success-800 - foreground: '#126f23', // $ion-text-success - }, - subtle: { - base: '#ebf9ec', // $ion-bg-success-subtle-default - contrast: '#126f23', // $ion-text-success - shade: '#b3ebb7', // $ion-bg-success-subtle-press - tint: '#dcf5de', // $ion-semantics-success-200 - foreground: '#126f23', // $ion-text-success - }, - }, - warning: { - bold: { - base: '#ffd600', // $ion-bg-warning-base-default - contrast: '#242424', // $ion-text-default - shade: '#df9c00', // $ion-bg-warning-base-press - tint: '#ffebb1', // $ion-primitives-yellow-300 - foreground: '#704b02', // $ion-text-warning - }, - subtle: { - base: '#fff5db', // $ion-bg-warning-subtle-default - contrast: '#704b02', // $ion-text-warning - shade: '#ffe07b', // $ion-bg-warning-subtle-press - tint: '#fff9ea', // $ion-primitives-yellow-100 - foreground: '#704b02', // $ion-text-warning - }, - }, - danger: { - bold: { - base: '#bf2222', // $ion-bg-danger-base-default - contrast: '#ffffff', // $ion-text-inverse - shade: '#761515', // $ion-bg-danger-base-press - tint: '#e52929', // $ion-semantics-danger-700 - foreground: '#991b1b', // $ion-text-danger - }, - subtle: { - base: '#feeded', // $ion-bg-danger-subtle-default - contrast: '#991b1b', // $ion-text-danger - shade: '#fcc1c1', // $ion-bg-danger-subtle-press - tint: '#fde1e1', // $ion-semantics-danger-200 - foreground: '#991b1b', // $ion-text-danger - }, - }, - light: { - bold: { - base: '#a2a2a2', // $ion-bg-neutral-base-default - contrast: '#242424', // $ion-text-default - shade: '#8c8c8c', // $ion-primitives-neutral-600 - tint: '#d5d5d5', // $ion-primitives-neutral-400 - foreground: '#242424', // $ion-text-default - }, - subtle: { - base: '#ffffff', // $ion-bg-neutral-subtlest-default - contrast: '#242424', // $ion-text-default - shade: '#efefef', // $ion-bg-neutral-subtlest-press - tint: '#f5f5f5', // $ion-primitives-neutral-100 - foreground: '#242424', // $ion-text-default - }, - }, - medium: { - bold: { - base: '#3b3b3b', // $ion-bg-neutral-bold-default - contrast: '#ffffff', // $ion-text-inverse - shade: '#242424', // $ion-bg-neutral-bold-press - tint: '#4e4e4e', // $ion-primitives-neutral-900 - foreground: '#242424', // $ion-text-default - }, - subtle: { - base: '#efefef', // $ion-bg-neutral-subtle-default - contrast: '#626262', // $ion-text-subtlest - shade: '#d5d5d5', // $ion-bg-neutral-subtle-press - tint: '#f5f5f5', // $ion-primitives-neutral-100 - foreground: '#242424', // $ion-text-default - }, - }, - dark: { - bold: { - base: '#242424', // $ion-bg-neutral-boldest-default - contrast: '#ffffff', // $ion-text-inverse - shade: '#111111', // $ion-bg-neutral-boldest-press - tint: '#292929', // $ion-primitives-neutral-1100 - foreground: '#242424', // $ion-text-default - }, - subtle: { - base: '#efefef', // $ion-bg-neutral-subtle-default - contrast: '#3b3b3b', // $ion-text-subtle - shade: '#d5d5d5', // $ion-bg-neutral-subtle-press - tint: '#f5f5f5', // $ion-primitives-neutral-100 - foreground: '#242424', // $ion-text-default - }, + backgroundColor: '#ffffff', + textColor: '#000000', + + components: { + IonCard: { + background: '#ffffff', + }, + IonItem: { + background: '#ffffff', + }, + IonTabBar: { + background: 'var(--ion-tab-bar-background, #ffffff)', + backgroundActivated: 'var(--ion-tab-bar-background-activated, #f2f4fd)', + backgroundFocused: 'var(--ion-tab-bar-background-focused, transparent)', + color: 'var(--ion-tab-bar-color, #626262)', + colorSelected: 'var(--ion-tab-bar-color-selected, #0d4bc3)', + borderColor: 'var(--ion-tab-bar-border-color, transparent)', }, }, }; diff --git a/core/src/themes/ios/dark.tokens.ts b/core/src/themes/ios/dark.tokens.ts index e69de29bb2d..8e7222b581a 100644 --- a/core/src/themes/ios/dark.tokens.ts +++ b/core/src/themes/ios/dark.tokens.ts @@ -0,0 +1,6 @@ +import { darkTheme as baseDarkTheme } from '../base/dark.tokens'; +import type { DarkTheme } from '../themes.interfaces'; + +export const darkTheme: DarkTheme = { + ...baseDarkTheme, +}; diff --git a/core/src/themes/ios/default.tokens.ts b/core/src/themes/ios/default.tokens.ts index e69de29bb2d..68641be1598 100644 --- a/core/src/themes/ios/default.tokens.ts +++ b/core/src/themes/ios/default.tokens.ts @@ -0,0 +1,16 @@ +import { defaultTheme as baseDefaultTheme } from '../base/default.tokens'; +import type { DefaultTheme } from '../themes.interfaces'; + +import { darkTheme } from './dark.tokens'; +import { lightTheme } from './light.tokens'; + +export const defaultTheme: DefaultTheme = { + ...baseDefaultTheme, + + name: 'ios', + + palette: { + light: lightTheme, + dark: darkTheme, + }, +}; diff --git a/core/src/themes/ios/light.tokens.ts b/core/src/themes/ios/light.tokens.ts index e69de29bb2d..b7679b9f553 100644 --- a/core/src/themes/ios/light.tokens.ts +++ b/core/src/themes/ios/light.tokens.ts @@ -0,0 +1,3 @@ +import type { LightTheme } from '../themes.interfaces'; + +export const lightTheme: LightTheme = {}; diff --git a/core/src/themes/md/dark.tokens.ts b/core/src/themes/md/dark.tokens.ts index e69de29bb2d..8e7222b581a 100644 --- a/core/src/themes/md/dark.tokens.ts +++ b/core/src/themes/md/dark.tokens.ts @@ -0,0 +1,6 @@ +import { darkTheme as baseDarkTheme } from '../base/dark.tokens'; +import type { DarkTheme } from '../themes.interfaces'; + +export const darkTheme: DarkTheme = { + ...baseDarkTheme, +}; diff --git a/core/src/themes/md/default.tokens.ts b/core/src/themes/md/default.tokens.ts index e69de29bb2d..708b184146a 100644 --- a/core/src/themes/md/default.tokens.ts +++ b/core/src/themes/md/default.tokens.ts @@ -0,0 +1,16 @@ +import { defaultTheme as baseDefaultTheme } from '../base/default.tokens'; +import type { DefaultTheme } from '../themes.interfaces'; + +import { darkTheme } from './dark.tokens.js'; +import { lightTheme } from './light.tokens.js'; + +export const defaultTheme: DefaultTheme = { + ...baseDefaultTheme, + + name: 'md', + + palette: { + light: lightTheme, + dark: darkTheme, + }, +}; diff --git a/core/src/themes/md/light.tokens.ts b/core/src/themes/md/light.tokens.ts index e69de29bb2d..b7679b9f553 100644 --- a/core/src/themes/md/light.tokens.ts +++ b/core/src/themes/md/light.tokens.ts @@ -0,0 +1,3 @@ +import type { LightTheme } from '../themes.interfaces'; + +export const lightTheme: LightTheme = {}; diff --git a/core/src/themes/test/basic/index.html b/core/src/themes/test/basic/index.html index 81bfba8139f..c6cd4821425 100644 --- a/core/src/themes/test/basic/index.html +++ b/core/src/themes/test/basic/index.html @@ -16,11 +16,25 @@ @@ -211,78 +95,80 @@
-
-

Scaling

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

Spacing

-
-
none
+
+
+

Border Width

+
-
-
xxs
+
+

Radii

+
-
-
xs
-
-
-
sm
-
-
-
md
-
-
-
lg
-
-
-
xl
+
+
+
+

Scaling

+
-
-
xxl
+
+

Spacing

+
-
-

Radii

-
none
-
xxs
-
xs
-
sm
-
md
-
lg
-
xl
-
xxl
-
-
-

Border Width

-
none
-
xxs
-
xs
-
sm
-
md
-
lg
-
xl
-
xxl
-
+ + diff --git a/core/src/themes/themes.interfaces.ts b/core/src/themes/themes.interfaces.ts index 3d5ac09a4e0..29653a3ee35 100644 --- a/core/src/themes/themes.interfaces.ts +++ b/core/src/themes/themes.interfaces.ts @@ -3,20 +3,56 @@ export type PlatformTheme = Omit; // Base tokens for all palettes export type BaseTheme = { + // CONFIG TOKENS + rippleEffect?: boolean; + formHighlight?: boolean; + + // GLOBAL THEME TOKENS + backgroundColor?: string; + backgroundColorRgb?: string; + textColor?: string; + textColorRgb?: string; + backgroundColorStep?: { + [key: string]: string; + }; + textColorStep?: { + [key: string]: string; + }; + // SPACE TOKENS spacing?: { - none?: string; - xxs?: string; - xs?: string; - sm?: string; - md?: string; - lg?: string; - xl?: string; - xxl?: string; + 0?: string; + 50?: string; + 100?: string; + 150?: string; + 200?: string; + 250?: string; + 300?: string; + 350?: string; + 400?: string; + 450?: string; + 500?: string; + 550?: string; + 600?: string; + 650?: string; + 700?: string; + 750?: string; + 800?: string; + 850?: string; + 900?: string; + 950?: string; + 1000?: string; + 1050?: string; + 1100?: string; + 1150?: string; + 1200?: string; }; scaling?: { 0?: string; + 25?: string; + 50?: string; + 75?: string; 100?: string; 150?: string; 200?: string; @@ -34,29 +70,54 @@ export type BaseTheme = { 800?: string; 850?: string; 900?: string; + 950?: string; + 1000?: string; + 1050?: string; + 1100?: string; + 1150?: string; + 1200?: string; + 1400?: string; + 1600?: string; + 1800?: string; + 2000?: string; + 2400?: string; + 2800?: string; + 3200?: string; + 3400?: string; + 3600?: string; + 4000?: string; + 5000?: string; + 6200?: string; + 7400?: string; + 9000?: string; }; // APPEARANCE TOKENS borderWidth?: { - none?: string; - xxs?: string; - xs?: string; - sm?: string; - md?: string; - lg?: string; - xl?: string; - xxl?: string; + 0?: string; + 25?: string; + 50?: string; + 75?: string; + 100?: string; + 150?: string; + 200?: string; + 250?: string; + 300?: string; + 350?: string; + 400?: string; }; radii?: { - none?: string; - xxs?: string; - xs?: string; - sm?: string; - md?: string; - lg?: string; - xl?: string; - xxl?: string; + 0?: string; + 25?: string; + 100?: string; + 200?: string; + 300?: string; + 400?: string; + 500?: string; + 800?: string; + 1000?: string; + full?: string; }; // TYPOGRAPHY TOKENS @@ -138,6 +199,8 @@ export type LightTheme = BaseTheme; // Default theme interface export type DefaultTheme = BaseTheme & { + name?: string; + palette?: { light?: LightTheme; dark?: DarkTheme; diff --git a/core/src/utils/test/theme.spec.ts b/core/src/utils/test/theme.spec.ts index 63382768cf4..d8426de908e 100644 --- a/core/src/utils/test/theme.spec.ts +++ b/core/src/utils/test/theme.spec.ts @@ -204,6 +204,8 @@ describe('generateCSSVars', () => { enabled: 'system', }, }, + rippleEffect: true, + formHighlight: true, borderWidth: { sm: '4px', }, @@ -233,7 +235,6 @@ describe('generateCSSVars', () => { const css = generateCSSVars(theme); - expect(css).toContain('--ion-palette-dark-enabled: system;'); expect(css).toContain('--ion-border-width-sm: 4px;'); expect(css).toContain('--ion-spacing-md: 12px;'); expect(css).toContain('--ion-scaling-0: 0;'); @@ -521,7 +522,6 @@ describe('generateGlobalThemeCSS', () => { @media(prefers-color-scheme: dark) { :root { - --ion-enabled: system; --ion-color-primary-bold: #0054e9; --ion-color-primary-bold-rgb: 0, 84, 233; --ion-color-primary-bold-contrast: #ffffff; diff --git a/core/src/utils/theme.ts b/core/src/utils/theme.ts index 419cb99d814..c91c082406f 100644 --- a/core/src/utils/theme.ts +++ b/core/src/utils/theme.ts @@ -81,6 +81,12 @@ export const generateCSSVars = (theme: any, prefix: string = CSS_PROPS_PREFIX): return []; } + // Do not generate CSS variables for excluded keys + const excludedKeys = ['enabled', 'ripple-effect', 'form-highlight']; + if (excludedKeys.includes(key)) { + return []; + } + // if key is camelCase, convert to kebab-case if (key.match(/([a-z])([A-Z])/g)) { key = key.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();