update DemoReactThemeSwitcher,
This commit is contained in:
@@ -1,96 +0,0 @@
|
|||||||
import {
|
|
||||||
IonButton,
|
|
||||||
IonButtons,
|
|
||||||
IonCol,
|
|
||||||
IonContent,
|
|
||||||
IonHeader,
|
|
||||||
IonIcon,
|
|
||||||
IonPage,
|
|
||||||
IonRow,
|
|
||||||
IonTitle,
|
|
||||||
IonToolbar,
|
|
||||||
useIonRouter,
|
|
||||||
} from '@ionic/react';
|
|
||||||
|
|
||||||
import { Geolocation } from '@capacitor/geolocation';
|
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { SkeletonDashboard } from '../TestComponents/SkeletonDashboard';
|
|
||||||
import { chevronBackOutline, refreshOutline } from 'ionicons/icons';
|
|
||||||
import { CurrentWeather } from '../TestComponents/CurrentWeather';
|
|
||||||
|
|
||||||
function Tab1() {
|
|
||||||
const router = useIonRouter();
|
|
||||||
|
|
||||||
const [currentWeather, setCurrentWeather] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getCurrentPosition();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const getCurrentPosition = async () => {
|
|
||||||
setCurrentWeather(false);
|
|
||||||
const coordinates = await Geolocation.getCurrentPosition();
|
|
||||||
getAddress(coordinates.coords);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getAddress = async (coords) => {
|
|
||||||
const query = `${coords.latitude},${coords.longitude}`;
|
|
||||||
const response = await fetch(
|
|
||||||
`https://api.weatherapi.com/v1/current.json?key=f93eb660b2424258bf5155016210712&q=${query}`
|
|
||||||
);
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
console.log(data);
|
|
||||||
setCurrentWeather(data);
|
|
||||||
};
|
|
||||||
|
|
||||||
// const router = useIonRouter();
|
|
||||||
function handleBackClick() {
|
|
||||||
router.goBack();
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<IonPage>
|
|
||||||
<IonHeader>
|
|
||||||
<IonToolbar>
|
|
||||||
<IonTitle>My Weather</IonTitle>
|
|
||||||
|
|
||||||
<IonButtons slot="end">
|
|
||||||
<IonButton onClick={() => getCurrentPosition()}>
|
|
||||||
<IonIcon icon={refreshOutline} color="primary" />
|
|
||||||
</IonButton>
|
|
||||||
</IonButtons>
|
|
||||||
|
|
||||||
<IonButtons slot="start">
|
|
||||||
<IonButton onClick={() => handleBackClick()}>
|
|
||||||
<IonIcon icon={chevronBackOutline} color="primary" />
|
|
||||||
</IonButton>
|
|
||||||
</IonButtons>
|
|
||||||
</IonToolbar>
|
|
||||||
</IonHeader>
|
|
||||||
<IonContent fullscreen>
|
|
||||||
<IonHeader collapse="condense">
|
|
||||||
<IonToolbar>
|
|
||||||
<IonTitle size="large">Dashboard</IonTitle>
|
|
||||||
</IonToolbar>
|
|
||||||
</IonHeader>
|
|
||||||
|
|
||||||
<IonRow className="ion-margin-start ion-margin-end ion-justify-content-center ion-text-center">
|
|
||||||
<IonCol size="12">
|
|
||||||
<h4>Here's your location based weather</h4>
|
|
||||||
</IonCol>
|
|
||||||
</IonRow>
|
|
||||||
|
|
||||||
<div style={{ marginTop: '-1.5rem' }}>
|
|
||||||
{currentWeather ? (
|
|
||||||
<CurrentWeather currentWeather={currentWeather} />
|
|
||||||
) : (
|
|
||||||
<SkeletonDashboard />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</IonContent>
|
|
||||||
</IonPage>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Tab1;
|
|
@@ -1,81 +0,0 @@
|
|||||||
import {
|
|
||||||
IonButton,
|
|
||||||
IonCol,
|
|
||||||
IonContent,
|
|
||||||
IonHeader,
|
|
||||||
IonPage,
|
|
||||||
IonRow,
|
|
||||||
IonSearchbar,
|
|
||||||
IonTitle,
|
|
||||||
IonToolbar,
|
|
||||||
} from '@ionic/react';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import { CurrentWeather } from '../TestComponents/CurrentWeather';
|
|
||||||
|
|
||||||
function Tab2() {
|
|
||||||
const [search, setSearch] = useState('');
|
|
||||||
const [currentWeather, setCurrentWeather] = useState(false);
|
|
||||||
|
|
||||||
const performSearch = async () => {
|
|
||||||
getAddress(search);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getAddress = async (city) => {
|
|
||||||
const response = await fetch(
|
|
||||||
`https://api.weatherapi.com/v1/current.json?key=f93eb660b2424258bf5155016210712&q=${city}&aqi=no`
|
|
||||||
);
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (data && data.current && data.location) {
|
|
||||||
setCurrentWeather(data);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<IonPage>
|
|
||||||
<IonHeader>
|
|
||||||
<IonToolbar>
|
|
||||||
<IonTitle>Search</IonTitle>
|
|
||||||
</IonToolbar>
|
|
||||||
</IonHeader>
|
|
||||||
<IonContent fullscreen>
|
|
||||||
<IonHeader collapse="condense">
|
|
||||||
<IonToolbar>
|
|
||||||
<IonTitle size="large">Search</IonTitle>
|
|
||||||
</IonToolbar>
|
|
||||||
</IonHeader>
|
|
||||||
|
|
||||||
<IonRow className="ion-justify-content-center ion-margin-top ion-align-items-center">
|
|
||||||
<IonCol size="7">
|
|
||||||
<IonSearchbar
|
|
||||||
placeholder="Try 'London'"
|
|
||||||
animated
|
|
||||||
value={search}
|
|
||||||
onIonChange={(e) => setSearch(e.target.value)}
|
|
||||||
/>
|
|
||||||
</IonCol>
|
|
||||||
|
|
||||||
<IonCol size="5">
|
|
||||||
<IonButton
|
|
||||||
expand="block"
|
|
||||||
className="ion-margin-start ion-margin-end"
|
|
||||||
onClick={performSearch}
|
|
||||||
>
|
|
||||||
Search
|
|
||||||
</IonButton>
|
|
||||||
</IonCol>
|
|
||||||
</IonRow>
|
|
||||||
|
|
||||||
<div style={{ marginTop: '-0.8rem' }}>
|
|
||||||
{currentWeather ? (
|
|
||||||
<CurrentWeather currentWeather={currentWeather} />
|
|
||||||
) : (
|
|
||||||
<h3 className="ion-text-center">Your search result will appear here</h3>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</IonContent>
|
|
||||||
</IonPage>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Tab2;
|
|
@@ -1,62 +0,0 @@
|
|||||||
import { IonCardSubtitle, IonCol, IonIcon, IonNote, IonRow } from '@ionic/react';
|
|
||||||
import { pulseOutline, sunnyOutline, thermometerOutline } from 'ionicons/icons';
|
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
export const WeatherProperty = ({ type, currentWeather }: { type: any; currentWeather: any }) => {
|
|
||||||
const [property, setProperty] = useState(false);
|
|
||||||
|
|
||||||
const properties = {
|
|
||||||
wind: {
|
|
||||||
isIcon: false,
|
|
||||||
icon: '/assets/WeatherDemo/wind.png',
|
|
||||||
alt: 'wind',
|
|
||||||
label: 'Wind',
|
|
||||||
value: `${currentWeather.current.wind_mph}mph`,
|
|
||||||
},
|
|
||||||
feelsLike: {
|
|
||||||
isIcon: true,
|
|
||||||
icon: thermometerOutline,
|
|
||||||
alt: 'feels like',
|
|
||||||
label: 'Feels like',
|
|
||||||
value: `${currentWeather.current.feelslike_c}°C`,
|
|
||||||
},
|
|
||||||
indexUV: {
|
|
||||||
isIcon: true,
|
|
||||||
icon: sunnyOutline,
|
|
||||||
alt: 'index uv',
|
|
||||||
label: 'Index UV',
|
|
||||||
value: currentWeather.current.uv,
|
|
||||||
},
|
|
||||||
pressure: {
|
|
||||||
isIcon: true,
|
|
||||||
icon: pulseOutline,
|
|
||||||
alt: 'pressure',
|
|
||||||
label: 'Pressure',
|
|
||||||
value: `${currentWeather.current.pressure_mb} mbar`,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setProperty(properties[type]);
|
|
||||||
}, [type]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<IonCol size="6">
|
|
||||||
<IonRow className="ion-justify-content-center ion-align-items-center">
|
|
||||||
<IonCol size="3">
|
|
||||||
{!property.isIcon && (
|
|
||||||
<img alt={property.alt} src={property.icon} height="32" width="32" />
|
|
||||||
)}
|
|
||||||
{property.isIcon && (
|
|
||||||
<IonIcon icon={property.icon} color="medium" style={{ fontSize: '2rem' }} />
|
|
||||||
)}
|
|
||||||
</IonCol>
|
|
||||||
|
|
||||||
<IonCol size="9">
|
|
||||||
<IonCardSubtitle>{property.label}</IonCardSubtitle>
|
|
||||||
<IonNote>{property.value}</IonNote>
|
|
||||||
</IonCol>
|
|
||||||
</IonRow>
|
|
||||||
</IonCol>
|
|
||||||
);
|
|
||||||
};
|
|
@@ -1,48 +0,0 @@
|
|||||||
import { IonCard, IonCardContent, IonGrid, IonRow, IonText, IonCardTitle } from '@ionic/react';
|
|
||||||
import { WeatherProperty } from './WeatherProperty';
|
|
||||||
|
|
||||||
export const CurrentWeather = ({ currentWeather }: { currentWeather: any }) => (
|
|
||||||
<IonGrid>
|
|
||||||
<IonCard>
|
|
||||||
<IonCardContent className="ion-text-center">
|
|
||||||
<IonText color="primary">
|
|
||||||
<h1>
|
|
||||||
{currentWeather.location.region},{' '}
|
|
||||||
<span style={{ color: 'gray' }}>{currentWeather.location.country}</span>
|
|
||||||
</h1>
|
|
||||||
</IonText>
|
|
||||||
|
|
||||||
<div className="ion-margin-top">
|
|
||||||
<img
|
|
||||||
alt="condition"
|
|
||||||
src={currentWeather.current.condition.icon.replace('//', 'https://')}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<IonText color="dark">
|
|
||||||
<h1 style={{ fontWeight: 'bold' }}>{currentWeather.current.condition.text}</h1>
|
|
||||||
</IonText>
|
|
||||||
|
|
||||||
<IonText color="medium">
|
|
||||||
<p>{new Date(currentWeather.location.localtime).toDateString()}</p>
|
|
||||||
</IonText>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<IonCardTitle style={{ fontSize: '3rem' }} className="ion-margin-top">
|
|
||||||
{currentWeather.current.temp_c}℃
|
|
||||||
</IonCardTitle>
|
|
||||||
|
|
||||||
<IonGrid className="ion-margin-top">
|
|
||||||
<IonRow>
|
|
||||||
<WeatherProperty type="wind" currentWeather={currentWeather} />
|
|
||||||
<WeatherProperty type="feelsLike" currentWeather={currentWeather} />
|
|
||||||
</IonRow>
|
|
||||||
|
|
||||||
<IonRow className="ion-margin-top">
|
|
||||||
<WeatherProperty type="indexUV" currentWeather={currentWeather} />
|
|
||||||
<WeatherProperty type="pressure" currentWeather={currentWeather} />
|
|
||||||
</IonRow>
|
|
||||||
</IonGrid>
|
|
||||||
</IonCardContent>
|
|
||||||
</IonCard>
|
|
||||||
</IonGrid>
|
|
||||||
);
|
|
@@ -1,117 +0,0 @@
|
|||||||
import {
|
|
||||||
IonCard,
|
|
||||||
IonCardContent,
|
|
||||||
IonCardSubtitle,
|
|
||||||
IonCardTitle,
|
|
||||||
IonCol,
|
|
||||||
IonGrid,
|
|
||||||
IonIcon,
|
|
||||||
IonNote,
|
|
||||||
IonRow,
|
|
||||||
IonSkeletonText,
|
|
||||||
IonText,
|
|
||||||
IonThumbnail,
|
|
||||||
} from '@ionic/react';
|
|
||||||
import { pulseOutline, sunnyOutline, thermometerOutline } from 'ionicons/icons';
|
|
||||||
|
|
||||||
export const SkeletonDashboard = () => (
|
|
||||||
<IonGrid>
|
|
||||||
<IonCard>
|
|
||||||
<IonCardContent className="ion-text-center">
|
|
||||||
<IonText color="primary">
|
|
||||||
<h1>
|
|
||||||
<IonSkeletonText animated style={{ height: '2rem', width: '90%' }} />
|
|
||||||
</h1>
|
|
||||||
</IonText>
|
|
||||||
|
|
||||||
<div className="ion-margin-top">
|
|
||||||
<IonThumbnail>
|
|
||||||
<IonSkeletonText animated style={{ width: '2rem', height: '2rem' }} />
|
|
||||||
</IonThumbnail>
|
|
||||||
|
|
||||||
<IonText color="dark">
|
|
||||||
<h1 style={{ fontWeight: 'bold' }}>
|
|
||||||
<IonSkeletonText animated style={{ height: '2rem', width: '90%' }} />
|
|
||||||
</h1>
|
|
||||||
</IonText>
|
|
||||||
|
|
||||||
<IonText color="medium">
|
|
||||||
<p>
|
|
||||||
<IonSkeletonText animated style={{ height: '2rem', width: '90%' }} />
|
|
||||||
</p>
|
|
||||||
</IonText>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<IonCardTitle style={{ fontSize: '3rem' }} className="ion-margin-top">
|
|
||||||
<IonSkeletonText animated style={{ height: '3rem', width: '30%', textAlign: 'center' }} />
|
|
||||||
</IonCardTitle>
|
|
||||||
|
|
||||||
<IonGrid className="ion-margin-top">
|
|
||||||
<IonRow>
|
|
||||||
<IonCol size="6">
|
|
||||||
<IonRow className="ion-justify-content-center ion-align-items-center">
|
|
||||||
<IonCol size="3">
|
|
||||||
<img alt="wind" src="/assets/WeatherDemo/wind.png" height="32" width="32" />
|
|
||||||
</IonCol>
|
|
||||||
|
|
||||||
<IonCol size="9">
|
|
||||||
<IonCardSubtitle>Wind</IonCardSubtitle>
|
|
||||||
<IonNote>
|
|
||||||
<IonSkeletonText animated style={{ height: '2rem', width: '90%' }} />
|
|
||||||
</IonNote>
|
|
||||||
</IonCol>
|
|
||||||
</IonRow>
|
|
||||||
</IonCol>
|
|
||||||
|
|
||||||
<IonCol size="6">
|
|
||||||
<IonRow className="ion-justify-content-center ion-align-items-center">
|
|
||||||
<IonCol size="3">
|
|
||||||
<IonIcon icon={thermometerOutline} color="medium" style={{ fontSize: '2rem' }} />
|
|
||||||
</IonCol>
|
|
||||||
|
|
||||||
<IonCol size="9">
|
|
||||||
<IonCardSubtitle>Feels like</IonCardSubtitle>
|
|
||||||
<IonNote>
|
|
||||||
<IonSkeletonText animated style={{ height: '2rem', width: '90%' }} />
|
|
||||||
</IonNote>
|
|
||||||
</IonCol>
|
|
||||||
</IonRow>
|
|
||||||
</IonCol>
|
|
||||||
</IonRow>
|
|
||||||
|
|
||||||
<IonRow className="ion-margin-top">
|
|
||||||
<IonCol size="6">
|
|
||||||
<IonRow className="ion-justify-content-center ion-align-items-center">
|
|
||||||
<IonCol size="3">
|
|
||||||
<IonIcon icon={sunnyOutline} color="medium" style={{ fontSize: '2rem' }} />
|
|
||||||
</IonCol>
|
|
||||||
|
|
||||||
<IonCol size="9">
|
|
||||||
<IonCardSubtitle>Index UV</IonCardSubtitle>
|
|
||||||
<IonNote>
|
|
||||||
<IonSkeletonText animated style={{ height: '2rem', width: '90%' }} />
|
|
||||||
</IonNote>
|
|
||||||
</IonCol>
|
|
||||||
</IonRow>
|
|
||||||
</IonCol>
|
|
||||||
|
|
||||||
<IonCol size="6">
|
|
||||||
<IonRow className="ion-justify-content-center ion-align-items-center">
|
|
||||||
<IonCol size="3">
|
|
||||||
<IonIcon icon={pulseOutline} color="medium" style={{ fontSize: '2rem' }} />
|
|
||||||
</IonCol>
|
|
||||||
|
|
||||||
<IonCol size="9">
|
|
||||||
<IonCardSubtitle>Pressure</IonCardSubtitle>
|
|
||||||
<IonNote>
|
|
||||||
<IonSkeletonText animated style={{ height: '2rem', width: '90%' }} />
|
|
||||||
</IonNote>
|
|
||||||
</IonCol>
|
|
||||||
</IonRow>
|
|
||||||
</IonCol>
|
|
||||||
</IonRow>
|
|
||||||
</IonGrid>
|
|
||||||
</IonCardContent>
|
|
||||||
</IonCard>
|
|
||||||
</IonGrid>
|
|
||||||
);
|
|
@@ -0,0 +1,24 @@
|
|||||||
|
.container {
|
||||||
|
text-align: center;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.container strong {
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container p {
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 22px;
|
||||||
|
color: #8c8c8c;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
@@ -0,0 +1,22 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import './ExploreContainer.scss';
|
||||||
|
|
||||||
|
const ExploreContainer = ({ name }: { name: string }): React.JSX.Element => {
|
||||||
|
return (
|
||||||
|
<div className="container">
|
||||||
|
<strong>{name}</strong>
|
||||||
|
<p>
|
||||||
|
Explore{' '}
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
href="https://ionicframework.com/docs/components"
|
||||||
|
>
|
||||||
|
UI Components
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ExploreContainer;
|
@@ -1,39 +1,58 @@
|
|||||||
import { IonIcon, IonLabel, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs } from '@ionic/react';
|
import { IonIcon, IonLabel, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs } from '@ionic/react';
|
||||||
|
|
||||||
import { cloudOutline, searchOutline } from 'ionicons/icons';
|
import {
|
||||||
|
brushOutline,
|
||||||
|
cloudOutline,
|
||||||
|
handLeftOutline,
|
||||||
|
informationCircle,
|
||||||
|
searchOutline,
|
||||||
|
} from 'ionicons/icons';
|
||||||
|
|
||||||
import { Route, Redirect } from 'react-router';
|
import { Route, Redirect } from 'react-router';
|
||||||
|
|
||||||
import Tab1 from './AppPages/Tab1';
|
import './theme/variables.scss';
|
||||||
import Tab2 from './AppPages/Tab2';
|
|
||||||
|
|
||||||
import './style.scss';
|
import ThemeStore from './store/ThemeStore';
|
||||||
|
import Info from './pages/Info';
|
||||||
|
import Themes from './pages/Themes';
|
||||||
|
import Examples from './pages/Examples';
|
||||||
|
|
||||||
function DemoReactThemeSwitcher() {
|
function DemoReactThemeSwitcher() {
|
||||||
|
const theme = ThemeStore.useState((s) => s.currentTheme);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IonTabs>
|
<div className="helloworld" style={theme ? theme : {}}>
|
||||||
<IonRouterOutlet>
|
<IonTabs>
|
||||||
<Route exact path="/demo-react-theme-switcher/tab1">
|
<IonRouterOutlet>
|
||||||
<Tab1 />
|
<Route exact path="/demo-react-theme-switcher/info">
|
||||||
</Route>
|
<Info />
|
||||||
<Route exact path="/demo-react-theme-switcher/tab2">
|
</Route>
|
||||||
<Tab2 />
|
<Route exact path="/demo-react-theme-switcher/themes">
|
||||||
</Route>
|
<Themes />
|
||||||
|
</Route>
|
||||||
|
<Route exact path="/demo-react-theme-switcher/examples">
|
||||||
|
<Examples />
|
||||||
|
</Route>
|
||||||
|
|
||||||
<Redirect exact path="/demo-react-theme-switcher" to="/demo-react-theme-switcher/tab1" />
|
<Redirect exact path="/demo-react-theme-switcher" to="/demo-react-theme-switcher/info" />
|
||||||
</IonRouterOutlet>
|
</IonRouterOutlet>
|
||||||
|
|
||||||
{/* */}
|
<IonTabBar slot="bottom">
|
||||||
<IonTabBar slot="bottom">
|
<IonTabButton tab="tab1" href="/demo-react-theme-switcher/info">
|
||||||
<IonTabButton tab="tab1" href="/demo-react-theme-switcher/tab1">
|
<IonIcon icon={informationCircle} />
|
||||||
<IonIcon icon={cloudOutline} />
|
<IonLabel>Info</IonLabel>
|
||||||
<IonLabel>Dashboard</IonLabel>
|
</IonTabButton>
|
||||||
</IonTabButton>
|
<IonTabButton tab="tab2" href="/demo-react-theme-switcher/themes">
|
||||||
<IonTabButton tab="tab2" href="/demo-react-theme-switcher/tab2">
|
<IonIcon icon={brushOutline} />
|
||||||
<IonIcon icon={searchOutline} />
|
<IonLabel>Themes</IonLabel>
|
||||||
<IonLabel>Search</IonLabel>
|
</IonTabButton>
|
||||||
</IonTabButton>
|
<IonTabButton tab="tab3" href="/demo-react-theme-switcher/examples">
|
||||||
</IonTabBar>
|
<IonIcon icon={handLeftOutline} />
|
||||||
</IonTabs>
|
<IonLabel>Examples</IonLabel>
|
||||||
|
</IonTabButton>
|
||||||
|
</IonTabBar>
|
||||||
|
</IonTabs>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +1,126 @@
|
|||||||
|
import {
|
||||||
|
IonBadge,
|
||||||
|
IonButton,
|
||||||
|
IonCardSubtitle,
|
||||||
|
IonCardTitle,
|
||||||
|
IonCol,
|
||||||
|
IonContent,
|
||||||
|
IonGrid,
|
||||||
|
IonHeader,
|
||||||
|
IonIcon,
|
||||||
|
IonItem,
|
||||||
|
IonLabel,
|
||||||
|
IonPage,
|
||||||
|
IonRange,
|
||||||
|
IonRow,
|
||||||
|
IonSelect,
|
||||||
|
IonSelectOption,
|
||||||
|
IonSpinner,
|
||||||
|
IonText,
|
||||||
|
IonTitle,
|
||||||
|
IonToggle,
|
||||||
|
IonToolbar,
|
||||||
|
} from '@ionic/react';
|
||||||
|
import { star, sunny } from 'ionicons/icons';
|
||||||
|
import { useGetSelectedTheme } from '../store/ThemeStore';
|
||||||
|
|
||||||
|
import './Examples.scss';
|
||||||
|
|
||||||
|
const Examples = (): React.JSX.Element => {
|
||||||
|
const currentTheme = useGetSelectedTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IonPage>
|
||||||
|
<IonHeader>
|
||||||
|
<IonToolbar>
|
||||||
|
<IonTitle>Examples</IonTitle>
|
||||||
|
</IonToolbar>
|
||||||
|
</IonHeader>
|
||||||
|
<IonContent fullscreen>
|
||||||
|
<IonGrid>
|
||||||
|
<IonRow
|
||||||
|
className="ion-text-center ion-padding ion-margin-bottom"
|
||||||
|
style={{ backgroundColor: 'var(--ion-color-main-light)' }}
|
||||||
|
>
|
||||||
|
<IonCol size="12">
|
||||||
|
<IonCardSubtitle color="light">Current Theme</IonCardSubtitle>
|
||||||
|
<IonCardTitle color="light">{currentTheme}</IonCardTitle>
|
||||||
|
<IonText color="light">
|
||||||
|
<p>Here are a few examples of how the theme looks on stock Ionic components.</p>
|
||||||
|
</IonText>
|
||||||
|
</IonCol>
|
||||||
|
</IonRow>
|
||||||
|
|
||||||
|
<IonRow className="ion-text-center">
|
||||||
|
<IonCol size="12">
|
||||||
|
<IonCardSubtitle>Buttons</IonCardSubtitle>
|
||||||
|
<IonButton color="main">Main Color button</IonButton>
|
||||||
|
<IonButton color="main-light">Light Color button</IonButton>
|
||||||
|
</IonCol>
|
||||||
|
</IonRow>
|
||||||
|
|
||||||
|
<IonRow className="ion-text-center">
|
||||||
|
<IonCol size="12">
|
||||||
|
<IonCardSubtitle>Toggle</IonCardSubtitle>
|
||||||
|
|
||||||
|
<IonItem lines="none">
|
||||||
|
<IonLabel>Toggle it on/off</IonLabel>
|
||||||
|
<IonToggle />
|
||||||
|
</IonItem>
|
||||||
|
</IonCol>
|
||||||
|
</IonRow>
|
||||||
|
|
||||||
|
<IonRow className="ion-text-center">
|
||||||
|
<IonCol size="12">
|
||||||
|
<IonCardSubtitle>Select</IonCardSubtitle>
|
||||||
|
<IonItem lines="none">
|
||||||
|
<IonLabel>Pick an option</IonLabel>
|
||||||
|
<IonSelect placeholder="Select...">
|
||||||
|
<IonSelectOption value="1">Option 1</IonSelectOption>
|
||||||
|
<IonSelectOption value="2">Option 2</IonSelectOption>
|
||||||
|
</IonSelect>
|
||||||
|
</IonItem>
|
||||||
|
</IonCol>
|
||||||
|
</IonRow>
|
||||||
|
|
||||||
|
<IonRow className="ion-text-center">
|
||||||
|
<IonCol size="12">
|
||||||
|
<IonCardSubtitle>Badge</IonCardSubtitle>
|
||||||
|
<IonItem lines="none">
|
||||||
|
<IonLabel>Awesome badge!!</IonLabel>
|
||||||
|
<IonBadge>
|
||||||
|
<IonIcon icon={star} />
|
||||||
|
Woohoo!
|
||||||
|
</IonBadge>
|
||||||
|
</IonItem>
|
||||||
|
</IonCol>
|
||||||
|
</IonRow>
|
||||||
|
|
||||||
|
<IonRow className="ion-text-center">
|
||||||
|
<IonCol size="12">
|
||||||
|
<IonCardSubtitle>Spinner</IonCardSubtitle>
|
||||||
|
<IonItem lines="none">
|
||||||
|
<IonLabel>Loading, please wait...</IonLabel>
|
||||||
|
<IonBadge>
|
||||||
|
<IonSpinner name="bubbles" />
|
||||||
|
</IonBadge>
|
||||||
|
</IonItem>
|
||||||
|
</IonCol>
|
||||||
|
</IonRow>
|
||||||
|
|
||||||
|
<IonRow className="ion-text-center">
|
||||||
|
<IonCol size="12">
|
||||||
|
<IonCardSubtitle>Range</IonCardSubtitle>
|
||||||
|
<IonRange min={1000} max={2000} step={100} snaps={true} ticks={false} color="main">
|
||||||
|
<IonIcon icon={sunny} size="small" slot="start" />
|
||||||
|
<IonIcon icon={sunny} slot="end" />
|
||||||
|
</IonRange>
|
||||||
|
</IonCol>
|
||||||
|
</IonRow>
|
||||||
|
</IonGrid>
|
||||||
|
</IonContent>
|
||||||
|
</IonPage>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Examples;
|
@@ -0,0 +1,83 @@
|
|||||||
|
import {
|
||||||
|
IonButton,
|
||||||
|
IonCardSubtitle,
|
||||||
|
IonCardTitle,
|
||||||
|
IonCol,
|
||||||
|
IonContent,
|
||||||
|
IonGrid,
|
||||||
|
IonHeader,
|
||||||
|
IonPage,
|
||||||
|
IonRow,
|
||||||
|
IonText,
|
||||||
|
IonTitle,
|
||||||
|
IonToolbar,
|
||||||
|
} from '@ionic/react';
|
||||||
|
import { useGetSelectedTheme } from '../store/ThemeStore';
|
||||||
|
import './Info.scss';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
function Info(): React.JSX.Element {
|
||||||
|
const currentTheme = useGetSelectedTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IonPage>
|
||||||
|
<IonHeader>
|
||||||
|
<IonToolbar>
|
||||||
|
<IonTitle>Info TS</IonTitle>
|
||||||
|
</IonToolbar>
|
||||||
|
</IonHeader>
|
||||||
|
<IonContent fullscreen>
|
||||||
|
<IonGrid>
|
||||||
|
<IonRow
|
||||||
|
className="ion-text-left ion-padding ion-margin-bottom"
|
||||||
|
style={{ backgroundColor: 'var(--ion-color-main-light)' }}
|
||||||
|
>
|
||||||
|
<IonCol size="12">
|
||||||
|
<IonCardSubtitle color="light">Current Theme</IonCardSubtitle>
|
||||||
|
<IonCardTitle color="light">{currentTheme}</IonCardTitle>
|
||||||
|
<IonText color="light">
|
||||||
|
<p>
|
||||||
|
This is an example showing how to easily implement dynamic themes into an Ionic
|
||||||
|
app. We could use the setProperty method, but you'll notice that we can pass a
|
||||||
|
style object into the IonApp component - I feel like we have more control this
|
||||||
|
way. With this in mind, we can utilise all of the Ionic color CSS variables and
|
||||||
|
custom variables.
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
Check out the <code>setTheme</code> function
|
||||||
|
<br />
|
||||||
|
<br />I haven't over-rode every possible Ionic CSS variable, just a few of the
|
||||||
|
core visually noticeable ones for this example.
|
||||||
|
</p>
|
||||||
|
</IonText>
|
||||||
|
</IonCol>
|
||||||
|
</IonRow>
|
||||||
|
|
||||||
|
<IonRow
|
||||||
|
className="ion-text-left ion-padding ion-margin-bottom"
|
||||||
|
style={{ backgroundColor: 'var(--ion-color-main-light)' }}
|
||||||
|
>
|
||||||
|
<IonCol size="12">
|
||||||
|
<IonCardSubtitle color="light">Switching themes</IonCardSubtitle>
|
||||||
|
<IonCardTitle color="light">Using global state</IonCardTitle>
|
||||||
|
<IonText color="light">
|
||||||
|
<p>
|
||||||
|
We now know that our overall theme is controlled via a style object, so we can
|
||||||
|
easily store this in state. In this example I'm using Pullstate, and updating the
|
||||||
|
"currentTheme" on each change. I've mimicked an API call from local JSON data, as
|
||||||
|
if it were a customer/client theme or branding.
|
||||||
|
</p>
|
||||||
|
</IonText>
|
||||||
|
</IonCol>
|
||||||
|
</IonRow>
|
||||||
|
|
||||||
|
<IonButton routerLink="/themes" color="main" expand="full">
|
||||||
|
View Themes →
|
||||||
|
</IonButton>
|
||||||
|
</IonGrid>
|
||||||
|
</IonContent>
|
||||||
|
</IonPage>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Info;
|
@@ -0,0 +1,60 @@
|
|||||||
|
import {
|
||||||
|
IonButton,
|
||||||
|
IonCol,
|
||||||
|
IonContent,
|
||||||
|
IonHeader,
|
||||||
|
IonIcon,
|
||||||
|
IonPage,
|
||||||
|
IonRow,
|
||||||
|
IonTitle,
|
||||||
|
IonToolbar,
|
||||||
|
} from '@ionic/react';
|
||||||
|
import { checkmark, checkmarkCircle, checkmarkOutline } from 'ionicons/icons';
|
||||||
|
|
||||||
|
import ExploreContainer from '../components/ExploreContainer';
|
||||||
|
|
||||||
|
import ThemeStore, { setTheme } from '../store/ThemeStore';
|
||||||
|
import './Themes.scss';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const Themes = (): React.JSX.Element => {
|
||||||
|
const themes = ThemeStore.useState((s: any) => s.themes);
|
||||||
|
const selectedThemeID = ThemeStore.useState((s: any) => s.selectedID);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IonPage>
|
||||||
|
<IonHeader>
|
||||||
|
<IonToolbar>
|
||||||
|
<IonTitle>Themes</IonTitle>
|
||||||
|
</IonToolbar>
|
||||||
|
</IonHeader>
|
||||||
|
<IonContent fullscreen>
|
||||||
|
{/* <ExploreContainer name="Tab 1 page" /> */}
|
||||||
|
|
||||||
|
<IonRow>
|
||||||
|
{themes.map((theme: any, index: number) => {
|
||||||
|
return (
|
||||||
|
<IonCol
|
||||||
|
size="6"
|
||||||
|
onClick={() => {
|
||||||
|
console.log(theme.file);
|
||||||
|
console.log(theme.id);
|
||||||
|
setTheme(theme.file, theme.id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{theme.id === selectedThemeID && (
|
||||||
|
<div className="selected-theme">
|
||||||
|
<IonIcon icon={checkmark} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<img src={theme.cover} alt="" />
|
||||||
|
</IonCol>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</IonRow>
|
||||||
|
</IonContent>
|
||||||
|
</IonPage>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Themes;
|
@@ -0,0 +1,109 @@
|
|||||||
|
import { Store } from 'pullstate';
|
||||||
|
|
||||||
|
const ThemeStore = new Store({
|
||||||
|
selectedID: '',
|
||||||
|
currentTheme: {},
|
||||||
|
themes: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'Leafy Green',
|
||||||
|
file: 'leafygreen.json',
|
||||||
|
cover: '/assets/DemoReactThemeSwitcher/themes/covers/leafygreen.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'Moody Blue',
|
||||||
|
file: 'moodyblue.json',
|
||||||
|
cover: '/assets/DemoReactThemeSwitcher/themes/covers/moodyblue.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: 'Earthy Tones',
|
||||||
|
file: 'earthytones.json',
|
||||||
|
cover: '/assets/DemoReactThemeSwitcher/themes/covers/earthytones.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: 'Peely Orange',
|
||||||
|
file: 'peelyorange.json',
|
||||||
|
cover: '/assets/DemoReactThemeSwitcher/themes/covers/peelyorange.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: 'Firey Red',
|
||||||
|
file: 'fireyred.json',
|
||||||
|
cover: '/assets/DemoReactThemeSwitcher/themes/covers/fireyred.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
name: 'Coffee Brown',
|
||||||
|
file: 'coffeebrown.json',
|
||||||
|
cover: '/assets/DemoReactThemeSwitcher/themes/covers/coffeebrown.png',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ThemeStore;
|
||||||
|
|
||||||
|
const buildTheme = (theme: any) => {
|
||||||
|
const appTheme = {
|
||||||
|
'--ion-toolbar-background': theme.toolbar_background_color,
|
||||||
|
'--ion-tab-bar-background': theme.tab_bar_background_color,
|
||||||
|
'--ion-toolbar-color': theme.toolbar_color,
|
||||||
|
'--ion-tab-bar-color': theme.tab_bar_color,
|
||||||
|
'--ion-tab-bar-color-selected': theme.tab_bar_activated_color,
|
||||||
|
|
||||||
|
'--ion-color-main-light': theme.light_color,
|
||||||
|
'--ion-color-main-light-shade': theme.light_color_shade,
|
||||||
|
'--ion-color-main-light-tint': theme.light_color_tint,
|
||||||
|
|
||||||
|
'--ion-color-main-color': theme.main_color,
|
||||||
|
|
||||||
|
// Set primary to be the main color as well
|
||||||
|
'--ion-color-primary': theme.main_color,
|
||||||
|
'--ion-color-main-color-shade': theme.main_color_shade,
|
||||||
|
'--ion-color-main-color-tint': theme.main_color_tint,
|
||||||
|
};
|
||||||
|
|
||||||
|
return appTheme;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGetSelectedTheme = () => {
|
||||||
|
const themes = ThemeStore.useState((s) => s.themes);
|
||||||
|
const selectedID = ThemeStore.useState((s) => s.selectedID);
|
||||||
|
var themeName = 'Default';
|
||||||
|
|
||||||
|
if (selectedID) {
|
||||||
|
const theme = themes.filter((t: any) => t.id === selectedID);
|
||||||
|
if (theme && theme[0]) {
|
||||||
|
themeName = theme[0].name;
|
||||||
|
} else {
|
||||||
|
themeName = 'false';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeName;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setTheme = async (file: string, id: number) => {
|
||||||
|
const response = await fetch(`/assets/DemoReactThemeSwitcher/themes/${file}`);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
const theme = buildTheme(data);
|
||||||
|
ThemeStore.update((s) => {
|
||||||
|
s.currentTheme = theme;
|
||||||
|
});
|
||||||
|
ThemeStore.update((s: any) => {
|
||||||
|
s.selectedID = id.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
// We could also override the style properties
|
||||||
|
// Using the setProperty method
|
||||||
|
// But i feel, we have more control using global state
|
||||||
|
// see below:
|
||||||
|
|
||||||
|
// for (var themeVar in theme) {
|
||||||
|
|
||||||
|
// document.documentElement.style.setProperty(themeVar, theme[themeVar]);
|
||||||
|
// }
|
||||||
|
};
|
@@ -0,0 +1,130 @@
|
|||||||
|
/* Ionic Variables and Theming. For more info, please see:
|
||||||
|
http://ionicframework.com/docs/theming/ */
|
||||||
|
|
||||||
|
/** Ionic CSS Variables **/
|
||||||
|
.helloworld {
|
||||||
|
/** primary **/
|
||||||
|
--ion-color-primary: #3880ff;
|
||||||
|
--ion-color-primary-rgb: 56, 128, 255;
|
||||||
|
--ion-color-primary-contrast: #ffffff;
|
||||||
|
--ion-color-primary-contrast-rgb: 255, 255, 255;
|
||||||
|
--ion-color-primary-shade: #3171e0;
|
||||||
|
--ion-color-primary-tint: #4c8dff;
|
||||||
|
|
||||||
|
/** secondary **/
|
||||||
|
--ion-color-secondary: #3dc2ff;
|
||||||
|
--ion-color-secondary-rgb: 61, 194, 255;
|
||||||
|
--ion-color-secondary-contrast: #ffffff;
|
||||||
|
--ion-color-secondary-contrast-rgb: 255, 255, 255;
|
||||||
|
--ion-color-secondary-shade: #36abe0;
|
||||||
|
--ion-color-secondary-tint: #50c8ff;
|
||||||
|
|
||||||
|
/** tertiary **/
|
||||||
|
--ion-color-tertiary: #5260ff;
|
||||||
|
--ion-color-tertiary-rgb: 82, 96, 255;
|
||||||
|
--ion-color-tertiary-contrast: #ffffff;
|
||||||
|
--ion-color-tertiary-contrast-rgb: 255, 255, 255;
|
||||||
|
--ion-color-tertiary-shade: #4854e0;
|
||||||
|
--ion-color-tertiary-tint: #6370ff;
|
||||||
|
|
||||||
|
/** success **/
|
||||||
|
--ion-color-success: #2dd36f;
|
||||||
|
--ion-color-success-rgb: 45, 211, 111;
|
||||||
|
--ion-color-success-contrast: #ffffff;
|
||||||
|
--ion-color-success-contrast-rgb: 255, 255, 255;
|
||||||
|
--ion-color-success-shade: #28ba62;
|
||||||
|
--ion-color-success-tint: #42d77d;
|
||||||
|
|
||||||
|
/** warning **/
|
||||||
|
--ion-color-warning: #ffc409;
|
||||||
|
--ion-color-warning-rgb: 255, 196, 9;
|
||||||
|
--ion-color-warning-contrast: #000000;
|
||||||
|
--ion-color-warning-contrast-rgb: 0, 0, 0;
|
||||||
|
--ion-color-warning-shade: #e0ac08;
|
||||||
|
--ion-color-warning-tint: #ffca22;
|
||||||
|
|
||||||
|
/** danger **/
|
||||||
|
--ion-color-danger: #eb445a;
|
||||||
|
--ion-color-danger-rgb: 235, 68, 90;
|
||||||
|
--ion-color-danger-contrast: #ffffff;
|
||||||
|
--ion-color-danger-contrast-rgb: 255, 255, 255;
|
||||||
|
--ion-color-danger-shade: #cf3c4f;
|
||||||
|
--ion-color-danger-tint: #ed576b;
|
||||||
|
|
||||||
|
/** dark **/
|
||||||
|
--ion-color-dark: #222428;
|
||||||
|
--ion-color-dark-rgb: 34, 36, 40;
|
||||||
|
--ion-color-dark-contrast: #ffffff;
|
||||||
|
--ion-color-dark-contrast-rgb: 255, 255, 255;
|
||||||
|
--ion-color-dark-shade: #1e2023;
|
||||||
|
--ion-color-dark-tint: #383a3e;
|
||||||
|
|
||||||
|
/** medium **/
|
||||||
|
--ion-color-medium: #92949c;
|
||||||
|
--ion-color-medium-rgb: 146, 148, 156;
|
||||||
|
--ion-color-medium-contrast: #ffffff;
|
||||||
|
--ion-color-medium-contrast-rgb: 255, 255, 255;
|
||||||
|
--ion-color-medium-shade: #808289;
|
||||||
|
--ion-color-medium-tint: #9d9fa6;
|
||||||
|
|
||||||
|
/** light **/
|
||||||
|
--ion-color-light: #f4f5f8;
|
||||||
|
--ion-color-light-rgb: 244, 245, 248;
|
||||||
|
--ion-color-light-contrast: #000000;
|
||||||
|
--ion-color-light-contrast-rgb: 0, 0, 0;
|
||||||
|
--ion-color-light-shade: #d7d8da;
|
||||||
|
--ion-color-light-tint: #f5f6f9;
|
||||||
|
|
||||||
|
--ion-color-main-light: #6439e4;
|
||||||
|
--ion-color-main-light-contrast: #ffffff;
|
||||||
|
--ion-color-main-light-shade: rgb(129, 121, 155);
|
||||||
|
--ion-color-main-light-tint: rgb(70, 61, 97);
|
||||||
|
|
||||||
|
--ion-color-main-color: #4b1cd8;
|
||||||
|
--ion-color-main-color-contrast: #ffffff;
|
||||||
|
--ion-color-main-color-shade: rgb(60, 35, 143);
|
||||||
|
--ion-color-main-color-tint: rgb(35, 23, 66);
|
||||||
|
|
||||||
|
/* --ion-background-color: #464646;
|
||||||
|
--ion-background-color-rgb: 70,70,70; */
|
||||||
|
|
||||||
|
--ion-toolbar-background: var(--ion-color-main-color);
|
||||||
|
--ion-toolbar-color: white;
|
||||||
|
--ion-tab-bar-background: var(--ion-color-main-color);
|
||||||
|
--ion-tab-bar-color: rgb(103, 101, 231);
|
||||||
|
--ion-tab-bar-color-selected: rgb(255, 255, 255);
|
||||||
|
|
||||||
|
.ion-color-main-light {
|
||||||
|
--ion-color-base: var(--ion-color-main-light);
|
||||||
|
--ion-color-base-rgb: var(--ion-color-main-light-rgb);
|
||||||
|
--ion-color-contrast: var(--ion-color-main-light-contrast);
|
||||||
|
--ion-color-contrast-rgb: var(--ion-color-main-light-contrast-rgb);
|
||||||
|
--ion-color-shade: var(--ion-color-main-light-shade);
|
||||||
|
--ion-color-tint: var(--ion-color-main-light-tint);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ion-color-main {
|
||||||
|
--ion-color-base: var(--ion-color-main-color);
|
||||||
|
--ion-color-base-rgb: var(--ion-color-main-color-rgb);
|
||||||
|
--ion-color-contrast: var(--ion-color-main-color-contrast);
|
||||||
|
--ion-color-contrast-rgb: var(--ion-color-main-color-contrast-rgb);
|
||||||
|
--ion-color-shade: var(--ion-color-main-color-shade);
|
||||||
|
--ion-color-tint: var(--ion-color-main-color-tint);
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-theme {
|
||||||
|
position: absolute;
|
||||||
|
background-color: rgba(77, 77, 77, 0.8);
|
||||||
|
width: 95%;
|
||||||
|
height: 95%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-theme ion-icon {
|
||||||
|
color: white;
|
||||||
|
font-size: 5rem;
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user