Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions BREAKING.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ This is a comprehensive list of the breaking changes introduced in the major ver
- [Radio Group](#version-9x-radio-group)
- [Spinner](#version-9x-spinner)
- [Textarea](#version-9x-textarea)
- [Thumbnail](#version-9x-thumbnail)

<h2 id="version-9x-global-styles">Global Styles</h2>

Expand Down Expand Up @@ -245,3 +246,39 @@ Additionally, the `radio-group-wrapper` div element has been removed, causing sl
Converted `ion-textarea` to use [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM).

If you were targeting the internals of `ion-textarea` in your CSS, you will need to target the `wrapper`, `container`, `label`, `native`, `supporting-text`, `helper-text`, `error-text`, `counter`, or `bottom` [Shadow Parts](https://ionicframework.com/docs/theming/css-shadow-parts) instead, or use the provided CSS Variables.

<h4 id="version-9x-thumbnail">Thumbnail</h4>

The following breaking changes apply to `ion-thumbnail`:

1. `--size` has been split into separate `--ion-thumbnail-width` and `--ion-thumbnail-height` CSS variables.
2. `--border-radius` has been replaced.
3. Theme classes (`ion-thumbnail.md`, `ion-thumbnail.ios`) are no longer supported.

<h5>Removed CSS variables</h5>

`--size` and `--border-radius` have been removed. Use the new token structure for global styles, or the corresponding CSS variable for component-specific overrides:

| Old (8.x) | New token (global) | New CSS variable (component-specific) |
|---|---|---|
| `--size` | `IonThumbnail.width` | `--ion-thumbnail-width` |
| `--size` | `IonThumbnail.height` | `--ion-thumbnail-height` |
| `--border-radius` | `IonThumbnail.border.radius` | `--ion-thumbnail-border-radius` |

> [!NOTE]
> Code that previously set `--size: 48px` on `ion-thumbnail` must now set both `--ion-thumbnail-width: 48px` and `--ion-thumbnail-height: 48px`.

<h5>Slotted inside `ion-item` or `ion-item-divider`</h5>

When `ion-thumbnail` is slotted inside a parent component, the parent owns the sizing.

Use the **parent's** thumbnail tokens instead:

| Context | New token (global) | New CSS variable (component-specific) |
|---|---|---|
| Inside `ion-item` | `IonItem.thumbnail.width` / `IonItem.thumbnail.height` | `--ion-item-thumbnail-width` / `--ion-item-thumbnail-height` |
| Inside `ion-item-divider` | `IonItemDivider.thumbnail.width` / `IonItemDivider.thumbnail.height` | `--ion-item-divider-thumbnail-width` / `--ion-item-divider-thumbnail-height` |

<h5>Theme classes</h5>

Remove any instances that target the theme classes: `ion-thumbnail.md`, `ion-thumbnail.ios`.
6 changes: 3 additions & 3 deletions core/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2806,9 +2806,9 @@ ion-textarea,part,wrapper

ion-thumbnail,shadow
ion-thumbnail,prop,mode,"ios" | "md",undefined,false,false
ion-thumbnail,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-thumbnail,css-prop,--border-radius
ion-thumbnail,css-prop,--size
ion-thumbnail,css-prop,--ion-thumbnail-border-radius
ion-thumbnail,css-prop,--ion-thumbnail-height
ion-thumbnail,css-prop,--ion-thumbnail-width

ion-title,shadow
ion-title,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true
Expand Down
8 changes: 0 additions & 8 deletions core/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4239,10 +4239,6 @@ export namespace Components {
* The mode determines the platform behaviors of the component.
*/
"mode"?: "ios" | "md";
/**
* The theme determines the visual appearance of the component.
*/
"theme"?: "ios" | "md" | "ionic";
}
interface IonTitle {
/**
Expand Down Expand Up @@ -10335,10 +10331,6 @@ declare namespace LocalJSX {
* The mode determines the platform behaviors of the component.
*/
"mode"?: "ios" | "md";
/**
* The theme determines the visual appearance of the component.
*/
"theme"?: "ios" | "md" | "ionic";
}
interface IonTitle {
/**
Expand Down
6 changes: 3 additions & 3 deletions core/src/components/item-divider/item-divider.scss
Original file line number Diff line number Diff line change
Expand Up @@ -308,15 +308,15 @@

// Thumbnail
::slotted(ion-thumbnail) {
// TODO(FW-6862): separate width and height tokens for thumbnails
--size: var(--ion-item-divider-thumbnail-width);

@include mixins.margin(
var(--ion-item-divider-thumbnail-margin-top),
var(--ion-item-divider-thumbnail-margin-end),
var(--ion-item-divider-thumbnail-margin-bottom),
var(--ion-item-divider-thumbnail-margin-start)
);

width: var(--ion-item-divider-thumbnail-width, revert-layer);
height: var(--ion-item-divider-thumbnail-height, revert-layer);
Comment on lines +318 to +319
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If these tokens are not set then default to the size that was set prior like the original --ion-thumbnail-width/--ion-thumbnail-height.

}

::slotted(ion-thumbnail[slot="start"]) {
Expand Down
4 changes: 3 additions & 1 deletion core/src/components/item/item.ios.scss
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,9 @@
// --------------------------------------------------

::slotted(ion-thumbnail) {
--size: #{$item-ios-thumbnail-size};
// TODO(FW-6847): adopt the revert-layer pattern from item-divider.scss
width: var(--ion-item-thumbnail-width, $item-ios-thumbnail-width);
height: var(--ion-item-thumbnail-height, $item-ios-thumbnail-height);
}

// iOS Item Avatar/Thumbnail
Expand Down
7 changes: 5 additions & 2 deletions core/src/components/item/item.ios.vars.scss
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,11 @@ $item-ios-avatar-width: 36px;
/// @prop - Height of the avatar in the item
$item-ios-avatar-height: $item-ios-avatar-width;

/// @prop - Size of the thumbnail in the item
$item-ios-thumbnail-size: 56px;
/// @prop - Width of the thumbnail in the item
$item-ios-thumbnail-width: 56px;

/// @prop - Height of the thumbnail in the item
$item-ios-thumbnail-height: $item-ios-thumbnail-width;

/// @prop - Padding top for the item content
$item-ios-padding-top: 10px;
Expand Down
4 changes: 3 additions & 1 deletion core/src/components/item/item.md.scss
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,9 @@
// --------------------------------------------------

::slotted(ion-thumbnail) {
--size: #{$item-md-thumbnail-size};
// TODO(FW-6847): adopt the revert-layer pattern from item-divider.scss
width: var(--ion-item-thumbnail-width, $item-md-thumbnail-width);
height: var(--ion-item-thumbnail-height, $item-md-thumbnail-height);
}

// Material Design Item Avatar/Thumbnail
Expand Down
7 changes: 5 additions & 2 deletions core/src/components/item/item.md.vars.scss
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@ $item-md-avatar-width: 40px;
/// @prop - Height of the avatar in the item
$item-md-avatar-height: $item-md-avatar-width;

/// @prop - Size of the thumbnail in the item
$item-md-thumbnail-size: 56px;
/// @prop - Width of the thumbnail in the item
$item-md-thumbnail-width: 56px;

/// @prop - Height of the thumbnail in the item
$item-md-thumbnail-height: $item-md-thumbnail-width;

/// @prop - Padding top for the item content
$item-md-padding-top: 10px;
Expand Down
7 changes: 7 additions & 0 deletions core/src/components/thumbnail/test/basic/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@
</ion-thumbnail>
<ion-label>Item Thumbnail</ion-label>
</ion-item>

<ion-item-divider id="ion-item-divider">
<ion-thumbnail slot="start">
<img src="/src/components/thumbnail/test/thumbnail.svg" />
</ion-thumbnail>
<ion-label>Item Divider Thumbnail</ion-label>
</ion-item-divider>
</ion-content>
</ion-app>
</body>
Expand Down
40 changes: 38 additions & 2 deletions core/src/components/thumbnail/test/basic/thumbnail.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';

/**
* ion-thumbnail does not have mode/RTL-specific logic
* This behavior does not vary across modes/directions
*/
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
test.describe(title('thumbnail: rendering'), () => {
Expand All @@ -22,8 +22,15 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, screenshot, c

await page.setContent(
`
<style>
ion-item {
--ion-item-thumbnail-width: 20px;
--ion-item-thumbnail-height: 20px;
}
</style>
Comment on lines +25 to +30
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this set --ion-item-thumbnail-width and --ion-item-thumbnail-height on ion-item instead of ion-thumbnail? The <ion-item-divider> test right below sets the divider variant on ion-item-divider, so the two would line up. It works either way because the property cascades, but the variable name implies it belongs on the parent.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


<ion-item>
<ion-thumbnail style="--size: 20px">
<ion-thumbnail>
<img src="/src/components/thumbnail/test/thumbnail.svg" />
</ion-thumbnail>
</ion-item>
Expand All @@ -34,6 +41,29 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, screenshot, c
const item = page.locator('ion-item');
await expect(item).toHaveScreenshot(screenshot(`thumbnail-ion-item-size-diff`));
});

test('size should be customizable in <ion-item-divider>', async ({ page }) => {
await page.setContent(
`
<style>
ion-item-divider {
--ion-item-divider-thumbnail-width: 20px;
--ion-item-divider-thumbnail-height: 20px;
}
</style>

<ion-item-divider>
<ion-thumbnail>
<img src="/src/components/thumbnail/test/thumbnail.svg" />
</ion-thumbnail>
</ion-item-divider>
`,
config
);

const itemDivider = page.locator('ion-item-divider');
await expect(itemDivider).toHaveScreenshot(screenshot(`thumbnail-ion-item-divider-size`));
});
});
});

Expand All @@ -53,5 +83,11 @@ configs().forEach(({ title, screenshot, config }) => {

await expect(referenceEl).toHaveScreenshot(screenshot(`thumbnail-ion-item-diff`));
});

test('should not have visual regressions when rendering inside of an <ion-item-divider>', async ({ page }) => {
const referenceEl = page.locator('#ion-item-divider');

await expect(referenceEl).toHaveScreenshot(screenshot('thumbnail-ion-item-divider'));
});
});
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions core/src/components/thumbnail/thumbnail.interfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export type IonThumbnailRecipe = {
height?: string;
width?: string;

border?: {
radius?: string;
};
};
18 changes: 8 additions & 10 deletions core/src/components/thumbnail/thumbnail.scss
Original file line number Diff line number Diff line change
@@ -1,27 +1,25 @@
@import "../../themes/native/native.globals";
@use "../../themes/mixins" as mixins;

// Thumbnail
// --------------------------------------------------

:host {
/**
* @prop --border-radius: Border radius of the thumbnail
* @prop --size: Size of the thumbnail
* @prop --ion-thumbnail-width: Width of the thumbnail
* @prop --ion-thumbnail-height: Height of the thumbnail
* @prop --ion-thumbnail-border-radius: Border radius of the thumbnail and the slotted image
*/
--size: 48px; // TODO(FW-6862): separate width and height tokens for thumbnails
--border-radius: 0;

@include border-radius(var(--border-radius));
@include mixins.border-radius(var(--ion-thumbnail-border-radius));

display: block;

width: var(--size, 48px);
height: var(--size, 48px);
width: var(--ion-thumbnail-width);
Comment on lines 15 to +16
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

var(--ion-thumbnail-width) and var(--ion-thumbnail-height) lost the literal fallback that var(--size, 48px) used to give you. I think the token system sets these globally so it's fine in practice, but if a consumer opts out of the default theme or the tokens haven't applied yet, the thumbnail collapses to auto and renders 0x0. Worth keeping a fallback, or is there a guarantee I'm missing?

height: var(--ion-thumbnail-height);
}

::slotted(ion-img),
::slotted(img) {
@include border-radius(var(--border-radius));
@include mixins.border-radius(var(--ion-thumbnail-border-radius));

width: 100%;
height: 100%;
Expand Down
10 changes: 1 addition & 9 deletions core/src/components/thumbnail/thumbnail.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import type { ComponentInterface } from '@stencil/core';
import { Component, Host, h } from '@stencil/core';

import { getIonTheme } from '../../global/ionic-global';

/**
* @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-thumbnail',
Expand All @@ -14,13 +11,8 @@ import { getIonTheme } from '../../global/ionic-global';
})
export class Thumbnail implements ComponentInterface {
render() {
const theme = getIonTheme(this);
return (
<Host
class={{
[theme]: true,
}}
>
<Host>
<slot></slot>
</Host>
);
Expand Down
9 changes: 9 additions & 0 deletions core/src/themes/ionic/default.tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -803,5 +803,14 @@ export const defaultTheme: DefaultTheme = {
},
},
},

IonThumbnail: {
height: 'var(--ion-scaling-xl)',
width: 'var(--ion-scaling-xl)',

border: {
radius: 'var(--ion-radii-xxxxs)',
},
},
},
};
9 changes: 9 additions & 0 deletions core/src/themes/ios/default.tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -831,5 +831,14 @@ export const defaultTheme: DefaultTheme = {
},
},
},

IonThumbnail: {
height: 'var(--ion-scaling-xxxl)',
width: 'var(--ion-scaling-xxxl)',

border: {
radius: 'var(--ion-radii-xxxxs)',
},
},
},
};
9 changes: 9 additions & 0 deletions core/src/themes/md/default.tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -956,5 +956,14 @@ export const defaultTheme: DefaultTheme = {
},
},
},

IonThumbnail: {
height: 'var(--ion-scaling-xxxl)',
width: 'var(--ion-scaling-xxxl)',

border: {
radius: 'var(--ion-radii-xxxxs)',
},
},
},
};
2 changes: 2 additions & 0 deletions core/src/themes/themes.interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { IonChipConfig, IonChipRecipe } from '../components/chip/chip.inter
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 { IonThumbnailRecipe } from '../components/thumbnail/thumbnail.interfaces';
import type { IonicConfig as IonicGlobalConfig } from '../utils/config';

// Platform-specific theme
Expand Down Expand Up @@ -290,6 +291,7 @@ type Components = {
IonItemDivider?: IonItemDividerRecipe;
IonProgressBar?: IonProgressBarRecipe;
IonSpinner?: IonSpinnerRecipe;
IonThumbnail?: IonThumbnailRecipe;

IonCard?: any;
IonItem?: any;
Expand Down
4 changes: 2 additions & 2 deletions packages/angular/src/directives/proxies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2489,14 +2489,14 @@ the user clears the textarea by performing a keydown event.


@ProxyCmp({
inputs: ['mode', 'theme']
inputs: ['mode']
})
@Component({
selector: 'ion-thumbnail',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: ['mode', 'theme'],
inputs: ['mode'],
})
export class IonThumbnail {
protected el: HTMLIonThumbnailElement;
Expand Down
Loading
Loading