mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-11-06 06:09:31 +08:00
feat(themes): add md theme tokens and update ionic theme to include semantic tokens (#30734)
- Updates the theme tokens interface to add more numeric tokens & semantic tokens - Moves the `config` values into a separate `config` option which uses the `IonicConfig` interface - Adds a config option for `formHighlight` to `IonicConfig` - Defines default & dark tokens for the `md` theme - Removes the numeric tokens from the `ionic` theme and adds semantic tokens - Remove the numerous `--ion-font-family` overrides in favor of the tokens --------- Co-authored-by: Brandy Smith <6577830+brandyscarney@users.noreply.github.com>
This commit is contained in:
File diff suppressed because one or more lines are too long
@ -7,16 +7,9 @@
|
||||
// Ionic Font Family
|
||||
// --------------------------------------------------
|
||||
|
||||
// TODO(FW-6744): Remove this after adding the ios tokens
|
||||
html.ios {
|
||||
--ion-default-font: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "Roboto", sans-serif;
|
||||
}
|
||||
html.md {
|
||||
--ion-default-font: "Roboto", "Helvetica Neue", sans-serif;
|
||||
}
|
||||
|
||||
html {
|
||||
--ion-dynamic-font: -apple-system-body;
|
||||
--ion-font-family: var(--ion-default-font);
|
||||
--ion-font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "Roboto", sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
|
||||
@ -8,10 +8,6 @@
|
||||
--background: #{globals.$ion-bg-surface-default};
|
||||
}
|
||||
|
||||
html {
|
||||
--ionic-dynamic-font: -apple-system-body;
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--ion-background-color, #{globals.$ion-bg-body});
|
||||
color: var(--ion-text-color, #{globals.$ion-text-default});
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
// TODO(ROU-10833): add font loading solution here, as a @font-face, base64 or cdn
|
||||
html {
|
||||
font-family: globals.$ion-font-family;
|
||||
font-family: var(--ion-font-family);
|
||||
}
|
||||
|
||||
body {
|
||||
|
||||
@ -11,12 +11,16 @@ export const defaultTheme: DefaultTheme = {
|
||||
dark: darkTheme,
|
||||
},
|
||||
|
||||
formHighlight: false,
|
||||
rippleEffect: false,
|
||||
config: {
|
||||
formHighlight: false,
|
||||
rippleEffect: false,
|
||||
},
|
||||
|
||||
spacing: {
|
||||
0: '0px',
|
||||
25: '1px',
|
||||
50: '2px',
|
||||
75: '3px',
|
||||
100: '4px',
|
||||
150: '6px',
|
||||
200: '8px',
|
||||
@ -98,17 +102,26 @@ export const defaultTheme: DefaultTheme = {
|
||||
300: '12px',
|
||||
350: '14px',
|
||||
400: '16px',
|
||||
hairline: '0.55px',
|
||||
},
|
||||
|
||||
radii: {
|
||||
0: '0px',
|
||||
25: '2px',
|
||||
25: '1px',
|
||||
50: '2px',
|
||||
75: '3px',
|
||||
100: '4px',
|
||||
150: '6px',
|
||||
200: '8px',
|
||||
250: '10px',
|
||||
300: '12px',
|
||||
350: '14px',
|
||||
400: '16px',
|
||||
500: '20px',
|
||||
600: '24px',
|
||||
700: '28px',
|
||||
800: '32px',
|
||||
900: '36px',
|
||||
1000: '40px',
|
||||
full: '999px',
|
||||
},
|
||||
|
||||
@ -14,104 +14,66 @@ export const defaultTheme: DefaultTheme = {
|
||||
dark: darkTheme,
|
||||
},
|
||||
|
||||
formHighlight: false,
|
||||
rippleEffect: false,
|
||||
config: {
|
||||
formHighlight: true,
|
||||
},
|
||||
|
||||
// TODO(FW-6745): see if we can remove this after the md tokens are added
|
||||
fontFamily: 'initial',
|
||||
fontFamily:
|
||||
'-apple-system, system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"',
|
||||
|
||||
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',
|
||||
xxxxs: 'var(--ion-spacing-25)',
|
||||
xxxs: 'var(--ion-spacing-50)',
|
||||
xxs: 'var(--ion-spacing-100)',
|
||||
xs: 'var(--ion-spacing-150)',
|
||||
sm: 'var(--ion-spacing-200)',
|
||||
md: 'var(--ion-spacing-300)',
|
||||
lg: 'var(--ion-spacing-400)',
|
||||
xl: 'var(--ion-spacing-500)',
|
||||
xxl: 'var(--ion-spacing-600)',
|
||||
xxxl: 'var(--ion-spacing-700)',
|
||||
xxxxl: 'var(--ion-spacing-800)',
|
||||
},
|
||||
|
||||
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',
|
||||
xxxxs: 'var(--ion-scaling-300)',
|
||||
xxxs: 'var(--ion-scaling-400)',
|
||||
xxs: 'var(--ion-scaling-500)',
|
||||
xs: 'var(--ion-scaling-600)',
|
||||
sm: 'var(--ion-scaling-700)',
|
||||
md: 'var(--ion-scaling-800)',
|
||||
lg: 'var(--ion-scaling-1000)',
|
||||
xl: 'var(--ion-scaling-1200)',
|
||||
xxl: 'var(--ion-scaling-1400)',
|
||||
xxxl: 'var(--ion-scaling-1800)',
|
||||
xxxxl: 'var(--ion-scaling-2000)',
|
||||
},
|
||||
|
||||
borderWidth: {
|
||||
0: '0',
|
||||
25: '1px',
|
||||
50: '2px',
|
||||
75: '3px',
|
||||
100: '4px',
|
||||
150: '6px',
|
||||
200: '8px',
|
||||
xxxxs: 'var(--ion-border-width-0)',
|
||||
xxxs: 'var(--ion-border-width-25)',
|
||||
xxs: 'var(--ion-border-width-50)',
|
||||
xs: 'var(--ion-border-width-75)',
|
||||
sm: 'var(--ion-border-width-100)',
|
||||
md: 'var(--ion-border-width-150)',
|
||||
lg: 'var(--ion-border-width-200)',
|
||||
xl: 'var(--ion-border-width-250)',
|
||||
xxl: 'var(--ion-border-width-300)',
|
||||
xxxl: 'var(--ion-border-width-350)',
|
||||
xxxxl: 'var(--ion-border-width-400)',
|
||||
},
|
||||
|
||||
radii: {
|
||||
xxxxs: 'var(--ion-radii-0)',
|
||||
xxxs: 'var(--ion-radii-25)',
|
||||
xxs: 'var(--ion-radii-50)',
|
||||
xs: 'var(--ion-radii-75)',
|
||||
sm: 'var(--ion-radii-100)',
|
||||
md: 'var(--ion-radii-200)',
|
||||
lg: 'var(--ion-radii-300)',
|
||||
xl: 'var(--ion-radii-400)',
|
||||
xxl: 'var(--ion-radii-500)',
|
||||
xxxl: 'var(--ion-radii-1000)',
|
||||
xxxxl: 'var(--ion-radii-full)',
|
||||
},
|
||||
};
|
||||
|
||||
@ -3,4 +3,68 @@ import type { DarkTheme } from '../themes.interfaces';
|
||||
|
||||
export const darkTheme: DarkTheme = {
|
||||
...baseDarkTheme,
|
||||
|
||||
backgroundColor: '#121212',
|
||||
backgroundColorRgb: '18, 18, 18',
|
||||
textColor: '#ffffff',
|
||||
textColorRgb: '255, 255, 255',
|
||||
|
||||
backgroundColorStep: {
|
||||
50: '#1e1e1e',
|
||||
100: '#2a2a2a',
|
||||
150: '#363636',
|
||||
200: '#414141',
|
||||
250: '#4d4d4d',
|
||||
300: '#595959',
|
||||
350: '#656565',
|
||||
400: '#717171',
|
||||
450: '#7d7d7d',
|
||||
500: '#898989',
|
||||
550: '#949494',
|
||||
600: '#a0a0a0',
|
||||
650: '#acacac',
|
||||
700: '#b8b8b8',
|
||||
750: '#c4c4c4',
|
||||
800: '#d0d0d0',
|
||||
850: '#dbdbdb',
|
||||
900: '#e7e7e7',
|
||||
950: '#f3f3f3',
|
||||
},
|
||||
|
||||
textColorStep: {
|
||||
50: '#f3f3f3',
|
||||
100: '#e7e7e7',
|
||||
150: '#dbdbdb',
|
||||
200: '#d0d0d0',
|
||||
250: '#c4c4c4',
|
||||
300: '#b8b8b8',
|
||||
350: '#acacac',
|
||||
400: '#a0a0a0',
|
||||
450: '#949494',
|
||||
500: '#898989',
|
||||
550: '#7d7d7d',
|
||||
600: '#717171',
|
||||
650: '#656565',
|
||||
700: '#595959',
|
||||
750: '#4d4d4d',
|
||||
800: '#414141',
|
||||
850: '#363636',
|
||||
900: '#2a2a2a',
|
||||
950: '#1e1e1e',
|
||||
},
|
||||
|
||||
components: {
|
||||
IonCard: {
|
||||
background: '#1e1e1e',
|
||||
},
|
||||
IonItem: {
|
||||
background: '#1e1e1e',
|
||||
},
|
||||
IonToolbar: {
|
||||
background: '#1f1f1f',
|
||||
},
|
||||
IonTabBar: {
|
||||
background: '#1f1f1f',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -13,4 +13,67 @@ export const defaultTheme: DefaultTheme = {
|
||||
light: lightTheme,
|
||||
dark: darkTheme,
|
||||
},
|
||||
|
||||
config: {
|
||||
formHighlight: true,
|
||||
rippleEffect: true,
|
||||
},
|
||||
|
||||
fontFamily: '"Roboto", "Helvetica Neue", sans-serif',
|
||||
|
||||
spacing: {
|
||||
xxxxs: 'var(--ion-spacing-25)',
|
||||
xxxs: 'var(--ion-spacing-50)',
|
||||
xxs: 'var(--ion-spacing-100)',
|
||||
xs: 'var(--ion-spacing-150)',
|
||||
sm: 'var(--ion-spacing-200)',
|
||||
md: 'var(--ion-spacing-300)',
|
||||
lg: 'var(--ion-spacing-400)',
|
||||
xl: 'var(--ion-spacing-500)',
|
||||
xxl: 'var(--ion-spacing-600)',
|
||||
xxxl: 'var(--ion-spacing-700)',
|
||||
xxxxl: 'var(--ion-spacing-800)',
|
||||
},
|
||||
|
||||
scaling: {
|
||||
xxxxs: 'var(--ion-scaling-300)',
|
||||
xxxs: 'var(--ion-scaling-400)',
|
||||
xxs: 'var(--ion-scaling-500)',
|
||||
xs: 'var(--ion-scaling-600)',
|
||||
sm: 'var(--ion-scaling-700)',
|
||||
md: 'var(--ion-scaling-800)',
|
||||
lg: 'var(--ion-scaling-900)',
|
||||
xl: 'var(--ion-scaling-1000)',
|
||||
xxl: 'var(--ion-scaling-1100)',
|
||||
xxxl: 'var(--ion-scaling-1200)',
|
||||
xxxxl: 'var(--ion-scaling-1600)',
|
||||
},
|
||||
|
||||
borderWidth: {
|
||||
xxxxs: 'var(--ion-border-width-0)',
|
||||
xxxs: 'var(--ion-border-width-25)',
|
||||
xxs: 'var(--ion-border-width-50)',
|
||||
xs: 'var(--ion-border-width-75)',
|
||||
sm: 'var(--ion-border-width-100)',
|
||||
md: 'var(--ion-border-width-150)',
|
||||
lg: 'var(--ion-border-width-200)',
|
||||
xl: 'var(--ion-border-width-250)',
|
||||
xxl: 'var(--ion-border-width-300)',
|
||||
xxxl: 'var(--ion-border-width-350)',
|
||||
xxxxl: 'var(--ion-border-width-400)',
|
||||
},
|
||||
|
||||
radii: {
|
||||
xxxxs: 'var(--ion-radii-0)',
|
||||
xxxs: 'var(--ion-radii-25)',
|
||||
xxs: 'var(--ion-radii-50)',
|
||||
xs: 'var(--ion-radii-75)',
|
||||
sm: 'var(--ion-radii-100)',
|
||||
md: 'var(--ion-radii-200)',
|
||||
lg: 'var(--ion-radii-300)',
|
||||
xl: 'var(--ion-radii-400)',
|
||||
xxl: 'var(--ion-radii-700)',
|
||||
xxxl: 'var(--ion-radii-900)',
|
||||
xxxxl: 'var(--ion-radii-full)',
|
||||
},
|
||||
};
|
||||
|
||||
@ -1,12 +1,10 @@
|
||||
import type { IonicConfig } from '../utils/config';
|
||||
|
||||
// Platform-specific theme
|
||||
export type PlatformTheme = Omit<BaseTheme, 'ios' | 'md'>;
|
||||
|
||||
// Base tokens for all palettes
|
||||
export type BaseTheme = {
|
||||
// CONFIG TOKENS
|
||||
rippleEffect?: boolean;
|
||||
formHighlight?: boolean;
|
||||
|
||||
// GLOBAL THEME TOKENS
|
||||
backgroundColor?: string;
|
||||
backgroundColorRgb?: string;
|
||||
@ -22,7 +20,9 @@ export type BaseTheme = {
|
||||
// SPACE TOKENS
|
||||
spacing?: {
|
||||
0?: string;
|
||||
25?: string;
|
||||
50?: string;
|
||||
75?: string;
|
||||
100?: string;
|
||||
150?: string;
|
||||
200?: string;
|
||||
@ -46,6 +46,17 @@ export type BaseTheme = {
|
||||
1100?: string;
|
||||
1150?: string;
|
||||
1200?: string;
|
||||
xxxxs?: string;
|
||||
xxxs?: string;
|
||||
xxs?: string;
|
||||
xs?: string;
|
||||
sm?: string;
|
||||
md?: string;
|
||||
lg?: string;
|
||||
xl?: string;
|
||||
xxl?: string;
|
||||
xxxl?: string;
|
||||
xxxxl?: string;
|
||||
};
|
||||
|
||||
scaling?: {
|
||||
@ -90,6 +101,17 @@ export type BaseTheme = {
|
||||
6200?: string;
|
||||
7400?: string;
|
||||
9000?: string;
|
||||
xxxxs?: string;
|
||||
xxxs?: string;
|
||||
xxs?: string;
|
||||
xs?: string;
|
||||
sm?: string;
|
||||
md?: string;
|
||||
lg?: string;
|
||||
xl?: string;
|
||||
xxl?: string;
|
||||
xxxl?: string;
|
||||
xxxxl?: string;
|
||||
};
|
||||
|
||||
// APPEARANCE TOKENS
|
||||
@ -105,18 +127,49 @@ export type BaseTheme = {
|
||||
300?: string;
|
||||
350?: string;
|
||||
400?: string;
|
||||
xxxxs?: string;
|
||||
xxxs?: string;
|
||||
xxs?: string;
|
||||
xs?: string;
|
||||
sm?: string;
|
||||
md?: string;
|
||||
lg?: string;
|
||||
xl?: string;
|
||||
xxl?: string;
|
||||
xxxl?: string;
|
||||
xxxxl?: string;
|
||||
hairline?: string;
|
||||
};
|
||||
|
||||
radii?: {
|
||||
0?: string;
|
||||
25?: string;
|
||||
50?: string;
|
||||
75?: string;
|
||||
100?: string;
|
||||
150?: string;
|
||||
200?: string;
|
||||
250?: string;
|
||||
300?: string;
|
||||
350?: string;
|
||||
400?: string;
|
||||
500?: string;
|
||||
600?: string;
|
||||
700?: string;
|
||||
800?: string;
|
||||
900?: string;
|
||||
1000?: string;
|
||||
xxxxs?: string;
|
||||
xxxs?: string;
|
||||
xxs?: string;
|
||||
xs?: string;
|
||||
sm?: string;
|
||||
md?: string;
|
||||
lg?: string;
|
||||
xl?: string;
|
||||
xxl?: string;
|
||||
xxxl?: string;
|
||||
xxxxl?: string;
|
||||
full?: string;
|
||||
};
|
||||
|
||||
@ -205,4 +258,6 @@ export type DefaultTheme = BaseTheme & {
|
||||
light?: LightTheme;
|
||||
dark?: DarkTheme;
|
||||
};
|
||||
|
||||
config?: IonicConfig;
|
||||
};
|
||||
|
||||
@ -12,6 +12,12 @@ export interface IonicConfig {
|
||||
*/
|
||||
animated?: boolean;
|
||||
|
||||
/**
|
||||
* When it's set to `false`, it disables the form highlight effect across the app.
|
||||
* Defaults to `false`.
|
||||
*/
|
||||
formHighlight?: boolean;
|
||||
|
||||
/**
|
||||
* When it's set to `false`, it disables all material-design ripple-effects across the app.
|
||||
* Defaults to `true`.
|
||||
|
||||
@ -186,6 +186,7 @@ describe('getCustomTheme', () => {
|
||||
describe('generateCSSVars', () => {
|
||||
it('should not generate CSS variables for an empty theme', () => {
|
||||
const theme = {
|
||||
name: 'test',
|
||||
palette: {
|
||||
light: {},
|
||||
dark: {},
|
||||
@ -198,14 +199,17 @@ describe('generateCSSVars', () => {
|
||||
|
||||
it('should generate CSS variables for a given theme', () => {
|
||||
const theme = {
|
||||
name: 'test',
|
||||
palette: {
|
||||
light: {},
|
||||
dark: {
|
||||
enabled: 'system',
|
||||
},
|
||||
},
|
||||
rippleEffect: true,
|
||||
formHighlight: true,
|
||||
config: {
|
||||
rippleEffect: true,
|
||||
formHighlight: true,
|
||||
},
|
||||
borderWidth: {
|
||||
sm: '4px',
|
||||
},
|
||||
@ -309,6 +313,7 @@ describe('injectCSS', () => {
|
||||
describe('generateGlobalThemeCSS', () => {
|
||||
it('should generate global CSS for a given theme', () => {
|
||||
const theme = {
|
||||
name: 'test',
|
||||
palette: {
|
||||
light: {},
|
||||
dark: {
|
||||
@ -339,6 +344,7 @@ describe('generateGlobalThemeCSS', () => {
|
||||
|
||||
it('should generate global CSS for a given theme with light palette', () => {
|
||||
const theme = {
|
||||
name: 'test',
|
||||
palette: {
|
||||
light: {
|
||||
color: {
|
||||
@ -425,6 +431,7 @@ describe('generateGlobalThemeCSS', () => {
|
||||
|
||||
it('should not include component or palette variables in global CSS', () => {
|
||||
const theme = {
|
||||
name: 'test',
|
||||
palette: {
|
||||
light: {},
|
||||
dark: {
|
||||
@ -479,6 +486,7 @@ describe('generateGlobalThemeCSS', () => {
|
||||
|
||||
it('should generate global CSS for a given theme with dark palette enabled for system preference', () => {
|
||||
const theme = {
|
||||
name: 'test',
|
||||
palette: {
|
||||
light: {},
|
||||
dark: {
|
||||
@ -598,6 +606,7 @@ describe('generateColorClasses', () => {
|
||||
|
||||
it('should generate color classes for a given theme', () => {
|
||||
const theme = {
|
||||
name: 'test',
|
||||
palette: {
|
||||
light: {
|
||||
color: {
|
||||
|
||||
@ -87,7 +87,7 @@ export const generateCSSVars = (theme: any, prefix: string = CSS_PROPS_PREFIX):
|
||||
}
|
||||
|
||||
// Do not generate CSS variables for excluded keys
|
||||
const excludedKeys = ['enabled', 'ripple-effect', 'form-highlight'];
|
||||
const excludedKeys = ['name', 'enabled', 'config'];
|
||||
if (excludedKeys.includes(key)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user