mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
feat(core): separate mode and theme
This commit is contained in:
15
core/api.txt
15
core/api.txt
@@ -1498,6 +1498,21 @@ ion-modal,part,backdrop
|
||||
ion-modal,part,content
|
||||
ion-modal,part,handle
|
||||
|
||||
ion-my-chip,shadow
|
||||
ion-my-chip,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true
|
||||
ion-my-chip,prop,disabled,boolean,false,false,false
|
||||
ion-my-chip,prop,hue,"bold" | "subtle" | undefined,'subtle',false,false
|
||||
ion-my-chip,prop,mode,"ios" | "md",undefined,false,false
|
||||
ion-my-chip,prop,outline,boolean,false,false,false
|
||||
ion-my-chip,prop,shape,"rectangular" | "round" | "soft" | undefined,'soft',false,false
|
||||
ion-my-chip,prop,size,"large" | "small" | undefined,'small',false,false
|
||||
ion-my-chip,prop,theme,"ios" | "md" | "ionic",undefined,false,false
|
||||
ion-my-chip,css-prop,--background
|
||||
ion-my-chip,css-prop,--border-radius
|
||||
ion-my-chip,css-prop,--color
|
||||
ion-my-chip,css-prop,--focus-ring-color
|
||||
ion-my-chip,css-prop,--focus-ring-width
|
||||
|
||||
ion-nav,shadow
|
||||
ion-nav,prop,animated,boolean,true,false,false
|
||||
ion-nav,prop,animation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false
|
||||
|
||||
77
core/src/components.d.ts
vendored
77
core/src/components.d.ts
vendored
@@ -2173,6 +2173,40 @@ export namespace Components {
|
||||
*/
|
||||
"trigger": string | undefined;
|
||||
}
|
||||
interface IonMyChip {
|
||||
/**
|
||||
* The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics).
|
||||
*/
|
||||
"color"?: Color;
|
||||
/**
|
||||
* If `true`, the user cannot interact with the chip.
|
||||
*/
|
||||
"disabled": boolean;
|
||||
/**
|
||||
* Set to `"bold"` for a chip with vibrant, bold colors or to `"subtle"` for a chip with muted, subtle colors. Only applies to the `ionic` theme.
|
||||
*/
|
||||
"hue"?: 'bold' | 'subtle';
|
||||
/**
|
||||
* The mode determines the platform behaviors of the component.
|
||||
*/
|
||||
"mode"?: "ios" | "md";
|
||||
/**
|
||||
* Display an outline style button.
|
||||
*/
|
||||
"outline": boolean;
|
||||
/**
|
||||
* Set to `"soft"` for a chip with slightly rounded corners, `"round"` for a chip with fully rounded corners, or `"rectangular"` for a chip without rounded corners. Defaults to `"round"` for the `"ionic"` theme and `"soft"` for all other themes.
|
||||
*/
|
||||
"shape"?: 'soft' | 'round' | 'rectangular';
|
||||
/**
|
||||
* Set to `"small"` for a chip with less height and padding. Defaults to `"large"` for the ionic theme, and undefined for all other themes.
|
||||
*/
|
||||
"size"?: 'small' | 'large';
|
||||
/**
|
||||
* The theme determines the visual appearance of the component.
|
||||
*/
|
||||
"theme"?: "ios" | "md" | "ionic";
|
||||
}
|
||||
interface IonNav {
|
||||
/**
|
||||
* If `true`, the nav should animate the transition of components.
|
||||
@@ -4759,6 +4793,12 @@ declare global {
|
||||
prototype: HTMLIonModalElement;
|
||||
new (): HTMLIonModalElement;
|
||||
};
|
||||
interface HTMLIonMyChipElement extends Components.IonMyChip, HTMLStencilElement {
|
||||
}
|
||||
var HTMLIonMyChipElement: {
|
||||
prototype: HTMLIonMyChipElement;
|
||||
new (): HTMLIonMyChipElement;
|
||||
};
|
||||
interface HTMLIonNavElementEventMap {
|
||||
"ionNavWillLoad": void;
|
||||
"ionNavWillChange": void;
|
||||
@@ -5457,6 +5497,7 @@ declare global {
|
||||
"ion-menu-button": HTMLIonMenuButtonElement;
|
||||
"ion-menu-toggle": HTMLIonMenuToggleElement;
|
||||
"ion-modal": HTMLIonModalElement;
|
||||
"ion-my-chip": HTMLIonMyChipElement;
|
||||
"ion-nav": HTMLIonNavElement;
|
||||
"ion-nav-link": HTMLIonNavLinkElement;
|
||||
"ion-note": HTMLIonNoteElement;
|
||||
@@ -7695,6 +7736,40 @@ declare namespace LocalJSX {
|
||||
*/
|
||||
"trigger"?: string | undefined;
|
||||
}
|
||||
interface IonMyChip {
|
||||
/**
|
||||
* The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics).
|
||||
*/
|
||||
"color"?: Color;
|
||||
/**
|
||||
* If `true`, the user cannot interact with the chip.
|
||||
*/
|
||||
"disabled"?: boolean;
|
||||
/**
|
||||
* Set to `"bold"` for a chip with vibrant, bold colors or to `"subtle"` for a chip with muted, subtle colors. Only applies to the `ionic` theme.
|
||||
*/
|
||||
"hue"?: 'bold' | 'subtle';
|
||||
/**
|
||||
* The mode determines the platform behaviors of the component.
|
||||
*/
|
||||
"mode"?: "ios" | "md";
|
||||
/**
|
||||
* Display an outline style button.
|
||||
*/
|
||||
"outline"?: boolean;
|
||||
/**
|
||||
* Set to `"soft"` for a chip with slightly rounded corners, `"round"` for a chip with fully rounded corners, or `"rectangular"` for a chip without rounded corners. Defaults to `"round"` for the `"ionic"` theme and `"soft"` for all other themes.
|
||||
*/
|
||||
"shape"?: 'soft' | 'round' | 'rectangular';
|
||||
/**
|
||||
* Set to `"small"` for a chip with less height and padding. Defaults to `"large"` for the ionic theme, and undefined for all other themes.
|
||||
*/
|
||||
"size"?: 'small' | 'large';
|
||||
/**
|
||||
* The theme determines the visual appearance of the component.
|
||||
*/
|
||||
"theme"?: "ios" | "md" | "ionic";
|
||||
}
|
||||
interface IonNav {
|
||||
/**
|
||||
* If `true`, the nav should animate the transition of components.
|
||||
@@ -9617,6 +9692,7 @@ declare namespace LocalJSX {
|
||||
"ion-menu-button": IonMenuButton;
|
||||
"ion-menu-toggle": IonMenuToggle;
|
||||
"ion-modal": IonModal;
|
||||
"ion-my-chip": IonMyChip;
|
||||
"ion-nav": IonNav;
|
||||
"ion-nav-link": IonNavLink;
|
||||
"ion-note": IonNote;
|
||||
@@ -9720,6 +9796,7 @@ declare module "@stencil/core" {
|
||||
"ion-menu-button": LocalJSX.IonMenuButton & JSXBase.HTMLAttributes<HTMLIonMenuButtonElement>;
|
||||
"ion-menu-toggle": LocalJSX.IonMenuToggle & JSXBase.HTMLAttributes<HTMLIonMenuToggleElement>;
|
||||
"ion-modal": LocalJSX.IonModal & JSXBase.HTMLAttributes<HTMLIonModalElement>;
|
||||
"ion-my-chip": LocalJSX.IonMyChip & JSXBase.HTMLAttributes<HTMLIonMyChipElement>;
|
||||
"ion-nav": LocalJSX.IonNav & JSXBase.HTMLAttributes<HTMLIonNavElement>;
|
||||
"ion-nav-link": LocalJSX.IonNavLink & JSXBase.HTMLAttributes<HTMLIonNavLinkElement>;
|
||||
"ion-note": LocalJSX.IonNote & JSXBase.HTMLAttributes<HTMLIonNoteElement>;
|
||||
|
||||
156
core/src/components/my-chip/my-chip.base.scss
Normal file
156
core/src/components/my-chip/my-chip.base.scss
Normal file
@@ -0,0 +1,156 @@
|
||||
// @import "../../themes/native/native.globals";
|
||||
@use "../../themes/functions.color" as color;
|
||||
@use "../../themes/mixins" as mixins;
|
||||
|
||||
:host {
|
||||
/**
|
||||
* @prop --background: Background of the chip
|
||||
* @prop --color: Color of the chip
|
||||
* @prop --border-radius: Border radius of the chip
|
||||
* @prop --focus-ring-color: Color of the focus ring
|
||||
* @prop --focus-ring-width: Width of the focus ring
|
||||
*/
|
||||
--focus-ring-color: var(--ion-color-blue-50);
|
||||
--focus-ring-width: var(--ion-spacing-xs);
|
||||
|
||||
@include mixins.font-smoothing();
|
||||
@include mixins.padding(var(--ion-spacing-xs), var(--ion-spacing-sm));
|
||||
@include mixins.border-radius(var(--border-radius));
|
||||
|
||||
display: inline-flex;
|
||||
|
||||
position: relative;
|
||||
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
background: var(--background);
|
||||
color: var(--color);
|
||||
|
||||
font-weight: var(--ion-font-weights-normal);
|
||||
|
||||
line-height: var(--ion-line-heights-md);
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
vertical-align: middle;
|
||||
|
||||
gap: var(--ion-spacing-xs);
|
||||
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:host(.chip-disabled) {
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
// Outline Chip
|
||||
// ---------------------------------------------
|
||||
|
||||
:host(.chip-outline) {
|
||||
border-width: var(--ion-spacing-xxs);
|
||||
border-style: solid; // Do we need to use a variable here? ionic uses tokens but all themes use solid so it's not really necessary
|
||||
}
|
||||
|
||||
// Chip: Focus
|
||||
// ---------------------------------------------
|
||||
|
||||
:host(.ion-focused) {
|
||||
outline: var(--focus-ring-width) solid var(--focus-ring-color);
|
||||
outline-offset: var(--focus-ring-width);
|
||||
}
|
||||
|
||||
// Chip Shapes
|
||||
// ---------------------------------------------
|
||||
|
||||
:host(.chip-soft) {
|
||||
--border-radius: var(--ion-radii-lg);
|
||||
}
|
||||
|
||||
:host(.chip-round) {
|
||||
--border-radius: var(--ion-radii-xxl);
|
||||
}
|
||||
|
||||
:host(.chip-rectangular) {
|
||||
--border-radius: var(--ion-radii-none);
|
||||
}
|
||||
|
||||
// Size
|
||||
// ---------------------------------------------
|
||||
|
||||
:host(.chip-small) {
|
||||
min-height: var(--ion-spacing-lg);
|
||||
|
||||
font-size: var(--ion-font-sizs-sm);
|
||||
}
|
||||
|
||||
:host(.chip-large) {
|
||||
min-height: var(--ion-spacing-xxl);
|
||||
|
||||
font-size: var(--ion-font-sizes-lg);
|
||||
}
|
||||
|
||||
// Subtle Chip
|
||||
// ---------------------------------------------
|
||||
|
||||
:host(.chip-subtle) {
|
||||
--background: var(--ion-color-gray-100);
|
||||
--color: var(--ion-color-gray-800);
|
||||
}
|
||||
|
||||
:host(.chip-outline.chip-subtle) {
|
||||
border-color: var(--ion-color-gray-300);
|
||||
}
|
||||
|
||||
// Bold Chip
|
||||
// ---------------------------------------------
|
||||
|
||||
:host(.chip-bold) {
|
||||
--background: var(--ion-color-gray-700);
|
||||
--color: var(--ion-color-white);
|
||||
}
|
||||
|
||||
:host(.chip-outline.chip-bold) {
|
||||
border-color: var(--ion-color-gray-800);
|
||||
}
|
||||
|
||||
// Chip Colors
|
||||
// ---------------------------------------------
|
||||
|
||||
// Subtle
|
||||
:host(.chip-subtle.ion-color) {
|
||||
background: color.current-color(
|
||||
base,
|
||||
$subtle: true
|
||||
); // these don't work because we need to update the function to use the new color system
|
||||
color: color.current-color(
|
||||
contrast,
|
||||
$subtle: true
|
||||
); // these don't work because we need to update the function to use the new color system
|
||||
}
|
||||
|
||||
:host(.chip-subtle.chip-outline.ion-color) {
|
||||
border-color: color.current-color(
|
||||
shade,
|
||||
$subtle: true
|
||||
); // these don't work because we need to update the function to use the new color system
|
||||
}
|
||||
|
||||
// Bold
|
||||
:host(.chip-bold.ion-color) {
|
||||
background: color.current-color(
|
||||
base
|
||||
); // these don't work because we need to update the function to use the new color system
|
||||
color: color.current-color(
|
||||
contrast
|
||||
); // these don't work because we need to update the function to use the new color system
|
||||
}
|
||||
|
||||
:host(.chip-bold.chip-outline.ion-color) {
|
||||
border-color: color.current-color(
|
||||
shade
|
||||
); // these don't work because we need to update the function to use the new color system
|
||||
}
|
||||
106
core/src/components/my-chip/my-chip.tsx
Normal file
106
core/src/components/my-chip/my-chip.tsx
Normal file
@@ -0,0 +1,106 @@
|
||||
import type { ComponentInterface } from '@stencil/core';
|
||||
import { Component, Host, Prop, h, Element } from '@stencil/core';
|
||||
import { createColorClasses } from '@utils/theme';
|
||||
|
||||
import { getIonMode, getIonCustomTheme } from '../../global/ionic-global';
|
||||
import type { Color } from '../../interface';
|
||||
import { generateCSSVars } from '../../themes/base/generate-css-vars';
|
||||
|
||||
/**
|
||||
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of the component.
|
||||
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the visual appearance of the component.
|
||||
*/
|
||||
@Component({
|
||||
tag: 'ion-my-chip',
|
||||
styleUrl: 'my-chip.base.scss',
|
||||
shadow: true,
|
||||
})
|
||||
export class MyChip implements ComponentInterface {
|
||||
@Element() el!: HTMLElement;
|
||||
|
||||
/**
|
||||
* The color to use from your application's color palette.
|
||||
* Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`.
|
||||
* For more information on colors, see [theming](/docs/theming/basics).
|
||||
*/
|
||||
@Prop({ reflect: true }) color?: Color;
|
||||
|
||||
/**
|
||||
* Display an outline style button.
|
||||
*/
|
||||
@Prop() outline = false;
|
||||
|
||||
/**
|
||||
* If `true`, the user cannot interact with the chip.
|
||||
*/
|
||||
@Prop() disabled = false;
|
||||
|
||||
/**
|
||||
* Set to `"bold"` for a chip with vibrant, bold colors or to `"subtle"` for
|
||||
* a chip with muted, subtle colors.
|
||||
*
|
||||
* Only applies to the `ionic` theme.
|
||||
*/
|
||||
// THOUGHT: Comparing to ChakraUI, they use variants (aka fills) for this
|
||||
// maybe we should consider using variants: solid, outline, subtle
|
||||
// they use solid for bold solid background, no outline, has states
|
||||
// subtle for light background, no outline, has states
|
||||
// outline for outline style, no background, has states
|
||||
// surface for light background, with outline, has states
|
||||
// ghost for no background, no outline, has states
|
||||
// plain for no background, no outline, no states
|
||||
@Prop() hue?: 'bold' | 'subtle' = 'subtle';
|
||||
|
||||
/**
|
||||
* Set to `"soft"` for a chip with slightly rounded corners, `"round"` for a chip with fully
|
||||
* rounded corners, or `"rectangular"` for a chip without rounded corners.
|
||||
* Defaults to `"round"` for the `"ionic"` theme and `"soft"` for all other themes.
|
||||
*/
|
||||
@Prop() shape?: 'soft' | 'round' | 'rectangular' = 'soft';
|
||||
|
||||
/**
|
||||
* Set to `"small"` for a chip with less height and padding.
|
||||
*
|
||||
* Defaults to `"large"` for the ionic theme, and undefined for all other themes.
|
||||
*/
|
||||
@Prop() size?: 'small' | 'large' = 'small';
|
||||
|
||||
componentWillLoad() {
|
||||
const myCustomTheme = getIonCustomTheme();
|
||||
const componentTheme = myCustomTheme.components['IonChip'];
|
||||
|
||||
// check if componentTheme is not an empty object or undefined
|
||||
if (componentTheme !== undefined || Object.keys(componentTheme).length > 0) {
|
||||
// apply a style tag to this component
|
||||
const style = document.createElement('style');
|
||||
style.innerHTML = [generateCSSVars(componentTheme, '--ion-', ':host')].join('\n\n');
|
||||
|
||||
// Attach to Shadow Root if available, otherwise Light DOM
|
||||
const root = this.el.shadowRoot ?? this.el;
|
||||
root.appendChild(style);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { hue, shape, size } = this;
|
||||
const mode = getIonMode(this);
|
||||
|
||||
return (
|
||||
<Host
|
||||
aria-disabled={this.disabled ? 'true' : null}
|
||||
class={createColorClasses(this.color, {
|
||||
[`chip-${shape}`]: shape !== undefined,
|
||||
'chip-outline': this.outline,
|
||||
'chip-disabled': this.disabled,
|
||||
'ion-activatable': true,
|
||||
'ion-focusable': !this.disabled,
|
||||
[`chip-${size}`]: size !== undefined,
|
||||
[`chip-${hue}`]: hue !== undefined,
|
||||
})}
|
||||
>
|
||||
<slot></slot>
|
||||
{mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
|
||||
</Host>
|
||||
);
|
||||
}
|
||||
}
|
||||
95
core/src/components/my-chip/test/basic/index.html
Normal file
95
core/src/components/my-chip/test/basic/index.html
Normal file
@@ -0,0 +1,95 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>MyChip - Basic</title>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
||||
/>
|
||||
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
|
||||
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
|
||||
<script src="../../../../../scripts/testing/scripts.js"></script>
|
||||
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
|
||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<ion-app>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>MyChip - Basic</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding" id="content" style="text-align: center">
|
||||
<h2>My Chip</h2>
|
||||
<p>
|
||||
This is a POC, not all the features are working as expected. For example, the color property still needs to be
|
||||
worked on.
|
||||
</p>
|
||||
<p>You can verify that the variables are being generated correctly by inspecting the root element.</p>
|
||||
|
||||
<ion-my-chip>default</ion-my-chip>
|
||||
|
||||
<ion-my-chip outline>outline</ion-my-chip>
|
||||
|
||||
<ion-my-chip size="large">large</ion-my-chip>
|
||||
|
||||
<ion-my-chip hue="bold">bold</ion-my-chip>
|
||||
<ion-my-chip hue="bold" outline>bold with outline</ion-my-chip>
|
||||
|
||||
<ion-my-chip color="primary">primary, subtle hue</ion-my-chip>
|
||||
<ion-my-chip color="primary" hue="bold">primary, bold hue</ion-my-chip>
|
||||
|
||||
<ion-my-chip color="secondary">secondary, subtle hue</ion-my-chip>
|
||||
<ion-my-chip color="secondary" hue="bold">secondary, bold hue</ion-my-chip>
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
|
||||
<script>
|
||||
window.Ionic = {
|
||||
config: {
|
||||
customTheme: {
|
||||
palette: {
|
||||
light: {
|
||||
color: {
|
||||
primary: {
|
||||
bold: {
|
||||
base: 'pink',
|
||||
constrast: 'black',
|
||||
},
|
||||
},
|
||||
secondary: {
|
||||
bold: {
|
||||
base: '#510aa8',
|
||||
},
|
||||
},
|
||||
// gray: {
|
||||
// 800: 'pink',
|
||||
// }
|
||||
},
|
||||
},
|
||||
},
|
||||
components: {
|
||||
IonChip: {
|
||||
spacing: {
|
||||
sm: '12px',
|
||||
},
|
||||
palette: {
|
||||
light: {
|
||||
color: {
|
||||
gray: {
|
||||
800: 'pink',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -2,6 +2,9 @@ import { Build, getMode, setMode, getElement } from '@stencil/core';
|
||||
import { printIonWarning } from '@utils/logging';
|
||||
|
||||
import type { IonicConfig, Mode, Theme } from '../interface';
|
||||
import { defaultTheme as baseTheme } from '../themes/base/base.tokens';
|
||||
import type { Theme as BaseTheme } from '../themes/base/base.tokens';
|
||||
import { deepMerge, generateCSSVars } from '../themes/base/generate-css-vars';
|
||||
import { shouldUseCloseWatcher } from '../utils/hardware-back-button';
|
||||
import { isPlatform, setupPlatforms } from '../utils/platform';
|
||||
|
||||
@@ -25,6 +28,23 @@ const printInvalidModeWarning = (mode: Mode, theme: Theme, ref?: any) => {
|
||||
);
|
||||
};
|
||||
|
||||
const applyTheme = (userTheme: BaseTheme, prefix?: string) => {
|
||||
const mergedTheme = deepMerge(baseTheme, userTheme);
|
||||
const { palette, components, ...restTokens } = mergedTheme;
|
||||
const { enabled, ...restDarkTokens } = palette.dark;
|
||||
|
||||
config.set('customTheme', mergedTheme);
|
||||
|
||||
const style = document.createElement('style');
|
||||
style.innerHTML = [generateCSSVars(restTokens, prefix), generateCSSVars(palette.light, prefix)].join('\n\n');
|
||||
|
||||
if (enabled === 'system') {
|
||||
style.innerHTML += `@media (prefers-color-scheme: dark) {\n${generateCSSVars(restDarkTokens, prefix)}\n}`;
|
||||
}
|
||||
|
||||
document.head.appendChild(style);
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates if a mode is accepted for a theme configuration.
|
||||
* @param mode The mode to validate.
|
||||
@@ -132,6 +152,14 @@ export const getIonTheme = (ref?: any): Theme => {
|
||||
return defaultTheme;
|
||||
};
|
||||
|
||||
export const getIonCustomTheme = (): any => {
|
||||
const customTheme = config.get('customTheme');
|
||||
if (customTheme) {
|
||||
return customTheme;
|
||||
}
|
||||
return defaultTheme;
|
||||
};
|
||||
|
||||
export const rIC = (callback: () => void) => {
|
||||
if ('requestIdleCallback' in window) {
|
||||
(window as any).requestIdleCallback(callback);
|
||||
@@ -225,6 +253,12 @@ export const initialize = (userConfig: IonicConfig = {}) => {
|
||||
doc.documentElement.setAttribute('theme', defaultTheme);
|
||||
doc.documentElement.classList.add(defaultTheme);
|
||||
|
||||
const customTheme: BaseTheme | undefined = configObj.customTheme;
|
||||
|
||||
if (customTheme) {
|
||||
applyTheme(customTheme);
|
||||
}
|
||||
|
||||
if (config.getBoolean('_testing')) {
|
||||
config.set('animated', false);
|
||||
}
|
||||
|
||||
63
core/src/themes/base/base.tokens.ts
Normal file
63
core/src/themes/base/base.tokens.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { defaultDarkTheme } from './dark.tokens';
|
||||
import { defaultLightTheme } from './light.tokens';
|
||||
|
||||
export const defaultTheme = {
|
||||
palette: {
|
||||
light: defaultLightTheme,
|
||||
dark: defaultDarkTheme,
|
||||
},
|
||||
// so far spacing is being used for
|
||||
// padding, margin, gap, border-width, height, width
|
||||
// should we create scale for height and width instead?
|
||||
// or keep it as is but change the keys to be numerical
|
||||
// like 0.5, 1, 1.5, 2, 2.5, 3, etc?
|
||||
spacing: {
|
||||
none: '0',
|
||||
xxs: '2px',
|
||||
xs: '4px',
|
||||
sm: '8px',
|
||||
md: '16px',
|
||||
lg: '24px',
|
||||
xl: '32px',
|
||||
xxl: '40px',
|
||||
},
|
||||
radii: {
|
||||
none: '0',
|
||||
xs: '1px',
|
||||
sm: '2px',
|
||||
md: '4px',
|
||||
lg: '8px',
|
||||
xl: '16px',
|
||||
xxl: '32px',
|
||||
},
|
||||
fontWeights: {
|
||||
thin: '100',
|
||||
extraLight: '200',
|
||||
light: '300',
|
||||
normal: '400',
|
||||
medium: '500',
|
||||
semiBold: '600',
|
||||
bold: '700',
|
||||
extraBold: '800',
|
||||
black: '900',
|
||||
},
|
||||
fontSizes: {
|
||||
xs: '12px',
|
||||
sm: '14px',
|
||||
md: '16px',
|
||||
lg: '18px',
|
||||
xl: '20px',
|
||||
xxl: '24px',
|
||||
},
|
||||
lineHeights: {
|
||||
xs: '1.2',
|
||||
sm: '1.4',
|
||||
md: '1.6',
|
||||
lg: '1.8',
|
||||
xl: '2',
|
||||
xxl: '2.4',
|
||||
},
|
||||
components: {},
|
||||
};
|
||||
|
||||
export type Theme = typeof defaultTheme;
|
||||
58
core/src/themes/base/dark.tokens.ts
Normal file
58
core/src/themes/base/dark.tokens.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
export const defaultDarkTheme = {
|
||||
enabled: 'never', // 'always' | 'system' | 'class' | 'never'
|
||||
color: {
|
||||
primary: {
|
||||
bold: {
|
||||
base: '#0054e9',
|
||||
constrast: '#ffffff',
|
||||
shade: '#0041c4', // darker version
|
||||
tint: '#0065ff', // lighter version
|
||||
},
|
||||
subtle: {
|
||||
base: '#0054e9',
|
||||
constrast: '#ffffff',
|
||||
shade: '#0041c4', // darker version
|
||||
tint: '#0065ff', // lighter version
|
||||
},
|
||||
},
|
||||
secondary: {
|
||||
bold: {
|
||||
base: '#0163aa',
|
||||
constrast: '#ffffff',
|
||||
shade: '#015a9e',
|
||||
tint: '#0176c4',
|
||||
},
|
||||
subtle: {
|
||||
base: '#0163aa',
|
||||
constrast: '#ffffff',
|
||||
shade: '#015a9e',
|
||||
tint: '#0176c4',
|
||||
},
|
||||
},
|
||||
red: {
|
||||
50: '#ffebee',
|
||||
100: '#ffcdd2',
|
||||
200: '#ef9a9a',
|
||||
},
|
||||
blue: {
|
||||
50: '#e3f2fd',
|
||||
100: '#bbdefb',
|
||||
200: '#90caf9',
|
||||
},
|
||||
gray: {
|
||||
50: '#f5f5f5',
|
||||
100: '#eeeeee',
|
||||
200: '#e0e0e0',
|
||||
300: '#bdbdbd',
|
||||
400: '#9e9e9e',
|
||||
500: '#757575',
|
||||
600: '#616161',
|
||||
700: '#424242',
|
||||
800: '#212121',
|
||||
},
|
||||
white: 'green',
|
||||
black: '#000000',
|
||||
},
|
||||
};
|
||||
|
||||
export type Theme = typeof defaultDarkTheme;
|
||||
30
core/src/themes/base/generate-css-vars.ts
Normal file
30
core/src/themes/base/generate-css-vars.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
// import type { Theme } from './base.tokens';
|
||||
|
||||
export function generateCSSVars(theme: any, themePrefix = '--ion-', selector = ':root'): string {
|
||||
const flatten = (obj: any, prefix = themePrefix): string =>
|
||||
Object.entries(obj)
|
||||
.flatMap(([key, val]) => {
|
||||
// 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();
|
||||
}
|
||||
return typeof val === 'object' && val !== null
|
||||
? flatten(val, `${prefix}${key}-`)
|
||||
: [`${prefix}${key}: ${val};`];
|
||||
})
|
||||
.join('\n');
|
||||
|
||||
return `${selector} {\n${flatten(theme)}\n}`;
|
||||
}
|
||||
|
||||
// Simple deep merge function
|
||||
export function deepMerge(target: any, source: any): any {
|
||||
for (const key in source) {
|
||||
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
||||
target[key] = deepMerge(target[key] ?? {}, source[key]);
|
||||
} else {
|
||||
target[key] = source[key];
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
57
core/src/themes/base/light.tokens.ts
Normal file
57
core/src/themes/base/light.tokens.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
export const defaultLightTheme = {
|
||||
color: {
|
||||
primary: {
|
||||
bold: {
|
||||
base: '#0054e9',
|
||||
constrast: '#ffffff',
|
||||
shade: '#0041c4', // darker version
|
||||
tint: '#0065ff', // lighter version
|
||||
},
|
||||
subtle: {
|
||||
base: '#0054e9',
|
||||
constrast: '#ffffff',
|
||||
shade: '#0041c4', // darker version
|
||||
tint: '#0065ff', // lighter version
|
||||
},
|
||||
},
|
||||
secondary: {
|
||||
bold: {
|
||||
base: '#0163aa',
|
||||
constrast: '#ffffff',
|
||||
shade: '#015a9e',
|
||||
tint: '#0176c4',
|
||||
},
|
||||
subtle: {
|
||||
base: '#0163aa',
|
||||
constrast: '#ffffff',
|
||||
shade: '#015a9e',
|
||||
tint: '#0176c4',
|
||||
},
|
||||
},
|
||||
red: {
|
||||
50: '#ffebee',
|
||||
100: '#ffcdd2',
|
||||
200: '#ef9a9a',
|
||||
},
|
||||
blue: {
|
||||
50: '#e3f2fd',
|
||||
100: '#bbdefb',
|
||||
200: '#90caf9',
|
||||
},
|
||||
gray: {
|
||||
50: '#f5f5f5',
|
||||
100: '#eeeeee',
|
||||
200: '#e0e0e0',
|
||||
300: '#bdbdbd',
|
||||
400: '#9e9e9e',
|
||||
500: '#757575',
|
||||
600: '#616161',
|
||||
700: '#424242',
|
||||
800: '#212121',
|
||||
},
|
||||
white: '#ffffff',
|
||||
black: '#000000',
|
||||
},
|
||||
};
|
||||
|
||||
export type Theme = typeof defaultLightTheme;
|
||||
@@ -364,6 +364,8 @@ export interface IonicConfig {
|
||||
scrollAssist?: boolean;
|
||||
hideCaretOnScroll?: boolean;
|
||||
|
||||
customTheme?: any;
|
||||
|
||||
// INTERNAL configs
|
||||
// TODO(FW-2832): types
|
||||
persistConfig?: boolean;
|
||||
|
||||
@@ -51,6 +51,7 @@ export const DIRECTIVES = [
|
||||
d.IonMenu,
|
||||
d.IonMenuButton,
|
||||
d.IonMenuToggle,
|
||||
d.IonMyChip,
|
||||
d.IonNavLink,
|
||||
d.IonNote,
|
||||
d.IonPicker,
|
||||
|
||||
@@ -1426,6 +1426,28 @@ export class IonMenuToggle {
|
||||
export declare interface IonMenuToggle extends Components.IonMenuToggle {}
|
||||
|
||||
|
||||
@ProxyCmp({
|
||||
inputs: ['color', 'disabled', 'hue', 'mode', 'outline', 'shape', 'size', 'theme']
|
||||
})
|
||||
@Component({
|
||||
selector: 'ion-my-chip',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: '<ng-content></ng-content>',
|
||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||
inputs: ['color', 'disabled', 'hue', 'mode', 'outline', 'shape', 'size', 'theme'],
|
||||
})
|
||||
export class IonMyChip {
|
||||
protected el: HTMLIonMyChipElement;
|
||||
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
|
||||
c.detach();
|
||||
this.el = r.nativeElement;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export declare interface IonMyChip extends Components.IonMyChip {}
|
||||
|
||||
|
||||
@ProxyCmp({
|
||||
inputs: ['component', 'componentProps', 'mode', 'routerAnimation', 'routerDirection', 'theme']
|
||||
})
|
||||
|
||||
@@ -51,6 +51,7 @@ import { defineCustomElement as defineIonLoading } from '@ionic/core/components/
|
||||
import { defineCustomElement as defineIonMenu } from '@ionic/core/components/ion-menu.js';
|
||||
import { defineCustomElement as defineIonMenuButton } from '@ionic/core/components/ion-menu-button.js';
|
||||
import { defineCustomElement as defineIonMenuToggle } from '@ionic/core/components/ion-menu-toggle.js';
|
||||
import { defineCustomElement as defineIonMyChip } from '@ionic/core/components/ion-my-chip.js';
|
||||
import { defineCustomElement as defineIonNavLink } from '@ionic/core/components/ion-nav-link.js';
|
||||
import { defineCustomElement as defineIonNote } from '@ionic/core/components/ion-note.js';
|
||||
import { defineCustomElement as defineIonPicker } from '@ionic/core/components/ion-picker.js';
|
||||
@@ -1420,6 +1421,30 @@ export class IonMenuToggle {
|
||||
export declare interface IonMenuToggle extends Components.IonMenuToggle {}
|
||||
|
||||
|
||||
@ProxyCmp({
|
||||
defineCustomElementFn: defineIonMyChip,
|
||||
inputs: ['color', 'disabled', 'hue', 'mode', 'outline', 'shape', 'size', 'theme']
|
||||
})
|
||||
@Component({
|
||||
selector: 'ion-my-chip',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: '<ng-content></ng-content>',
|
||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||
inputs: ['color', 'disabled', 'hue', 'mode', 'outline', 'shape', 'size', 'theme'],
|
||||
standalone: true
|
||||
})
|
||||
export class IonMyChip {
|
||||
protected el: HTMLIonMyChipElement;
|
||||
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
|
||||
c.detach();
|
||||
this.el = r.nativeElement;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export declare interface IonMyChip extends Components.IonMyChip {}
|
||||
|
||||
|
||||
@ProxyCmp({
|
||||
defineCustomElementFn: defineIonNavLink,
|
||||
inputs: ['component', 'componentProps', 'mode', 'routerAnimation', 'routerDirection', 'theme']
|
||||
|
||||
@@ -43,6 +43,7 @@ import { defineCustomElement as defineIonListHeader } from '@ionic/core/componen
|
||||
import { defineCustomElement as defineIonMenu } from '@ionic/core/components/ion-menu.js';
|
||||
import { defineCustomElement as defineIonMenuButton } from '@ionic/core/components/ion-menu-button.js';
|
||||
import { defineCustomElement as defineIonMenuToggle } from '@ionic/core/components/ion-menu-toggle.js';
|
||||
import { defineCustomElement as defineIonMyChip } from '@ionic/core/components/ion-my-chip.js';
|
||||
import { defineCustomElement as defineIonNav } from '@ionic/core/components/ion-nav.js';
|
||||
import { defineCustomElement as defineIonNavLink } from '@ionic/core/components/ion-nav-link.js';
|
||||
import { defineCustomElement as defineIonNote } from '@ionic/core/components/ion-note.js';
|
||||
@@ -116,6 +117,7 @@ export const IonListHeader = /*@__PURE__*/createReactComponent<JSX.IonListHeader
|
||||
export const IonMenu = /*@__PURE__*/createReactComponent<JSX.IonMenu, HTMLIonMenuElement>('ion-menu', undefined, undefined, defineIonMenu);
|
||||
export const IonMenuButton = /*@__PURE__*/createReactComponent<JSX.IonMenuButton, HTMLIonMenuButtonElement>('ion-menu-button', undefined, undefined, defineIonMenuButton);
|
||||
export const IonMenuToggle = /*@__PURE__*/createReactComponent<JSX.IonMenuToggle, HTMLIonMenuToggleElement>('ion-menu-toggle', undefined, undefined, defineIonMenuToggle);
|
||||
export const IonMyChip = /*@__PURE__*/createReactComponent<JSX.IonMyChip, HTMLIonMyChipElement>('ion-my-chip', undefined, undefined, defineIonMyChip);
|
||||
export const IonNav = /*@__PURE__*/createReactComponent<JSX.IonNav, HTMLIonNavElement>('ion-nav', undefined, undefined, defineIonNav);
|
||||
export const IonNavLink = /*@__PURE__*/createReactComponent<JSX.IonNavLink, HTMLIonNavLinkElement>('ion-nav-link', undefined, undefined, defineIonNavLink);
|
||||
export const IonNote = /*@__PURE__*/createReactComponent<JSX.IonNote, HTMLIonNoteElement>('ion-note', undefined, undefined, defineIonNote);
|
||||
|
||||
@@ -49,6 +49,7 @@ import { defineCustomElement as defineIonListHeader } from '@ionic/core/componen
|
||||
import { defineCustomElement as defineIonMenu } from '@ionic/core/components/ion-menu.js';
|
||||
import { defineCustomElement as defineIonMenuButton } from '@ionic/core/components/ion-menu-button.js';
|
||||
import { defineCustomElement as defineIonMenuToggle } from '@ionic/core/components/ion-menu-toggle.js';
|
||||
import { defineCustomElement as defineIonMyChip } from '@ionic/core/components/ion-my-chip.js';
|
||||
import { defineCustomElement as defineIonNav } from '@ionic/core/components/ion-nav.js';
|
||||
import { defineCustomElement as defineIonNavLink } from '@ionic/core/components/ion-nav-link.js';
|
||||
import { defineCustomElement as defineIonNote } from '@ionic/core/components/ion-note.js';
|
||||
@@ -639,6 +640,16 @@ export const IonMenuToggle: StencilVueComponent<JSX.IonMenuToggle> = /*@__PURE__
|
||||
]);
|
||||
|
||||
|
||||
export const IonMyChip: StencilVueComponent<JSX.IonMyChip> = /*@__PURE__*/ defineContainer<JSX.IonMyChip>('ion-my-chip', defineIonMyChip, [
|
||||
'color',
|
||||
'outline',
|
||||
'disabled',
|
||||
'hue',
|
||||
'shape',
|
||||
'size'
|
||||
]);
|
||||
|
||||
|
||||
export const IonNav: StencilVueComponent<JSX.IonNav> = /*@__PURE__*/ defineContainer<JSX.IonNav>('ion-nav', defineIonNav, [
|
||||
'delegate',
|
||||
'swipeGesture',
|
||||
|
||||
Reference in New Issue
Block a user