fix(react/vue): properly switch ionicon based on the mode when ios/md is set (#26924)

closes #26207
This commit is contained in:
Brandy Carney
2023-03-14 15:03:28 -04:00
committed by GitHub
parent af79673246
commit 1eb9a085b2
16 changed files with 383 additions and 14 deletions

View File

@ -87,6 +87,30 @@
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item>
<ion-icon slot="start" ios="star-outline" md="star"></ion-icon>
<ion-label>
<p>ios: star outline</p>
<p>md: star</p>
</ion-label>
</ion-item>
<ion-item>
<ion-icon slot="start" mode="ios" ios="star-outline" md="star"></ion-icon>
<ion-label>
<h3>mode: ios</h3>
<p>ios: star outline</p>
<p>md: star</p>
</ion-label>
</ion-item>
<ion-item>
<ion-icon slot="start" mode="md" ios="star-outline" md="star"></ion-icon>
<ion-label>
<h3>mode: md</h3>
<p>ios: star outline</p>
<p>md: star</p>
</ion-label>
</ion-item>
<ion-item detail="true"> <ion-item detail="true">
<ion-label> <ion-label>
<code> ion-item w/ [detail="true"] attr. text text text text text text </code> <code> ion-item w/ [detail="true"] attr. text text text text text text </code>

View File

@ -4,7 +4,7 @@ import { NavContext } from '../contexts/NavContext';
import type { IonicReactProps } from './IonicReactProps'; import type { IonicReactProps } from './IonicReactProps';
import { IonIconInner } from './inner-proxies'; import { IonIconInner } from './inner-proxies';
import { createForwardRef, isPlatform } from './utils'; import { createForwardRef, getConfig } from './utils';
interface IonIconProps { interface IonIconProps {
color?: string; color?: string;
@ -34,12 +34,15 @@ class IonIconContainer extends React.PureComponent<InternalProps> {
} }
render() { render() {
const { icon, ios, md, ...rest } = this.props; const { icon, ios, md, mode, ...rest } = this.props;
let iconToUse: typeof icon; let iconToUse: typeof icon;
const config = getConfig();
const iconMode = mode || config?.get('mode');
if (ios || md) { if (ios || md) {
if (isPlatform('ios')) { if (iconMode === 'ios') {
iconToUse = ios ?? md ?? icon; iconToUse = ios ?? md ?? icon;
} else { } else {
iconToUse = md ?? ios ?? icon; iconToUse = md ?? ios ?? icon;

View File

@ -0,0 +1,47 @@
# React Test App
## Getting Started
### Setup
Make sure you are using the latest versions of node and npm. If you do not have these, [download the installer](https://nodejs.org/) for the LTS version of Node.js. This is the best way to also [install npm](https://blog.npmjs.org/post/85484771375/how-to-install-npm#_=_).
### Building Dependencies
Navigate to the `core`, `packages/react` and `packages/react-router` directories and build each of them:
```bash
npm i
npm run build
```
Then, install dependencies from this directory for this test app:
```
npm i
```
### Syncing Changes
When making changes to the React package, run the following from this directory to sync the changes:
```bash
npm run sync
```
### Previewing App
To preview this app locally, run the following from this directory:
```bash
npm start
```
### Running Tests
To run the e2e tests, run the following from this directory:
```
npm run build
npm run e2e
```

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon md android" viewBox="0 0 512 512"><title>Logo Android</title><path d="M380.91 199l42.47-73.57a8.63 8.63 0 00-3.12-11.76 8.52 8.52 0 00-11.71 3.12l-43 74.52c-32.83-15-69.78-23.35-109.52-23.35s-76.69 8.36-109.52 23.35l-43-74.52a8.6 8.6 0 10-14.88 8.64L131 199C57.8 238.64 8.19 312.77 0 399.55h512c-8.19-86.78-57.8-160.91-131.09-200.55zM138.45 327.65a21.46 21.46 0 1121.46-21.46 21.47 21.47 0 01-21.46 21.46zm235 0A21.46 21.46 0 11395 306.19a21.47 21.47 0 01-21.51 21.46z"/></svg>

After

Width:  |  Height:  |  Size: 533 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon ios apple" viewBox="0 0 512 512"><title>Logo Apple</title><path d="M349.13 136.86c-40.32 0-57.36 19.24-85.44 19.24-28.79 0-50.75-19.1-85.69-19.1-34.2 0-70.67 20.88-93.83 56.45-32.52 50.16-27 144.63 25.67 225.11 18.84 28.81 44 61.12 77 61.47h.6c28.68 0 37.2-18.78 76.67-19h.6c38.88 0 46.68 18.89 75.24 18.89h.6c33-.35 59.51-36.15 78.35-64.85 13.56-20.64 18.6-31 29-54.35-76.19-28.92-88.43-136.93-13.08-178.34-23-28.8-55.32-45.48-85.79-45.48z"/><path d="M340.25 32c-24 1.63-52 16.91-68.4 36.86-14.88 18.08-27.12 44.9-22.32 70.91h1.92c25.56 0 51.72-15.39 67-35.11 14.72-18.77 25.88-45.37 21.8-72.66z"/></svg>

After

Width:  |  Height:  |  Size: 661 B

View File

@ -26,6 +26,7 @@ import OverlayHooks from './pages/overlay-hooks/OverlayHooks';
import OverlayComponents from './pages/overlay-components/OverlayComponents'; import OverlayComponents from './pages/overlay-components/OverlayComponents';
import KeepContentsMounted from './pages/overlay-components/KeepContentsMounted'; import KeepContentsMounted from './pages/overlay-components/KeepContentsMounted';
import Tabs from './pages/Tabs'; import Tabs from './pages/Tabs';
import Icons from './pages/Icons';
import NavComponent from './pages/navigation/NavComponent'; import NavComponent from './pages/navigation/NavComponent';
import IonModalConditionalSibling from './pages/overlay-components/IonModalConditionalSibling'; import IonModalConditionalSibling from './pages/overlay-components/IonModalConditionalSibling';
import IonModalConditional from './pages/overlay-components/IonModalConditional'; import IonModalConditional from './pages/overlay-components/IonModalConditional';
@ -54,6 +55,7 @@ const App: React.FC = () => (
<Route path="/keep-contents-mounted" component={KeepContentsMounted} /> <Route path="/keep-contents-mounted" component={KeepContentsMounted} />
<Route path="/navigation" component={NavComponent} /> <Route path="/navigation" component={NavComponent} />
<Route path="/tabs" component={Tabs} /> <Route path="/tabs" component={Tabs} />
<Route path="/icons" component={Icons} />
</IonRouterOutlet> </IonRouterOutlet>
</IonReactRouter> </IonReactRouter>
</IonApp> </IonApp>

View File

@ -0,0 +1,95 @@
import React, { useState } from 'react';
import { IonBackButton, IonButton, IonButtons, IonContent, IonHeader, IonIcon, IonItem, IonLabel, IonList, IonTitle, IonToolbar } from '@ionic/react';
import { heart, heartCircleOutline, logoApple, logoTwitter, personCircleOutline, star, starOutline, trash } from 'ionicons/icons';
interface IconsProps {}
const Icons: React.FC<IconsProps> = () => {
const [dynamic, setDynamic] = useState(star);
const iosCustomSvg = "../assets/logo-apple.svg";
const mdCustomSvg = "../assets/logo-android.svg";
const toggle = () => {
setDynamic(dynamic => dynamic === star ? starOutline : star);
}
return (
<>
<IonHeader translucent={true}>
<IonToolbar>
<IonButtons>
<IonBackButton></IonBackButton>
</IonButtons>
<IonTitle>Icons</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent fullscreen={true}>
<IonHeader collapse="condense">
<IonToolbar>
<IonTitle size="large">Icons</IonTitle>
</IonToolbar>
</IonHeader>
<IonList>
<IonItem>
<IonIcon slot="start" icon={heart}></IonIcon>
<IonLabel>Static Icons</IonLabel>
<IonIcon slot="end" icon={personCircleOutline} color="dark"></IonIcon>
<IonIcon slot="end" icon={trash} color="danger"></IonIcon>
</IonItem>
<IonItem>
<IonIcon icon={logoApple} slot="start"></IonIcon>
<IonLabel>Logo Icons</IonLabel>
<IonIcon icon={logoTwitter} slot="end"></IonIcon>
</IonItem>
<IonItem>
<IonIcon slot="start" icon={dynamic} color="warning"></IonIcon>
<IonLabel>Dynamic Icon</IonLabel>
<IonButton slot="end" fill="outline" onClick={() => toggle()}>
Toggle Icon
</IonButton>
</IonItem>
<IonItem>
<IonIcon slot="start" ios={heartCircleOutline} md={personCircleOutline}></IonIcon>
<IonLabel>
<p>ios: heart circle</p>
<p>md: person circle</p>
</IonLabel>
</IonItem>
<IonItem>
<IonIcon slot="start" ios={starOutline} md={star}></IonIcon>
<IonLabel>
<p>ios: star outline</p>
<p>md: star</p>
</IonLabel>
</IonItem>
<IonItem>
<IonIcon slot="start" mode="ios" ios={starOutline} md={star}></IonIcon>
<IonLabel>
<h3>mode: ios</h3>
<p>ios: star outline</p>
<p>md: star</p>
</IonLabel>
</IonItem>
<IonItem>
<IonIcon slot="start" mode="md" ios={starOutline} md={star}></IonIcon>
<IonLabel>
<h3>mode: md</h3>
<p>ios: star outline</p>
<p>md: star</p>
</IonLabel>
</IonItem>
<IonItem>
<IonIcon id="customSvg" slot="start" ios={iosCustomSvg} md={mdCustomSvg}></IonIcon>
<IonLabel>
<p>Custom SVG</p>
</IonLabel>
</IonItem>
</IonList>
</IonContent>
</>
);
};
export default Icons;

View File

@ -25,26 +25,21 @@ const Main: React.FC<MainProps> = () => {
<IonItem routerLink="/overlay-hooks"> <IonItem routerLink="/overlay-hooks">
<IonLabel>Overlay Hooks</IonLabel> <IonLabel>Overlay Hooks</IonLabel>
</IonItem> </IonItem>
</IonList>
<IonList>
<IonItem routerLink="/overlay-components"> <IonItem routerLink="/overlay-components">
<IonLabel>Overlay Components</IonLabel> <IonLabel>Overlay Components</IonLabel>
</IonItem> </IonItem>
</IonList>
<IonList>
<IonItem routerLink="/keep-contents-mounted"> <IonItem routerLink="/keep-contents-mounted">
<IonLabel>Keep Contents Mounted Overlay Components</IonLabel> <IonLabel>Keep Contents Mounted Overlay Components</IonLabel>
</IonItem> </IonItem>
</IonList>
<IonList>
<IonItem routerLink="/navigation"> <IonItem routerLink="/navigation">
<IonLabel>Navigation</IonLabel> <IonLabel>Navigation</IonLabel>
</IonItem> </IonItem>
</IonList>
<IonList>
<IonItem routerLink="/tabs"> <IonItem routerLink="/tabs">
<IonLabel>Tabs</IonLabel> <IonLabel>Tabs</IonLabel>
</IonItem> </IonItem>
<IonItem routerLink="/icons">
<IonLabel>Icons</IonLabel>
</IonItem>
</IonList> </IonList>
</IonContent> </IonContent>
</IonPage> </IonPage>

View File

@ -0,0 +1,22 @@
describe('Icons', () => {
it('should use ios svg', () => {
cy.visit('/icons?ionic:mode=ios');
cy.get('#customSvg').shadow().find('svg').should('have.class', 'ios');
cy.get('#customSvg').shadow().find('svg').should('have.class', 'apple');
});
it('should use md svg', () => {
cy.visit('/icons?ionic:mode=md');
cy.get('#customSvg').shadow().find('svg').should('have.class', 'md');
cy.get('#customSvg').shadow().find('svg').should('have.class', 'android');
});
it('should use fallback md svg', () => {
cy.visit('/icons');
cy.get('#customSvg').shadow().find('svg').should('have.class', 'md');
cy.get('#customSvg').shadow().find('svg').should('have.class', 'android');
});
})

View File

@ -1,7 +1,8 @@
import { isPlatform } from "@ionic/core/components";
import { defineCustomElement } from "ionicons/components/ion-icon.js"; import { defineCustomElement } from "ionicons/components/ion-icon.js";
import { h, defineComponent } from "vue"; import { h, defineComponent } from "vue";
import { getConfig } from "../utils";
export const IonIcon = /*@__PURE__*/ defineComponent({ export const IonIcon = /*@__PURE__*/ defineComponent({
name: "IonIcon", name: "IonIcon",
props: { props: {
@ -19,11 +20,15 @@ export const IonIcon = /*@__PURE__*/ defineComponent({
setup(props, { slots }) { setup(props, { slots }) {
defineCustomElement(); defineCustomElement();
return () => { return () => {
const { icon, ios, md } = props; const { icon, ios, md, mode } = props;
let iconToUse: typeof icon; let iconToUse: typeof icon;
const config = getConfig();
const iconMode = mode || config?.get("mode");
if (ios || md) { if (ios || md) {
if (isPlatform("ios")) { if (iconMode === "ios") {
iconToUse = ios ?? md ?? icon; iconToUse = ios ?? md ?? icon;
} else { } else {
iconToUse = md ?? ios ?? icon; iconToUse = md ?? ios ?? icon;

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon md android" viewBox="0 0 512 512"><title>Logo Android</title><path d="M380.91 199l42.47-73.57a8.63 8.63 0 00-3.12-11.76 8.52 8.52 0 00-11.71 3.12l-43 74.52c-32.83-15-69.78-23.35-109.52-23.35s-76.69 8.36-109.52 23.35l-43-74.52a8.6 8.6 0 10-14.88 8.64L131 199C57.8 238.64 8.19 312.77 0 399.55h512c-8.19-86.78-57.8-160.91-131.09-200.55zM138.45 327.65a21.46 21.46 0 1121.46-21.46 21.47 21.47 0 01-21.46 21.46zm235 0A21.46 21.46 0 11395 306.19a21.47 21.47 0 01-21.51 21.46z"/></svg>

After

Width:  |  Height:  |  Size: 533 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon ios apple" viewBox="0 0 512 512"><title>Logo Apple</title><path d="M349.13 136.86c-40.32 0-57.36 19.24-85.44 19.24-28.79 0-50.75-19.1-85.69-19.1-34.2 0-70.67 20.88-93.83 56.45-32.52 50.16-27 144.63 25.67 225.11 18.84 28.81 44 61.12 77 61.47h.6c28.68 0 37.2-18.78 76.67-19h.6c38.88 0 46.68 18.89 75.24 18.89h.6c33-.35 59.51-36.15 78.35-64.85 13.56-20.64 18.6-31 29-54.35-76.19-28.92-88.43-136.93-13.08-178.34-23-28.8-55.32-45.48-85.79-45.48z"/><path d="M340.25 32c-24 1.63-52 16.91-68.4 36.86-14.88 18.08-27.12 44.9-22.32 70.91h1.92c25.56 0 51.72-15.39 67-35.11 14.72-18.77 25.88-45.37 21.8-72.66z"/></svg>

After

Width:  |  Height:  |  Size: 661 B

View File

@ -33,6 +33,10 @@ const routes: Array<RouteRecordRaw> = [
path: '/keep-contents-mounted', path: '/keep-contents-mounted',
component: () => import('@/views/OverlaysKeepContentsMounted.vue') component: () => import('@/views/OverlaysKeepContentsMounted.vue')
}, },
{
path: '/icons',
component: () => import('@/views/Icons.vue')
},
{ {
path: '/inputs', path: '/inputs',
component: () => import('@/views/Inputs.vue') component: () => import('@/views/Inputs.vue')

View File

@ -20,6 +20,9 @@
<ion-item button router-link="/overlays"> <ion-item button router-link="/overlays">
<ion-label>Overlays</ion-label> <ion-label>Overlays</ion-label>
</ion-item> </ion-item>
<ion-item button router-link="/icons">
<ion-label>Icons</ion-label>
</ion-item>
<ion-item button router-link="/inputs"> <ion-item button router-link="/inputs">
<ion-label>Inputs</ion-label> <ion-label>Inputs</ion-label>
</ion-item> </ion-item>

View File

@ -0,0 +1,143 @@
<template>
<ion-page data-pageid="icons">
<ion-header :translucent="true">
<ion-toolbar>
<ion-buttons>
<ion-back-button></ion-back-button>
</ion-buttons>
<ion-title>Icons</ion-title>
</ion-toolbar>
</ion-header>
<ion-content :fullscreen="true">
<ion-header collapse="condense">
<ion-toolbar>
<ion-title size="large">Icons</ion-title>
</ion-toolbar>
</ion-header>
<ion-list>
<ion-item>
<ion-icon slot="start" :icon="heart"></ion-icon>
<ion-label>Static Icons</ion-label>
<ion-icon slot="end" :icon="personCircleOutline" color="dark"></ion-icon>
<ion-icon slot="end" :icon="trash" color="danger"></ion-icon>
</ion-item>
<ion-item>
<ion-icon :icon="logoApple" slot="start"></ion-icon>
<ion-label>Logo Icons</ion-label>
<ion-icon :icon="logoTwitter" slot="end"></ion-icon>
</ion-item>
<ion-item>
<ion-icon slot="start" :icon="dynamic" color="warning"></ion-icon>
<ion-label>Dynamic Icon</ion-label>
<ion-button slot="end" fill="outline" @click="toggle">
Toggle Icon
</ion-button>
</ion-item>
<ion-item>
<ion-icon slot="start" :ios="heartCircleOutline" :md="personCircleOutline"></ion-icon>
<ion-label>
<p>ios: heart circle</p>
<p>md: person circle</p>
</ion-label>
</ion-item>
<ion-item>
<ion-icon slot="start" :ios="starOutline" :md="star"></ion-icon>
<ion-label>
<p>ios: star outline</p>
<p>md: star</p>
</ion-label>
</ion-item>
<ion-item>
<ion-icon slot="start" mode="ios" :ios="starOutline" :md="star"></ion-icon>
<ion-label>
<h3>mode: ios</h3>
<p>ios: star outline</p>
<p>md: star</p>
</ion-label>
</ion-item>
<ion-item>
<ion-icon slot="start" mode="md" :ios="starOutline" :md="star"></ion-icon>
<ion-label>
<h3>mode: md</h3>
<p>ios: star outline</p>
<p>md: star</p>
</ion-label>
</ion-item>
<ion-item>
<ion-icon id="customSvg" slot="start" :ios="iosCustomSvg" :md="mdCustomSvg"></ion-icon>
<ion-label>
<p>Custom SVG</p>
</ion-label>
</ion-item>
</ion-list>
</ion-content>
</ion-page>
</template>
<script lang="ts">
import {
IonBackButton,
IonButton,
IonButtons,
IonContent,
IonHeader,
IonIcon,
IonItem,
IonLabel,
IonList,
IonPage,
IonTitle,
IonToolbar
} from '@ionic/vue';
import { defineComponent, ref } from 'vue';
import { heart, heartCircleOutline, logoApple, logoTwitter, personCircleOutline, star, starOutline, trash } from 'ionicons/icons';
export default defineComponent({
components: {
IonBackButton,
IonButton,
IonButtons,
IonContent,
IonHeader,
IonIcon,
IonItem,
IonLabel,
IonList,
IonPage,
IonTitle,
IonToolbar
},
setup() {
const iosCustomSvg = "../assets/logo-apple.svg";
const mdCustomSvg = "../assets/logo-android.svg";
const dynamic = ref(star);
const toggle = () => {
const current = dynamic.value;
dynamic.value = current === star ? starOutline : star;
}
return {
iosCustomSvg,
mdCustomSvg,
dynamic,
heart,
heartCircleOutline,
logoApple,
logoTwitter,
personCircleOutline,
star,
starOutline,
trash,
toggle
}
}
});
</script>

View File

@ -0,0 +1,22 @@
describe('Icons', () => {
it('should use ios svg', () => {
cy.visit('http://localhost:8080/icons?ionic:mode=ios');
cy.get('#customSvg').shadow().find('svg').should('have.class', 'ios');
cy.get('#customSvg').shadow().find('svg').should('have.class', 'apple');
});
it('should use md svg', () => {
cy.visit('http://localhost:8080/icons?ionic:mode=md');
cy.get('#customSvg').shadow().find('svg').should('have.class', 'md');
cy.get('#customSvg').shadow().find('svg').should('have.class', 'android');
});
it('should use fallback md svg', () => {
cy.visit('http://localhost:8080/icons');
cy.get('#customSvg').shadow().find('svg').should('have.class', 'md');
cy.get('#customSvg').shadow().find('svg').should('have.class', 'android');
});
})