diff --git a/BREAKING.md b/BREAKING.md index 12fca9b9580..3e62ae575d0 100644 --- a/BREAKING.md +++ b/BREAKING.md @@ -26,6 +26,7 @@ This is a comprehensive list of the breaking changes introduced in the major ver - [Item Divider](#version-9x-item-divider) - [Radio Group](#version-9x-radio-group) - [Spinner](#version-9x-spinner) + - [Text](#version-9x-text) - [Textarea](#version-9x-textarea)

Global Styles

@@ -289,6 +290,28 @@ Additionally, the `radio-group-wrapper` div element has been removed, causing sl - `.spinner-[spinner-name]` → `.spinner-name-[spinner-name]` - Specific theme classes (e.g., `ion-spinner.md`) are no longer supported. Style modifications based on the active theme must be implemented using theme tokens rather than direct class targeting. +

Text

+ +The following breaking changes apply to `ion-text`: + +1. The color applied by the `color` prop is now driven by the centralized Ionic Theming system, scoped to the new `hue` property. +2. Theme classes (`ion-text.md`, `ion-text.ios`) are no longer supported. + +
New `hue` property and color tokens
+ +A new `hue` property selects between vibrant and muted color variants. It defaults to `"bold"`, which preserves prior behavior when `color` is set. + +When `color` is set, the text color now reads from a token instead of `--ion-color-base` directly. Global overrides should use the theme tokens; component-specific overrides use the corresponding CSS variables: + +| Hue | Token (global) | CSS variable (component-specific) | +|---|---|---| +| `bold` | `IonText.hue.bold.semantic.default.color` | `--ion-text-hue-bold-semantic-default-color` | +| `subtle` | `IonText.hue.subtle.semantic.default.color` | `--ion-text-hue-subtle-semantic-default-color` | + +
Theme classes
+ +Remove any instances that target the theme classes: `ion-text.md`, `ion-text.ios`. +

Textarea

Converted `ion-textarea` to use [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM). diff --git a/core/api.txt b/core/api.txt index 618916e5be4..6defeed5dc0 100644 --- a/core/api.txt +++ b/core/api.txt @@ -2699,8 +2699,10 @@ ion-tabs,event,ionTabsWillChange,{ tab: string; },false ion-text,shadow ion-text,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true +ion-text,prop,hue,"bold" | "subtle" | undefined,undefined,false,false ion-text,prop,mode,"ios" | "md",undefined,false,false -ion-text,prop,theme,"ios" | "md" | "ionic",undefined,false,false +ion-text,css-prop,--ion-text-hue-bold-semantic-default-color +ion-text,css-prop,--ion-text-hue-subtle-semantic-default-color ion-textarea,shadow ion-textarea,prop,autoGrow,boolean,false,false,true diff --git a/core/src/components.d.ts b/core/src/components.d.ts index 1b7292c8df1..440c0e6ceb8 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -44,6 +44,7 @@ import { SelectModalOption } from "./components/select-modal/select-modal-interf import { SelectPopoverOption } from "./components/select-popover/select-popover-interface"; import { SpinnerSize } from "./components/spinner/spinner.interfaces"; import { TabBarChangedEventDetail, TabButtonClickEventDetail, TabButtonLayout } from "./components/tab-bar/tab-bar-interface"; +import { IonTextHue } from "./components/text/text.interfaces"; import { TextareaChangeEventDetail, TextareaInputEventDetail } from "./components/textarea/textarea-interface"; import { ToastButton, ToastDismissOptions, ToastLayout, ToastPosition, ToastPresentOptions, ToastSwipeGestureDirection } from "./components/toast/toast-interface"; import { ToggleChangeEventDetail } from "./components/toggle/toggle-interface"; @@ -86,6 +87,7 @@ export { SelectModalOption } from "./components/select-modal/select-modal-interf export { SelectPopoverOption } from "./components/select-popover/select-popover-interface"; export { SpinnerSize } from "./components/spinner/spinner.interfaces"; export { TabBarChangedEventDetail, TabButtonClickEventDetail, TabButtonLayout } from "./components/tab-bar/tab-bar-interface"; +export { IonTextHue } from "./components/text/text.interfaces"; export { TextareaChangeEventDetail, TextareaInputEventDetail } from "./components/textarea/textarea-interface"; export { ToastButton, ToastDismissOptions, ToastLayout, ToastPosition, ToastPresentOptions, ToastSwipeGestureDirection } from "./components/toast/toast-interface"; export { ToggleChangeEventDetail } from "./components/toggle/toggle-interface"; @@ -4075,13 +4077,13 @@ export namespace Components { */ "color"?: Color; /** - * The mode determines the platform behaviors of the component. + * Set to `"bold"` for a text with vibrant, bold colors or to `"subtle"` for a text with muted, subtle colors. Defaults to `"bold"` if both the hue property and theme config are unset. */ - "mode"?: "ios" | "md"; + "hue"?: IonTextHue; /** - * The theme determines the visual appearance of the component. + * The mode determines the platform behaviors of the component. */ - "theme"?: "ios" | "md" | "ionic"; + "mode"?: "ios" | "md"; } interface IonTextarea { /** @@ -10155,13 +10157,13 @@ declare namespace LocalJSX { */ "color"?: Color; /** - * The mode determines the platform behaviors of the component. + * Set to `"bold"` for a text with vibrant, bold colors or to `"subtle"` for a text with muted, subtle colors. Defaults to `"bold"` if both the hue property and theme config are unset. */ - "mode"?: "ios" | "md"; + "hue"?: IonTextHue; /** - * The theme determines the visual appearance of the component. + * The mode determines the platform behaviors of the component. */ - "theme"?: "ios" | "md" | "ionic"; + "mode"?: "ios" | "md"; } interface IonTextarea { /** @@ -11313,6 +11315,7 @@ declare namespace LocalJSX { } interface IonTextAttributes { "color": Color; + "hue": IonTextHue; } interface IonTextareaAttributes { "color": Color; diff --git a/core/src/components/text/test/basic/text.e2e.ts-snapshots/text-color-md-ltr-Mobile-Chrome-linux.png b/core/src/components/text/test/basic/text.e2e.ts-snapshots/text-color-md-ltr-Mobile-Chrome-linux.png index f5fe40421a6..9ae6f0920aa 100644 Binary files a/core/src/components/text/test/basic/text.e2e.ts-snapshots/text-color-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/text/test/basic/text.e2e.ts-snapshots/text-color-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/text/test/basic/text.e2e.ts-snapshots/text-color-md-ltr-Mobile-Firefox-linux.png b/core/src/components/text/test/basic/text.e2e.ts-snapshots/text-color-md-ltr-Mobile-Firefox-linux.png index f3f11dd72c6..33ec09299e0 100644 Binary files a/core/src/components/text/test/basic/text.e2e.ts-snapshots/text-color-md-ltr-Mobile-Firefox-linux.png and b/core/src/components/text/test/basic/text.e2e.ts-snapshots/text-color-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/text/test/basic/text.e2e.ts-snapshots/text-color-md-ltr-Mobile-Safari-linux.png b/core/src/components/text/test/basic/text.e2e.ts-snapshots/text-color-md-ltr-Mobile-Safari-linux.png index 484124fc7c1..f73830aa2b5 100644 Binary files a/core/src/components/text/test/basic/text.e2e.ts-snapshots/text-color-md-ltr-Mobile-Safari-linux.png and b/core/src/components/text/test/basic/text.e2e.ts-snapshots/text-color-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/text/test/hue/index.html b/core/src/components/text/test/hue/index.html new file mode 100644 index 00000000000..d430806d7f3 --- /dev/null +++ b/core/src/components/text/test/hue/index.html @@ -0,0 +1,61 @@ + + + + + Text - Hue + + + + + + + + + + + + + + + Text - Hue + + + + +

Text Hue: Bold

+ + The quick brown fox jumps over the lazy dog + The quick brown fox jumps over the lazy dog + The quick brown fox jumps over the lazy dog + The quick brown fox jumps over the lazy dog + The quick brown fox jumps over the lazy dog + The quick brown fox jumps over the lazy dog + The quick brown fox jumps over the lazy dog + The quick brown fox jumps over the lazy dog + The quick brown fox jumps over the lazy dog + The quick brown fox jumps over the lazy dog + +

Text Hue: Subtle

+ + The quick brown fox jumps over the lazy dog + The quick brown fox jumps over the lazy dog + The quick brown fox jumps over the lazy dog + The quick brown fox jumps over the lazy dog + The quick brown fox jumps over the lazy dog + The quick brown fox jumps over the lazy dog + The quick brown fox jumps over the lazy dog + The quick brown fox jumps over the lazy dog + The quick brown fox jumps over the lazy dog + The quick brown fox jumps over the lazy dog +
+
+ + diff --git a/core/src/components/text/text.interfaces.ts b/core/src/components/text/text.interfaces.ts new file mode 100644 index 00000000000..30e643c57b1 --- /dev/null +++ b/core/src/components/text/text.interfaces.ts @@ -0,0 +1,18 @@ +export type IonTextRecipe = { + hue?: { + [K in IonTextHue]?: { + /** Any of the semantic colors like primary, secondary, etc. */ + semantic?: { + default?: { + color?: string; + }; + }; + }; + }; +}; + +export type IonTextConfig = { + hue?: IonTextHue; +}; + +export type IonTextHue = 'bold' | 'subtle'; diff --git a/core/src/components/text/text.scss b/core/src/components/text/text.scss index d86de15d200..f1f0be8fbd7 100644 --- a/core/src/components/text/text.scss +++ b/core/src/components/text/text.scss @@ -1,8 +1,19 @@ -@import "../../themes/native/native.globals"; - -// Text +// Text: Common Styles // -------------------------------------------------- -:host(.ion-color) { - color: current-color(base); +:host { + /** + * @prop --ion-text-hue-bold-semantic-default-color: Color of the `bold` hue when a semantic color is applied + * @prop --ion-text-hue-subtle-semantic-default-color: Color of the `subtle` hue when a semantic color is applied + */ + + color: inherit; +} + +:host(.text-hue-bold.ion-color) { + color: var(--ion-text-hue-bold-semantic-default-color); +} + +:host(.text-hue-subtle.ion-color) { + color: var(--ion-text-hue-subtle-semantic-default-color); } diff --git a/core/src/components/text/text.tsx b/core/src/components/text/text.tsx index c3fab899fd8..6cc8a5afdb1 100644 --- a/core/src/components/text/text.tsx +++ b/core/src/components/text/text.tsx @@ -2,12 +2,13 @@ import type { ComponentInterface } from '@stencil/core'; import { Component, Host, Prop, h } from '@stencil/core'; import { createColorClasses } from '@utils/theme'; -import { getIonTheme } from '../../global/ionic-global'; +import { config } from '../../global/config'; import type { Color } from '../../interface'; +import type { IonTextHue } from './text.interfaces'; + /** * @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-text', @@ -22,12 +23,30 @@ export class Text implements ComponentInterface { */ @Prop({ reflect: true }) color?: Color; + /** + * Set to `"bold"` for a text with vibrant, bold colors or to `"subtle"` for + * a text with muted, subtle colors. + * + * Defaults to `"bold"` if both the hue property and theme config are unset. + */ + @Prop() hue?: IonTextHue; + + /** + * Gets the text hue. Uses the `hue` property if set, otherwise + * checks the theme config and falls back to 'bold' if neither is provided. + */ + get hueValue(): IonTextHue { + const hueConfig = config.getObjectValue('IonText.hue', 'bold') as IonTextHue; + + return this.hue || hueConfig; + } + render() { - const theme = getIonTheme(this); + const { hueValue } = this; return ( diff --git a/core/src/themes/ionic/default.tokens.ts b/core/src/themes/ionic/default.tokens.ts index 04aa66835c7..948c2b92882 100644 --- a/core/src/themes/ionic/default.tokens.ts +++ b/core/src/themes/ionic/default.tokens.ts @@ -41,6 +41,10 @@ export const defaultTheme: DefaultTheme = { IonSpinner: { size: 'xsmall', }, + + IonText: { + hue: 'bold', + }, }, }, @@ -830,5 +834,25 @@ export const defaultTheme: DefaultTheme = { }, }, }, + + IonText: { + hue: { + bold: { + semantic: { + default: { + color: currentColor('foreground'), + }, + }, + }, + + subtle: { + semantic: { + default: { + color: currentColor('foreground', { subtle: true }), + }, + }, + }, + }, + }, }, }; diff --git a/core/src/themes/ios/default.tokens.ts b/core/src/themes/ios/default.tokens.ts index 26d8569a84d..a3883b0654c 100644 --- a/core/src/themes/ios/default.tokens.ts +++ b/core/src/themes/ios/default.tokens.ts @@ -43,6 +43,10 @@ export const defaultTheme: DefaultTheme = { IonSpinner: { size: 'medium', }, + + IonText: { + hue: 'bold', + }, }, }, @@ -858,5 +862,25 @@ export const defaultTheme: DefaultTheme = { }, }, }, + + IonText: { + hue: { + bold: { + semantic: { + default: { + color: currentColor('foreground'), + }, + }, + }, + + subtle: { + semantic: { + default: { + color: currentColor('foreground', { subtle: true }), + }, + }, + }, + }, + }, }, }; diff --git a/core/src/themes/md/default.tokens.ts b/core/src/themes/md/default.tokens.ts index 0fa301c8b2a..0b185db44da 100644 --- a/core/src/themes/md/default.tokens.ts +++ b/core/src/themes/md/default.tokens.ts @@ -46,6 +46,10 @@ export const defaultTheme: DefaultTheme = { IonSpinner: { size: 'medium', }, + + IonText: { + hue: 'bold', + }, }, }, @@ -983,5 +987,25 @@ export const defaultTheme: DefaultTheme = { }, }, }, + + IonText: { + hue: { + bold: { + semantic: { + default: { + color: currentColor('foreground'), + }, + }, + }, + + subtle: { + semantic: { + default: { + color: currentColor('foreground', { subtle: true }), + }, + }, + }, + }, + }, }, }; diff --git a/core/src/themes/themes.interfaces.ts b/core/src/themes/themes.interfaces.ts index 0a87addcbff..5804badc75d 100644 --- a/core/src/themes/themes.interfaces.ts +++ b/core/src/themes/themes.interfaces.ts @@ -4,6 +4,7 @@ import type { IonContentRecipe } from '../components/content/content.interfaces' import type { IonItemDividerRecipe } from '../components/item-divider/item-divider.interfaces'; import type { IonProgressBarConfig, IonProgressBarRecipe } from '../components/progress-bar/progress-bar.interfaces'; import type { IonSpinnerConfig, IonSpinnerRecipe } from '../components/spinner/spinner.interfaces'; +import type { IonTextConfig, IonTextRecipe } from '../components/text/text.interfaces'; import type { IonicConfig as IonicGlobalConfig } from '../utils/config'; // Platform-specific theme @@ -249,6 +250,7 @@ export type IonicConfig = IonicGlobalConfig & { IonChip?: IonChipConfig; IonProgressBar?: IonProgressBarConfig; IonSpinner?: IonSpinnerConfig; + IonText?: IonTextConfig; }; }; @@ -292,6 +294,7 @@ type Components = { IonItemDivider?: IonItemDividerRecipe; IonProgressBar?: IonProgressBarRecipe; IonSpinner?: IonSpinnerRecipe; + IonText?: IonTextRecipe; IonCard?: any; IonItem?: any; diff --git a/packages/angular/src/directives/proxies.ts b/packages/angular/src/directives/proxies.ts index 1dd5491da67..880c28a1d30 100644 --- a/packages/angular/src/directives/proxies.ts +++ b/packages/angular/src/directives/proxies.ts @@ -2414,14 +2414,14 @@ export declare interface IonTabButton extends Components.IonTabButton {} @ProxyCmp({ - inputs: ['color', 'mode', 'theme'] + inputs: ['color', 'hue', 'mode'] }) @Component({ selector: 'ion-text', changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['color', 'mode', 'theme'], + inputs: ['color', 'hue', 'mode'], }) export class IonText { protected el: HTMLIonTextElement; diff --git a/packages/angular/standalone/src/directives/proxies.ts b/packages/angular/standalone/src/directives/proxies.ts index d11f99cd10c..8f8f849b868 100644 --- a/packages/angular/standalone/src/directives/proxies.ts +++ b/packages/angular/standalone/src/directives/proxies.ts @@ -2161,14 +2161,14 @@ export declare interface IonTabButton extends Components.IonTabButton {} @ProxyCmp({ defineCustomElementFn: defineIonText, - inputs: ['color', 'mode', 'theme'] + inputs: ['color', 'hue', 'mode'] }) @Component({ selector: 'ion-text', changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['color', 'mode', 'theme'], + inputs: ['color', 'hue', 'mode'], standalone: true }) export class IonText { diff --git a/packages/vue/src/proxies.ts b/packages/vue/src/proxies.ts index b2382020f56..64380f598d8 100644 --- a/packages/vue/src/proxies.ts +++ b/packages/vue/src/proxies.ts @@ -1042,7 +1042,8 @@ export const IonTab: StencilVueComponent = /*@__PURE__*/ defineConta export const IonText: StencilVueComponent = /*@__PURE__*/ defineContainer('ion-text', defineIonText, [ - 'color' + 'color', + 'hue' ]);