feat(card): Add shape feature to card and style into Ionic theme (#29292)

Issue number: resolves #

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

- Adds the new shapes to ionic theme card

## Does this introduce a breaking change?

- [ ] Yes
- [X] No

<!--
  If this introduces a breaking change:
1. Describe the impact and migration path for existing applications
below.
  2. Update the BREAKING.md file with the breaking change.
3. Add "BREAKING CHANGE: [...]" to the commit description when merging.
See
https://github.com/ionic-team/ionic-framework/blob/main/.github/CONTRIBUTING.md#footer
for more information.
-->

---------

Co-authored-by: Brandy Carney <brandy@ionic.io>
This commit is contained in:
Marcelino
2024-04-10 18:20:08 +01:00
committed by GitHub
parent e93941ffa3
commit 9e7c9a5934
23 changed files with 266 additions and 5 deletions

View File

@@ -282,6 +282,7 @@ ion-card,prop,mode,"ios" | "md",undefined,false,false
ion-card,prop,rel,string | undefined,undefined,false,false
ion-card,prop,routerAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false
ion-card,prop,routerDirection,"back" | "forward" | "root",'forward',false,false
ion-card,prop,shape,"rectangular" | "round" | undefined,undefined,false,true
ion-card,prop,target,string | undefined,undefined,false,false
ion-card,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-card,prop,type,"button" | "reset" | "submit",'button',false,false

View File

@@ -618,6 +618,10 @@ export namespace Components {
* When using a router, it specifies the transition direction when navigating to another page using `href`.
*/
"routerDirection": RouterDirection;
/**
* Set to `"round"` for a card with more rounded corners, or `"rectangular"` for a card without rounded corners.
*/
"shape"?: 'round' | 'rectangular';
/**
* Specifies where to display the linked URL. Only applies when an `href` is provided. Special keywords: `"_blank"`, `"_self"`, `"_parent"`, `"_top"`.
*/
@@ -5842,6 +5846,10 @@ declare namespace LocalJSX {
* When using a router, it specifies the transition direction when navigating to another page using `href`.
*/
"routerDirection"?: RouterDirection;
/**
* Set to `"round"` for a card with more rounded corners, or `"rectangular"` for a card without rounded corners.
*/
"shape"?: 'round' | 'rectangular';
/**
* Specifies where to display the linked URL. Only applies when an `href` is provided. Special keywords: `"_blank"`, `"_self"`, `"_parent"`, `"_top"`.
*/

View File

@@ -0,0 +1,28 @@
@use "../../foundations/ionic.vars.scss" as tokens;
@use "../../themes/ionic.mixins" as mixins;
// Ionic Card
// --------------------------------------------------
:host {
--background: #{tokens.$ionic-color-neutral-0};
--border-radius: #{tokens.$ionic-border-radius-rounded-small};
@include mixins.padding(tokens.$ionic-space-base);
@include mixins.border-radius(var(--border-radius));
display: block;
border: #{tokens.$ionic-border-size-small} solid #{tokens.$ionic-color-neutral-50};
background: var(--background);
color: var(--color);
}
:host(.card-round) {
--border-radius: #{tokens.$ionic-border-radius-rounded-large};
}
:host(.card-rectangular) {
--border-radius: #{tokens.$ionic-border-radius-square};
}

View File

@@ -20,7 +20,7 @@ import type { RouterDirection } from '../router/utils/interface';
styleUrls: {
ios: 'card.ios.scss',
md: 'card.md.scss',
ionic: 'card.md.scss',
ionic: 'card.ionic.scss',
},
shadow: true,
})
@@ -82,6 +82,11 @@ export class Card implements ComponentInterface, AnchorInterface, ButtonInterfac
*/
@Prop() routerAnimation: AnimationBuilder | undefined;
/**
* Set to `"round"` for a card with more rounded corners, or `"rectangular"` for a card without rounded corners.
*/
@Prop({ reflect: true }) shape?: 'round' | 'rectangular';
/**
* Specifies where to display the linked URL.
* Only applies when an `href` is provided.
@@ -131,12 +136,16 @@ export class Card implements ComponentInterface, AnchorInterface, ButtonInterfac
}
render() {
const { shape } = this;
const theme = getIonTheme(this);
return (
<Host
class={createColorClasses(this.color, {
[theme]: true,
// TODO(FW-6119): remove theme === 'ionic' when support for other themes is added
[`card-${shape}`]: theme === 'ionic' && shape !== undefined,
'card-disabled': this.disabled,
'ion-activatable': this.isClickable(),
})}

View File

@@ -0,0 +1,40 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
/**
* Shape is only available in the Ionic theme.
* TODO(FW-6119): add the `ios` and `md` modes when shape support is added.
*/
configs({ modes: ['ionic-md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
test.describe(title('card: shape'), () => {
test.describe('default', () => {
test('should not have visual regressions', async ({ page }) => {
await page.goto(`/src/components/card/test/shape`, config);
const container = page.locator('#default');
await expect(container).toHaveScreenshot(screenshot(`card-default`));
});
});
test.describe('round', () => {
test('should not have visual regressions', async ({ page }) => {
await page.goto(`/src/components/card/test/shape`, config);
const container = page.locator('#round');
await expect(container).toHaveScreenshot(screenshot(`card-round`));
});
});
test.describe('rectangular', () => {
test('should not have visual regressions', async ({ page }) => {
await page.goto(`/src/components/card/test/shape`, config);
const container = page.locator('#rectangular');
await expect(container).toHaveScreenshot(screenshot(`card-rectangular`));
});
});
});
});

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,85 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Card - Shape</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>
<!-- Styles only for testing purposes to identify the card shape -->
<style>
:root {
--ion-background-color: #dde2ef;
}
#default,
#round,
#rectangular {
padding: 8px;
}
</style>
</head>
<body>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Card - Shape</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding ion-text-center" id="content" no-bounce>
<h1>Default</h1>
<div id="default" class="ion-margin-top">
<ion-card>
<ion-card-header>
<ion-card-subtitle>Card Subtitle</ion-card-subtitle>
<ion-card-title>Card Title</ion-card-title>
</ion-card-header>
<ion-card-content>
Keep close to Nature's heart... and break clear away, once in awhile, and climb a mountain or spend a week
in the woods. Wash your spirit clean.
</ion-card-content>
</ion-card>
</div>
<h1>Round</h1>
<div id="round" class="ion-margin-top">
<ion-card shape="round">
<ion-card-header>
<ion-card-subtitle>Card Subtitle</ion-card-subtitle>
<ion-card-title>Card Title</ion-card-title>
</ion-card-header>
<ion-card-content>
Keep close to Nature's heart... and break clear away, once in awhile, and climb a mountain or spend a week
in the woods. Wash your spirit clean.
</ion-card-content>
</ion-card>
</div>
<h1>Rectangular</h1>
<div id="rectangular" class="ion-margin-top">
<ion-card shape="rectangular">
<ion-card-header>
<ion-card-subtitle>Card Subtitle</ion-card-subtitle>
<ion-card-title>Card Title</ion-card-title>
</ion-card-header>
<ion-card-content>
Keep close to Nature's heart... and break clear away, once in awhile, and climb a mountain or spend a week
in the woods. Wash your spirit clean.
</ion-card-content>
</ion-card>
</div>
</ion-content>
</ion-app>
</body>
</html>

View File

@@ -0,0 +1,78 @@
<!-- TODO: FW-6077 This file can be removed once the additional test cases are added -->
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Card - Theme Ionic</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>
<style>
.ion-margin-top {
margin-top: 32px;
}
</style>
</head>
<body>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Card - Ionic</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding ion-text-center" id="content" no-bounce>
<h4>Preview options</h4>
<p>
<ion-select id="select-shape" justify="space-between" interface="alert" label="Shape" placeholder="">
<ion-select-option value="">default</ion-select-option>
<ion-select-option value="round">Round</ion-select-option>
<ion-select-option value="rectangular">Rectangular</ion-select-option>
</ion-select>
</p>
<div id="screenshot-wrapper" class="ion-margin-top">
<ion-card>
<ion-card-header>
<ion-card-subtitle>Card Subtitle</ion-card-subtitle>
<ion-card-title>Card Title</ion-card-title>
</ion-card-header>
<ion-card-content>
Keep close to Nature's heart... and break clear away, once in awhile, and climb a mountain or spend a week
in the woods. Wash your spirit clean.
</ion-card-content>
</ion-card>
</div>
</ion-content>
</ion-app>
<script>
const listOfCards = Array.from(document.querySelectorAll('ion-card'));
function updateAttr(el, name, value) {
if (value === '' || value === undefined) {
el.removeAttribute(name);
} else {
el.setAttribute(name, value);
}
}
const selectShape = document.getElementById('select-shape');
selectShape.addEventListener('ionChange', (e) => {
for (const el of listOfCards) {
updateAttr(el, 'shape', e.detail.value);
}
});
</script>
</body>
</html>

View File

@@ -43,6 +43,9 @@
"$value": "#303d60"
},
"neutral": {
"0": {
"$value": "#ffffff"
},
"10": {
"$value": "#f5f5f5"
},

View File

@@ -17,6 +17,7 @@
--ionic-color-primary-900: #010408;
--ionic-color-primary: #1068eb;
--ionic-color-secondary: #303d60;
--ionic-color-neutral-0: #ffffff;
--ionic-color-neutral-10: #f5f5f5;
--ionic-color-neutral-50: #e7e7e7;
--ionic-color-neutral-100: #dadada;

View File

@@ -84,6 +84,12 @@
.ionic-background-color-secondary {
background-color: $ionic-color-secondary;
}
.ionic-color-neutral-0 {
color: $ionic-color-neutral-0;
}
.ionic-background-color-neutral-0 {
background-color: $ionic-color-neutral-0;
}
.ionic-color-neutral-10 {
color: $ionic-color-neutral-10;
}

View File

@@ -16,6 +16,7 @@ $ionic-color-primary-800: var(--ionic-color-primary-800, #061935);
$ionic-color-primary-900: var(--ionic-color-primary-900, #010408);
$ionic-color-primary: var(--ionic-color-primary, #1068eb);
$ionic-color-secondary: var(--ionic-color-secondary, #303d60);
$ionic-color-neutral-0: var(--ionic-color-neutral-0, #ffffff);
$ionic-color-neutral-10: var(--ionic-color-neutral-10, #f5f5f5);
$ionic-color-neutral-50: var(--ionic-color-neutral-50, #e7e7e7);
$ionic-color-neutral-100: var(--ionic-color-neutral-100, #dadada);

View File

@@ -399,14 +399,14 @@ export declare interface IonButtons extends Components.IonButtons {}
@ProxyCmp({
inputs: ['button', 'color', 'disabled', 'download', 'href', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'target', 'theme', 'type']
inputs: ['button', 'color', 'disabled', 'download', 'href', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'shape', 'target', 'theme', 'type']
})
@Component({
selector: 'ion-card',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: ['button', 'color', 'disabled', 'download', 'href', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'target', 'theme', 'type'],
inputs: ['button', 'color', 'disabled', 'download', 'href', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'shape', 'target', 'theme', 'type'],
})
export class IonCard {
protected el: HTMLElement;

View File

@@ -493,14 +493,14 @@ export declare interface IonButtons extends Components.IonButtons {}
@ProxyCmp({
defineCustomElementFn: defineIonCard,
inputs: ['button', 'color', 'disabled', 'download', 'href', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'target', 'theme', 'type']
inputs: ['button', 'color', 'disabled', 'download', 'href', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'shape', 'target', 'theme', 'type']
})
@Component({
selector: 'ion-card',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: ['button', 'color', 'disabled', 'download', 'href', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'target', 'theme', 'type'],
inputs: ['button', 'color', 'disabled', 'download', 'href', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'shape', 'target', 'theme', 'type'],
standalone: true
})
export class IonCard {

View File

@@ -184,6 +184,7 @@ export const IonCard = /*@__PURE__*/ defineContainer<JSX.IonCard>('ion-card', de
'rel',
'routerDirection',
'routerAnimation',
'shape',
'target'
]);