diff --git a/BREAKING.md b/BREAKING.md index 6cd1cd3e06a..61719f311ec 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) - [Thumbnail](#version-9x-thumbnail) @@ -290,6 +291,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 2d53d4befa4..9da256cdb9f 100644 --- a/core/api.txt +++ b/core/api.txt @@ -2707,8 +2707,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 a90ea6c6619..9d29fde767d 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -11,11 +11,12 @@ import { ActionSheetButton } from "./components/action-sheet/action-sheet-interf import { OverlayEventDetail } from "./utils/overlays-interface"; import { IonicSafeString } from "./utils/sanitization"; import { AlertButton, AlertInput } from "./components/alert/alert-interface"; -import { IonBadgeHue, IonBadgeShape, IonBadgeSize, IonBadgeVerticalPosition } from "./components/badge/badge.interfaces"; +import { Hue } from "./themes/themes.interfaces"; +import { IonBadgeShape, IonBadgeSize, IonBadgeVerticalPosition } from "./components/badge/badge.interfaces"; import { RouteID, RouterDirection, RouterEventDetail, RouteWrite } from "./components/router/utils/interface"; import { BreadcrumbCollapsedClickEventDetail } from "./components/breadcrumb/breadcrumb-interface"; import { CheckboxChangeEventDetail } from "./components/checkbox/checkbox-interface"; -import { IonChipFill, IonChipHue, IonChipShape, IonChipSize } from "./components/chip/chip.interfaces"; +import { IonChipFill, IonChipShape, IonChipSize } from "./components/chip/chip.interfaces"; import { ScrollBaseDetail, ScrollDetail } from "./components/content/content.interfaces"; import { DatetimeChangeEventDetail, DatetimeHighlight, DatetimeHighlightCallback, DatetimeHourCycle, DatetimePresentation, FormatOptions, TitleSelectedDatesFormatter } from "./components/datetime/datetime-interface"; import { GalleryColumns, GalleryGap } from "./components/gallery/gallery-interface"; @@ -54,11 +55,12 @@ export { ActionSheetButton } from "./components/action-sheet/action-sheet-interf export { OverlayEventDetail } from "./utils/overlays-interface"; export { IonicSafeString } from "./utils/sanitization"; export { AlertButton, AlertInput } from "./components/alert/alert-interface"; -export { IonBadgeHue, IonBadgeShape, IonBadgeSize, IonBadgeVerticalPosition } from "./components/badge/badge.interfaces"; +export { Hue } from "./themes/themes.interfaces"; +export { IonBadgeShape, IonBadgeSize, IonBadgeVerticalPosition } from "./components/badge/badge.interfaces"; export { RouteID, RouterDirection, RouterEventDetail, RouteWrite } from "./components/router/utils/interface"; export { BreadcrumbCollapsedClickEventDetail } from "./components/breadcrumb/breadcrumb-interface"; export { CheckboxChangeEventDetail } from "./components/checkbox/checkbox-interface"; -export { IonChipFill, IonChipHue, IonChipShape, IonChipSize } from "./components/chip/chip.interfaces"; +export { IonChipFill, IonChipShape, IonChipSize } from "./components/chip/chip.interfaces"; export { ScrollBaseDetail, ScrollDetail } from "./components/content/content.interfaces"; export { DatetimeChangeEventDetail, DatetimeHighlight, DatetimeHighlightCallback, DatetimeHourCycle, DatetimePresentation, FormatOptions, TitleSelectedDatesFormatter } from "./components/datetime/datetime-interface"; export { GalleryColumns, GalleryGap } from "./components/gallery/gallery-interface"; @@ -477,7 +479,7 @@ export namespace Components { /** * Set to `"bold"` for a badge with vibrant, bold colors or to `"subtle"` for a badge with muted, subtle colors. Defaults to `"bold"` if both the hue property and theme config are unset. */ - "hue"?: IonBadgeHue; + "hue"?: Hue; /** * The mode determines the platform behaviors of the component. */ @@ -884,7 +886,7 @@ export namespace Components { /** * Set to `"bold"` for a chip with vibrant, bold colors or to `"subtle"` for a chip with muted, subtle colors. Defaults to `"subtle"` if both the hue property and theme config are unset. */ - "hue"?: IonChipHue; + "hue"?: Hue; /** * The mode determines the platform behaviors of the component. */ @@ -4106,13 +4108,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"?: Hue; /** - * 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 { /** @@ -6469,7 +6471,7 @@ declare namespace LocalJSX { /** * Set to `"bold"` for a badge with vibrant, bold colors or to `"subtle"` for a badge with muted, subtle colors. Defaults to `"bold"` if both the hue property and theme config are unset. */ - "hue"?: IonBadgeHue; + "hue"?: Hue; /** * The mode determines the platform behaviors of the component. */ @@ -6911,7 +6913,7 @@ declare namespace LocalJSX { /** * Set to `"bold"` for a chip with vibrant, bold colors or to `"subtle"` for a chip with muted, subtle colors. Defaults to `"subtle"` if both the hue property and theme config are unset. */ - "hue"?: IonChipHue; + "hue"?: Hue; /** * The mode determines the platform behaviors of the component. */ @@ -10218,13 +10220,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"?: Hue; /** - * 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 { /** @@ -10712,7 +10714,7 @@ declare namespace LocalJSX { } interface IonBadgeAttributes { "color": Color; - "hue": IonBadgeHue; + "hue": Hue; "shape": IonBadgeShape; "size": IonBadgeSize; "vertical": IonBadgeVerticalPosition; @@ -10800,7 +10802,7 @@ declare namespace LocalJSX { "outline": boolean; "fill": IonChipFill; "disabled": boolean; - "hue": IonChipHue; + "hue": Hue; "shape": IonChipShape; "size": IonChipSize; } @@ -11378,6 +11380,7 @@ declare namespace LocalJSX { } interface IonTextAttributes { "color": Color; + "hue": Hue; } interface IonTextareaAttributes { "color": Color; diff --git a/core/src/components/badge/badge.interfaces.ts b/core/src/components/badge/badge.interfaces.ts index 0c3d1b982d8..7e8faeacbe5 100644 --- a/core/src/components/badge/badge.interfaces.ts +++ b/core/src/components/badge/badge.interfaces.ts @@ -1,4 +1,4 @@ -import type { IonPadding } from '../../themes/themes.interfaces'; +import type { IonPadding, Hue } from '../../themes/themes.interfaces'; export type IonBadgeRecipe = { font?: { @@ -7,7 +7,7 @@ export type IonBadgeRecipe = { // Hues hue?: { - [K in IonBadgeHue]?: IonBadgeStateDefinition & { + [K in Hue]?: IonBadgeStateDefinition & { semantic?: IonBadgeStateDefinition; }; }; @@ -80,13 +80,11 @@ type IonBadgeSizeDotDefinition = IonBadgeSizeDefinition & { }; export type IonBadgeConfig = { - hue?: IonBadgeHue; + hue?: Hue; size?: IonBadgeSize; shape?: IonBadgeShape; }; -export const ION_BADGE_HUES = ['bold', 'subtle'] as const; -export type IonBadgeHue = (typeof ION_BADGE_HUES)[number]; export const ION_BADGE_SHAPES = ['crisp', 'soft', 'round', 'rectangular'] as const; export type IonBadgeShape = (typeof ION_BADGE_SHAPES)[number]; export const ION_BADGE_SIZES = ['small', 'medium', 'large'] as const; diff --git a/core/src/components/badge/badge.tsx b/core/src/components/badge/badge.tsx index f1025803254..f534030ac3c 100644 --- a/core/src/components/badge/badge.tsx +++ b/core/src/components/badge/badge.tsx @@ -4,8 +4,9 @@ import { createColorClasses } from '@utils/theme'; import { config } from '../../global/config'; import type { Color } from '../../interface'; +import type { Hue } from '../../themes/themes.interfaces'; -import type { IonBadgeHue, IonBadgeShape, IonBadgeSize, IonBadgeVerticalPosition } from './badge.interfaces'; +import type { IonBadgeShape, IonBadgeSize, IonBadgeVerticalPosition } from './badge.interfaces'; /** * @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of the component. @@ -31,7 +32,7 @@ export class Badge implements ComponentInterface { * * Defaults to `"bold"` if both the hue property and theme config are unset. */ - @Prop() hue?: IonBadgeHue; + @Prop() hue?: Hue; /** * Set to `"crisp"` for a badge with even slightly rounded corners, @@ -84,8 +85,8 @@ export class Badge implements ComponentInterface { * Gets the badge hue. Uses the `hue` property if set, otherwise * checks the theme config and falls back to 'bold' if neither is provided. */ - get hueValue(): IonBadgeHue { - const hueConfig = config.getObjectValue('IonBadge.hue', 'bold') as IonBadgeHue; + get hueValue(): Hue { + const hueConfig = config.getObjectValue('IonBadge.hue', 'bold') as Hue; const hue = this.hue || hueConfig; return hue; diff --git a/core/src/components/badge/test/hue/badge.e2e.ts b/core/src/components/badge/test/hue/badge.e2e.ts index 077b1f00512..ac76109f59f 100644 --- a/core/src/components/badge/test/hue/badge.e2e.ts +++ b/core/src/components/badge/test/hue/badge.e2e.ts @@ -1,7 +1,7 @@ import { expect } from '@playwright/test'; import { configs, test } from '@utils/test/playwright'; -import { ION_BADGE_HUES } from '../../../badge/badge.interfaces'; +import { HUES } from '../../../../themes/themes.interfaces'; /** * This behavior does not vary across modes/directions. @@ -10,7 +10,7 @@ import { ION_BADGE_HUES } from '../../../badge/badge.interfaces'; */ configs({ directions: ['ltr'], modes: ['md', 'ionic-md'] }).forEach(({ config, screenshot, title }) => { test.describe(title('badge: hue'), () => { - ION_BADGE_HUES.forEach((hue) => { + HUES.forEach((hue) => { test(`should render ${hue} badges`, async ({ page }) => { await page.setContent( ` diff --git a/core/src/components/chip/chip.interfaces.ts b/core/src/components/chip/chip.interfaces.ts index 341a56a7faf..c66a56a1a3c 100644 --- a/core/src/components/chip/chip.interfaces.ts +++ b/core/src/components/chip/chip.interfaces.ts @@ -1,4 +1,4 @@ -import type { IonPadding, IonMargin } from '../../themes/themes.interfaces'; +import type { IonPadding, IonMargin, Hue } from '../../themes/themes.interfaces'; export type IonChipRecipe = { letterSpacing?: string | number; @@ -13,7 +13,7 @@ export type IonChipRecipe = { // Hues with fills hue?: { - [K in IonChipHue]?: IonChipFillDefinition; + [K in Hue]?: IonChipFillDefinition; }; // Sizes @@ -111,12 +111,11 @@ type IonChipAvatarDefinition = IonChipMediaDefinition & { export type IonChipConfig = { fill?: IonChipFill; - hue?: IonChipHue; + hue?: Hue; size?: IonChipSize; shape?: IonChipShape; }; export type IonChipFill = 'outline' | 'solid'; -export type IonChipHue = 'bold' | 'subtle'; export type IonChipSize = 'small' | 'large'; export type IonChipShape = 'soft' | 'round' | 'rectangular'; diff --git a/core/src/components/chip/chip.tsx b/core/src/components/chip/chip.tsx index 5b06b7adc02..efc5f5266f3 100644 --- a/core/src/components/chip/chip.tsx +++ b/core/src/components/chip/chip.tsx @@ -5,8 +5,9 @@ import { createColorClasses } from '@utils/theme'; import { config } from '../../global/config'; import type { Color } from '../../interface'; +import type { Hue } from '../../themes/themes.interfaces'; -import type { IonChipFill, IonChipHue, IonChipSize, IonChipShape } from './chip.interfaces'; +import type { IonChipFill, IonChipSize, IonChipShape } from './chip.interfaces'; /** * @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of the component. @@ -54,7 +55,7 @@ export class Chip implements ComponentInterface { * * Defaults to `"subtle"` if both the hue property and theme config are unset. */ - @Prop() hue?: IonChipHue; + @Prop() hue?: Hue; /** * Set to `"soft"` for a chip with slightly rounded corners, @@ -98,8 +99,8 @@ export class Chip implements ComponentInterface { * Gets the chip hue. Uses the `hue` property if set, otherwise * checks the theme config. Defaults to `subtle` if neither is set. */ - get hueValue(): IonChipHue { - const hueConfig = config.getObjectValue('IonChip.hue', 'subtle') as IonChipHue; + get hueValue(): Hue { + const hueConfig = config.getObjectValue('IonChip.hue', 'subtle') as Hue; const hue = this.hue || hueConfig; return hue; diff --git a/core/src/components/text/test/basic/text.e2e.ts b/core/src/components/text/test/basic/text.e2e.ts index a98baa015d1..81fce3d4b8a 100644 --- a/core/src/components/text/test/basic/text.e2e.ts +++ b/core/src/components/text/test/basic/text.e2e.ts @@ -19,18 +19,5 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co const text = page.locator('ion-text'); await expect(text.nth(0)).toHaveScreenshot(screenshot(`text`)); }); - test('should render text with color prop', async ({ page }) => { - await page.setContent( - ` - - The quick brown fox jumps over the lazy dog - - `, - config - ); - - const text = page.locator('ion-text'); - await expect(text.nth(0)).toHaveScreenshot(screenshot(`text-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 deleted file mode 100644 index f5fe40421a6..00000000000 Binary files a/core/src/components/text/test/basic/text.e2e.ts-snapshots/text-color-md-ltr-Mobile-Chrome-linux.png and /dev/null 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 deleted file mode 100644 index f3f11dd72c6..00000000000 Binary files a/core/src/components/text/test/basic/text.e2e.ts-snapshots/text-color-md-ltr-Mobile-Firefox-linux.png and /dev/null 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 deleted file mode 100644 index 6f05eece866..00000000000 Binary files a/core/src/components/text/test/basic/text.e2e.ts-snapshots/text-color-md-ltr-Mobile-Safari-linux.png and /dev/null 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..42bb9133fc4 --- /dev/null +++ b/core/src/components/text/test/hue/index.html @@ -0,0 +1,65 @@ + + + + + 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/test/hue/text.e2e.ts b/core/src/components/text/test/hue/text.e2e.ts new file mode 100644 index 00000000000..415c2f3f919 --- /dev/null +++ b/core/src/components/text/test/hue/text.e2e.ts @@ -0,0 +1,22 @@ +import { expect } from '@playwright/test'; +import { configs, test } from '@utils/test/playwright'; + +import { HUES } from '../../../../themes/themes.interfaces'; + +/** + * This behavior does not vary across modes/directions. + */ +configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => { + test.describe(title('text: basic'), () => { + test.beforeEach(async ({ page }) => { + await page.goto(`/src/components/text/test/hue`, config); + }); + + HUES.forEach((hue) => { + test(`should render ${hue} text`, async ({ page }) => { + const text = page.locator(`#${hue}`); + await expect(text).toHaveScreenshot(screenshot(`text-hue-${hue}`)); + }); + }); + }); +}); diff --git a/core/src/components/text/test/hue/text.e2e.ts-snapshots/text-hue-bold-md-ltr-Mobile-Chrome-linux.png b/core/src/components/text/test/hue/text.e2e.ts-snapshots/text-hue-bold-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..896a649edcd Binary files /dev/null and b/core/src/components/text/test/hue/text.e2e.ts-snapshots/text-hue-bold-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/text/test/hue/text.e2e.ts-snapshots/text-hue-bold-md-ltr-Mobile-Firefox-linux.png b/core/src/components/text/test/hue/text.e2e.ts-snapshots/text-hue-bold-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..35de690be75 Binary files /dev/null and b/core/src/components/text/test/hue/text.e2e.ts-snapshots/text-hue-bold-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/text/test/hue/text.e2e.ts-snapshots/text-hue-bold-md-ltr-Mobile-Safari-linux.png b/core/src/components/text/test/hue/text.e2e.ts-snapshots/text-hue-bold-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..551e96f9dfb Binary files /dev/null and b/core/src/components/text/test/hue/text.e2e.ts-snapshots/text-hue-bold-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/text/test/hue/text.e2e.ts-snapshots/text-hue-subtle-md-ltr-Mobile-Chrome-linux.png b/core/src/components/text/test/hue/text.e2e.ts-snapshots/text-hue-subtle-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..896a649edcd Binary files /dev/null and b/core/src/components/text/test/hue/text.e2e.ts-snapshots/text-hue-subtle-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/text/test/hue/text.e2e.ts-snapshots/text-hue-subtle-md-ltr-Mobile-Firefox-linux.png b/core/src/components/text/test/hue/text.e2e.ts-snapshots/text-hue-subtle-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..35de690be75 Binary files /dev/null and b/core/src/components/text/test/hue/text.e2e.ts-snapshots/text-hue-subtle-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/text/test/hue/text.e2e.ts-snapshots/text-hue-subtle-md-ltr-Mobile-Safari-linux.png b/core/src/components/text/test/hue/text.e2e.ts-snapshots/text-hue-subtle-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..551e96f9dfb Binary files /dev/null and b/core/src/components/text/test/hue/text.e2e.ts-snapshots/text-hue-subtle-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/text/text.interfaces.ts b/core/src/components/text/text.interfaces.ts new file mode 100644 index 00000000000..eeef7f69f4f --- /dev/null +++ b/core/src/components/text/text.interfaces.ts @@ -0,0 +1,20 @@ +import type { Hue } from '../../themes/themes.interfaces'; + +export type IonTextRecipe = { + hue?: { + [K in Hue]?: { + /** Any of the semantic colors like primary, secondary, etc. */ + semantic?: { + default?: { + color?: string; + }; + }; + }; + }; +}; + +export type IonTextConfig = { + hue?: Hue; +}; + +export type IonTextHue = Hue; 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..d4b40825098 100644 --- a/core/src/components/text/text.tsx +++ b/core/src/components/text/text.tsx @@ -2,12 +2,12 @@ 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 { Hue } from '../../themes/themes.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 +22,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?: Hue; + + /** + * 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(): Hue { + const hueConfig = config.getObjectValue('IonText.hue', 'bold') as Hue; + + return this.hue || hueConfig; + } + render() { - const theme = getIonTheme(this); + const { hueValue } = this; return ( diff --git a/core/src/themes/base/dark.tokens.ts b/core/src/themes/base/dark.tokens.ts index 1b611610c66..643545da890 100644 --- a/core/src/themes/base/dark.tokens.ts +++ b/core/src/themes/base/dark.tokens.ts @@ -13,6 +13,7 @@ const colors = { dark: '#f4f5f8', }; +// TODO(FW-7558): Finalize color palette and update these color values as needed export const darkTheme: DarkTheme = { enabled: 'never', color: { diff --git a/core/src/themes/base/light.tokens.ts b/core/src/themes/base/light.tokens.ts index f584b3279c7..0ff2482ed40 100644 --- a/core/src/themes/base/light.tokens.ts +++ b/core/src/themes/base/light.tokens.ts @@ -13,6 +13,7 @@ const colors = { dark: '#222428', }; +// TODO(FW-7558): Finalize color palette and update these color values as needed export const lightTheme: LightTheme = { color: { primary: { diff --git a/core/src/themes/ionic/default.tokens.ts b/core/src/themes/ionic/default.tokens.ts index e6fd11c2815..f9a6bdf0a02 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', + }, }, }, @@ -831,6 +835,26 @@ export const defaultTheme: DefaultTheme = { }, }, + IonText: { + hue: { + bold: { + semantic: { + default: { + color: currentColor('foreground'), + }, + }, + }, + + subtle: { + semantic: { + default: { + color: currentColor('foreground', { subtle: true }), + }, + }, + }, + }, + }, + IonThumbnail: { height: 'var(--ion-scaling-xl)', width: 'var(--ion-scaling-xl)', diff --git a/core/src/themes/ionic/test/colors/theme.e2e.ts b/core/src/themes/ionic/test/colors/theme.e2e.ts index 2adccbad0c0..fb5fa71ad85 100644 --- a/core/src/themes/ionic/test/colors/theme.e2e.ts +++ b/core/src/themes/ionic/test/colors/theme.e2e.ts @@ -66,7 +66,7 @@ const styleTestHelpers = ` configs({ modes: ['ionic-md'], directions: ['ltr'], palettes: ['light', 'dark'] }).forEach(({ config, title }) => { const colors = ['primary', 'secondary', 'tertiary', 'success', 'warning', 'danger', 'light', 'medium', 'dark']; - // TODO: Re-enable this test once the colors have been finalized + // TODO(FW-7558): Re-enable this test once the colors have been finalized test.describe.skip(title('palette colors: bold'), () => { test.beforeEach(({ skip }) => { skip.browser('firefox', 'Color contrast ratio is consistent across browsers'); @@ -134,7 +134,7 @@ configs({ modes: ['ionic-md'], directions: ['ltr'], palettes: ['light', 'dark'] } }); - // TODO: Re-enable this test once the colors have been finalized + // TODO(FW-7558): Re-enable this test once the colors have been finalized test.describe.skip(title('palette colors: subtle'), () => { test.beforeEach(({ skip }) => { skip.browser('firefox', 'Color contrast ratio is consistent across browsers'); diff --git a/core/src/themes/ios/default.tokens.ts b/core/src/themes/ios/default.tokens.ts index dcebf89e929..673459d44d3 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', + }, }, }, @@ -859,6 +863,26 @@ export const defaultTheme: DefaultTheme = { }, }, + IonText: { + hue: { + bold: { + semantic: { + default: { + color: currentColor('foreground'), + }, + }, + }, + + subtle: { + semantic: { + default: { + color: currentColor('foreground', { subtle: true }), + }, + }, + }, + }, + }, + IonThumbnail: { height: 'var(--ion-scaling-xxxl)', width: 'var(--ion-scaling-xxxl)', diff --git a/core/src/themes/md/default.tokens.ts b/core/src/themes/md/default.tokens.ts index 84a617f796f..f9411dc0429 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', + }, }, }, @@ -984,6 +988,26 @@ export const defaultTheme: DefaultTheme = { }, }, + IonText: { + hue: { + bold: { + semantic: { + default: { + color: currentColor('foreground'), + }, + }, + }, + + subtle: { + semantic: { + default: { + color: currentColor('foreground', { subtle: true }), + }, + }, + }, + }, + }, + IonThumbnail: { height: 'var(--ion-scaling-xxxl)', width: 'var(--ion-scaling-xxxl)', diff --git a/core/src/themes/themes.interfaces.ts b/core/src/themes/themes.interfaces.ts index dec74cfc901..65c291ad847 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 { IonThumbnailRecipe } from '../components/thumbnail/thumbnail.interfaces'; import type { IonicConfig as IonicGlobalConfig } from '../utils/config'; @@ -250,6 +251,7 @@ export type IonicConfig = IonicGlobalConfig & { IonChip?: IonChipConfig; IonProgressBar?: IonProgressBarConfig; IonSpinner?: IonSpinnerConfig; + IonText?: IonTextConfig; }; }; @@ -293,6 +295,7 @@ type Components = { IonItemDivider?: IonItemDividerRecipe; IonProgressBar?: IonProgressBarRecipe; IonSpinner?: IonSpinnerRecipe; + IonText?: IonTextRecipe; IonThumbnail?: IonThumbnailRecipe; IonCard?: any; @@ -320,3 +323,6 @@ export type NumberStringKeys = { // Enforce keys are strings of numbers (like 50, '50', etc.) [K in number as `${K}`]?: string; }; + +export const HUES = ['bold', 'subtle'] as const; +export type Hue = (typeof HUES)[number]; diff --git a/packages/angular/src/directives/proxies.ts b/packages/angular/src/directives/proxies.ts index f53df1d8c8d..6bb4b88de6c 100644 --- a/packages/angular/src/directives/proxies.ts +++ b/packages/angular/src/directives/proxies.ts @@ -2436,14 +2436,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 daf4149e3b6..e6b702f7e9a 100644 --- a/packages/angular/standalone/src/directives/proxies.ts +++ b/packages/angular/standalone/src/directives/proxies.ts @@ -2186,14 +2186,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 55abafc7b73..f97c1f7b193 100644 --- a/packages/vue/src/proxies.ts +++ b/packages/vue/src/proxies.ts @@ -1051,7 +1051,8 @@ export const IonTab: StencilVueComponent = /*@__PURE__*/ defineConta export const IonText: StencilVueComponent = /*@__PURE__*/ defineContainer('ion-text', defineIonText, [ - 'color' + 'color', + 'hue' ]);