Compare commits
11 Commits
c3f680aa22
...
develop/mo
Author | SHA1 | Date | |
---|---|---|---|
![]() |
4303704753 | ||
![]() |
4bb213ef0c | ||
![]() |
657c652657 | ||
![]() |
5be77aae23 | ||
![]() |
ffbe63e421 | ||
![]() |
ba1e718039 | ||
![]() |
8b32d153db | ||
![]() |
d3e554b218 | ||
![]() |
5b10977a64 | ||
![]() |
a40b0fa4b1 | ||
![]() |
a4692a7d1f |
@@ -14,9 +14,9 @@ import {
|
||||
|
||||
import { Geolocation } from '@capacitor/geolocation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { SkeletonDashboard } from '../components/SkeletonDashboard';
|
||||
import { SkeletonDashboard } from '../TestComponents/SkeletonDashboard';
|
||||
import { chevronBackOutline, refreshOutline } from 'ionicons/icons';
|
||||
import { CurrentWeather } from '../components/CurrentWeather';
|
||||
import { CurrentWeather } from '../TestComponents/CurrentWeather';
|
||||
|
||||
function Tab1() {
|
||||
const router = useIonRouter();
|
||||
|
@@ -10,7 +10,7 @@ import {
|
||||
IonToolbar,
|
||||
} from '@ionic/react';
|
||||
import { useState } from 'react';
|
||||
import { CurrentWeather } from '../components/CurrentWeather';
|
||||
import { CurrentWeather } from '../TestComponents/CurrentWeather';
|
||||
|
||||
function Tab2() {
|
||||
const [search, setSearch] = useState('');
|
||||
|
@@ -0,0 +1,5 @@
|
||||
.keypad {
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
118
03_source/mobile/src/pages/Demo2FaExample/components/Keypad.tsx
Normal file
118
03_source/mobile/src/pages/Demo2FaExample/components/Keypad.tsx
Normal file
@@ -0,0 +1,118 @@
|
||||
import { IonRow } from "@ionic/react";
|
||||
import styles from "./Keypad.module.scss";
|
||||
import KeypadButton from "./KeypadButton";
|
||||
|
||||
const Keypad = (props: any): JSX.Element => {
|
||||
const { activeIndex, handleClick, handleRemove, amount, correct } = props;
|
||||
|
||||
const keypadButtons = [
|
||||
[
|
||||
{
|
||||
value: "1",
|
||||
handleClick: () => handleClick(activeIndex, 1),
|
||||
small: false,
|
||||
remove: false,
|
||||
},
|
||||
{
|
||||
value: "2",
|
||||
handleClick: () => handleClick(activeIndex, 2),
|
||||
small: false,
|
||||
remove: false,
|
||||
},
|
||||
{
|
||||
value: "3",
|
||||
handleClick: () => handleClick(activeIndex, 3),
|
||||
small: false,
|
||||
remove: false,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
value: "4",
|
||||
handleClick: () => handleClick(activeIndex, 4),
|
||||
small: false,
|
||||
remove: false,
|
||||
},
|
||||
{
|
||||
value: "5",
|
||||
handleClick: () => handleClick(activeIndex, 5),
|
||||
small: false,
|
||||
remove: false,
|
||||
},
|
||||
{
|
||||
value: "6",
|
||||
handleClick: () => handleClick(activeIndex, 6),
|
||||
small: false,
|
||||
remove: false,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
value: "7",
|
||||
handleClick: () => handleClick(activeIndex, 7),
|
||||
small: false,
|
||||
remove: false,
|
||||
},
|
||||
{
|
||||
value: "8",
|
||||
handleClick: () => handleClick(activeIndex, 8),
|
||||
small: false,
|
||||
remove: false,
|
||||
},
|
||||
{
|
||||
value: "9",
|
||||
handleClick: () => handleClick(activeIndex, 9),
|
||||
small: false,
|
||||
remove: false,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
value: "Resend",
|
||||
handleClick: () => handleClick(activeIndex, 1),
|
||||
small: true,
|
||||
remove: false,
|
||||
},
|
||||
{
|
||||
value: "0",
|
||||
handleClick: () => handleClick(activeIndex, 2),
|
||||
small: false,
|
||||
remove: false,
|
||||
},
|
||||
{
|
||||
value: "",
|
||||
handleClick: () => handleRemove(),
|
||||
small: true,
|
||||
remove: true,
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
return (
|
||||
<div className={`${styles.keypad}`}>
|
||||
{keypadButtons.map((keypadRow, index) => {
|
||||
const isDisabled = parseInt(activeIndex) === parseInt(amount);
|
||||
|
||||
return (
|
||||
<IonRow key={`keypadRow_${index}`}>
|
||||
{keypadRow.map((button, index2) => {
|
||||
return (
|
||||
<KeypadButton
|
||||
correct={correct}
|
||||
isDisabled={isDisabled}
|
||||
key={`keypadButton_${index2}`}
|
||||
value={button.value}
|
||||
handleClick={button.handleClick}
|
||||
small={button.small}
|
||||
remove={button.remove}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</IonRow>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Keypad;
|
@@ -0,0 +1,30 @@
|
||||
.logo {
|
||||
height: 4rem;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.keypadButton {
|
||||
--background: none;
|
||||
--color: black;
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
--outline: none;
|
||||
--border: none;
|
||||
--box-shadow: none;
|
||||
padding: none;
|
||||
margin: none;
|
||||
--background-hover: rgb(245, 245, 245) !important;
|
||||
--background-focused: rgb(245, 245, 245) !important;
|
||||
--background-activated: rgb(245, 245, 245) !important;
|
||||
}
|
||||
|
||||
.smallKeypadButton {
|
||||
font-size: 1.4rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.keypad {
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
import { IonButton, IonCol, IonIcon } from "@ionic/react";
|
||||
import { backspaceOutline } from "ionicons/icons";
|
||||
import styles from "./KeypadButton.module.scss";
|
||||
|
||||
const KeypadButton = (props: any): JSX.Element => {
|
||||
const {
|
||||
small,
|
||||
value,
|
||||
remove,
|
||||
handleClick,
|
||||
isDisabled = false,
|
||||
correct,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<IonCol size="4" className={styles.keypadButton}>
|
||||
<IonButton
|
||||
disabled={(!small || correct) && isDisabled}
|
||||
className={`${styles.keypadButton} ${
|
||||
small && styles.smallKeypadButton
|
||||
}`}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{!remove && value}
|
||||
{remove && <IonIcon icon={backspaceOutline} />}
|
||||
</IonButton>
|
||||
</IonCol>
|
||||
);
|
||||
};
|
||||
|
||||
export default KeypadButton;
|
@@ -0,0 +1,38 @@
|
||||
.keypadInput {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
font-size: 2.5rem;
|
||||
width: 100%;
|
||||
min-height: 2.5rem;
|
||||
background-color: rgb(245, 245, 245);
|
||||
border-radius: 4px;
|
||||
color: rgb(207, 207, 207);
|
||||
transition: 0.2s linear;
|
||||
}
|
||||
|
||||
.active {
|
||||
background-color: rgba(26, 150, 251, 0.2);
|
||||
border: 0 !important;
|
||||
color: white !important;
|
||||
transition: 0.2s linear;
|
||||
}
|
||||
|
||||
.filled {
|
||||
color: rgb(151, 151, 151);
|
||||
transition: 0.2s linear;
|
||||
}
|
||||
|
||||
.incorrect {
|
||||
background-color: rgba(251, 26, 26, 0.2);
|
||||
color: rgb(218, 67, 67);
|
||||
transition: 0.2s linear;
|
||||
}
|
||||
|
||||
.correct {
|
||||
background-color: rgba(26, 251, 120, 0.2);
|
||||
color: rgb(67, 218, 112);
|
||||
transition: 0.2s linear;
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
import { IonCol } from "@ionic/react";
|
||||
import styles from "./KeypadInput.module.scss";
|
||||
|
||||
const KeypadInput = (props: any): JSX.Element => {
|
||||
const {
|
||||
value,
|
||||
isActive = false,
|
||||
isFilled = false,
|
||||
incorrect,
|
||||
correct,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<IonCol size="2">
|
||||
<div
|
||||
className={`${styles.keypadInput} ${isActive && styles.active} ${
|
||||
isFilled && styles.filled
|
||||
} ${incorrect && styles.incorrect} ${correct && styles.correct}`}
|
||||
onClick={props.handleChange}
|
||||
>
|
||||
{value}
|
||||
{!isFilled && "0"}
|
||||
</div>
|
||||
</IonCol>
|
||||
);
|
||||
};
|
||||
|
||||
export default KeypadInput;
|
@@ -0,0 +1,50 @@
|
||||
import { IonRow } from "@ionic/react";
|
||||
import { useEffect, useRef } from "react";
|
||||
import KeypadInput from "./KeypadInput";
|
||||
|
||||
const KeypadInputs = (props: any): JSX.Element => {
|
||||
const { values, activeIndex, incorrect, correct } = props;
|
||||
const keypadRef = useRef<HTMLIonRowElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (incorrect && keypadRef.current) {
|
||||
keypadRef.current.classList.add("incorrect");
|
||||
|
||||
setTimeout(() => {
|
||||
if (keypadRef.current) {
|
||||
keypadRef.current.classList.remove("incorrect");
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
}, [incorrect]);
|
||||
|
||||
useEffect(() => {
|
||||
if (correct) {
|
||||
if (keypadRef.current) {
|
||||
keypadRef.current.classList.add("correct");
|
||||
}
|
||||
}
|
||||
}, [correct]);
|
||||
|
||||
return (
|
||||
<IonRow ref={keypadRef} className="ion-justify-content-center">
|
||||
{values.map((value: string, index: number) => {
|
||||
const isActive = parseInt(index.toString()) === parseInt(activeIndex);
|
||||
const isFilled = value !== "" ? true : false;
|
||||
|
||||
return (
|
||||
<KeypadInput
|
||||
correct={correct}
|
||||
incorrect={incorrect}
|
||||
isFilled={isFilled}
|
||||
isActive={isActive}
|
||||
value={value}
|
||||
placeholder="0"
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</IonRow>
|
||||
);
|
||||
};
|
||||
|
||||
export default KeypadInputs;
|
@@ -1,38 +1,19 @@
|
||||
import { IonIcon, IonLabel, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs } from '@ionic/react';
|
||||
import { IonRouterOutlet, IonTabs } from '@ionic/react';
|
||||
|
||||
import { cloudOutline, searchOutline } from 'ionicons/icons';
|
||||
import { Route, Redirect } from 'react-router';
|
||||
|
||||
import Tab1 from './AppPages/Tab1';
|
||||
import Tab2 from './AppPages/Tab2';
|
||||
|
||||
import './style.scss';
|
||||
import Home from './pages/Home';
|
||||
|
||||
function Demo2FaExample() {
|
||||
return (
|
||||
<IonTabs>
|
||||
<IonTabs className="demo-2fa-example">
|
||||
<IonRouterOutlet>
|
||||
<Route exact path="/demo-weather-app/tab1">
|
||||
<Tab1 />
|
||||
</Route>
|
||||
<Route exact path="/demo-weather-app/tab2">
|
||||
<Tab2 />
|
||||
<Route exact path="/demo-2fa-example/home">
|
||||
<Home />
|
||||
</Route>
|
||||
|
||||
<Redirect exact path="/demo-weather-app" to="/demo-weather-app/tab1" />
|
||||
<Redirect exact path="/demo-2fa-example" to="/demo-2fa-example/home" />
|
||||
</IonRouterOutlet>
|
||||
|
||||
{/* */}
|
||||
<IonTabBar slot="bottom">
|
||||
<IonTabButton tab="tab1" href="/demo-weather-app/tab1">
|
||||
<IonIcon icon={cloudOutline} />
|
||||
<IonLabel>Dashboard</IonLabel>
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="tab2" href="/demo-weather-app/tab2">
|
||||
<IonIcon icon={searchOutline} />
|
||||
<IonLabel>Search</IonLabel>
|
||||
</IonTabButton>
|
||||
</IonTabBar>
|
||||
</IonTabs>
|
||||
);
|
||||
}
|
||||
|
@@ -0,0 +1,30 @@
|
||||
.logo {
|
||||
height: 4rem;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.incorrect {
|
||||
color: rgb(218, 67, 67);
|
||||
}
|
||||
|
||||
.successContainer {
|
||||
}
|
||||
|
||||
.successText {
|
||||
background-color: rgba(26, 251, 120, 0.2);
|
||||
color: rgb(67, 218, 112);
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.successContinue {
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.successContinue ion-icon {
|
||||
margin-top: 0.2rem;
|
||||
}
|
135
03_source/mobile/src/pages/Demo2FaExample/pages/Home.tsx
Normal file
135
03_source/mobile/src/pages/Demo2FaExample/pages/Home.tsx
Normal file
@@ -0,0 +1,135 @@
|
||||
import {
|
||||
IonButton,
|
||||
IonButtons,
|
||||
IonCol,
|
||||
IonContent,
|
||||
IonGrid,
|
||||
IonHeader,
|
||||
IonIcon,
|
||||
IonImg,
|
||||
IonPage,
|
||||
IonRow,
|
||||
IonToolbar,
|
||||
useIonRouter,
|
||||
} from '@ionic/react';
|
||||
import { arrowForwardOutline, chevronBackOutline } from 'ionicons/icons';
|
||||
import styles from './Home.module.scss';
|
||||
import KeypadInputs from '../components/KeypadInputs';
|
||||
import Keypad from '../components/Keypad';
|
||||
import { JSX, useEffect, useRef, useState } from 'react';
|
||||
|
||||
const Home = (): JSX.Element => {
|
||||
const correctCode = [5, 9, 2, 5];
|
||||
const [keypadValues, setKeypadValues] = useState(['', '', '', '']);
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
const successRef = useRef<HTMLIonRowElement>(null);
|
||||
|
||||
const [incorrect, setIncorrect] = useState(false);
|
||||
const [correct, setCorrect] = useState(false);
|
||||
|
||||
const tempValues: { [key: string]: any } = {};
|
||||
|
||||
const handleClick = (index: number, value: any) => {
|
||||
const stringKey = index.toString();
|
||||
tempValues[stringKey] = value;
|
||||
|
||||
setKeypadValues(value);
|
||||
setActiveIndex((activeIndex) => activeIndex + 1);
|
||||
};
|
||||
|
||||
const handleRemove = () => {
|
||||
const tempValues = [...keypadValues];
|
||||
tempValues[activeIndex - 1] = '';
|
||||
|
||||
setKeypadValues(tempValues);
|
||||
activeIndex > 0 && setActiveIndex((activeIndex) => activeIndex - 1);
|
||||
setIncorrect(false);
|
||||
setCorrect(false);
|
||||
};
|
||||
|
||||
const router = useIonRouter();
|
||||
function handleBackClick() {
|
||||
router.goBack();
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (parseInt(activeIndex.toString()) === parseInt(keypadValues.length.toString())) {
|
||||
var error = false;
|
||||
|
||||
keypadValues.forEach((value, index) => {
|
||||
if (parseInt(value) !== parseInt(correctCode[index].toString())) {
|
||||
error = true;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (error) {
|
||||
setIncorrect(true);
|
||||
} else {
|
||||
setCorrect(true);
|
||||
|
||||
setTimeout(() => {
|
||||
if (successRef.current) {
|
||||
successRef.current.classList.remove('hidden');
|
||||
successRef.current.classList.add('success');
|
||||
}
|
||||
}, 900);
|
||||
}
|
||||
}
|
||||
}, [activeIndex]);
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonToolbar>
|
||||
<IonHeader></IonHeader>
|
||||
|
||||
<IonButtons slot="start">
|
||||
<IonButton shape={'round'} onClick={() => handleBackClick()}>
|
||||
<IonIcon icon={chevronBackOutline} color="primary" />
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
<IonContent fullscreen>
|
||||
<IonGrid className="ion-text-center ion-padding-top">
|
||||
<IonRow>
|
||||
<IonCol size="12">
|
||||
<IonImg src="/assets/icon/favicon.png" className={styles.logo} />
|
||||
<h1>Verification required</h1>
|
||||
<p>Enter your 4 digit verification code</p>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<IonRow ref={successRef} className="ion-justify-content-center hidden">
|
||||
<IonCol size="12" className={styles.successContainer}>
|
||||
<p className={styles.successText}>
|
||||
Awesome! You may continue.
|
||||
<br />
|
||||
<span className={styles.successContinue}>
|
||||
Continue
|
||||
<IonIcon icon={arrowForwardOutline} />
|
||||
</span>
|
||||
</p>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<KeypadInputs
|
||||
incorrect={incorrect}
|
||||
correct={correct}
|
||||
values={keypadValues}
|
||||
activeIndex={activeIndex}
|
||||
/>
|
||||
{incorrect && <p className={styles.incorrect}>Wrong code entered</p>}
|
||||
<Keypad
|
||||
handleRemove={handleRemove}
|
||||
handleClick={handleClick}
|
||||
activeIndex={activeIndex}
|
||||
amount={keypadValues.length}
|
||||
correct={correct}
|
||||
/>
|
||||
</IonGrid>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
@@ -1,103 +0,0 @@
|
||||
#about-page {
|
||||
ion-toolbar {
|
||||
position: absolute;
|
||||
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
--background: transparent;
|
||||
--color: white;
|
||||
}
|
||||
|
||||
ion-toolbar ion-back-button,
|
||||
ion-toolbar ion-button,
|
||||
ion-toolbar ion-menu-button {
|
||||
--color: white;
|
||||
}
|
||||
|
||||
.about-header {
|
||||
position: relative;
|
||||
|
||||
width: 100%;
|
||||
height: 30%;
|
||||
}
|
||||
|
||||
.about-header .about-image {
|
||||
position: absolute;
|
||||
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
opacity: 0;
|
||||
|
||||
transition: opacity 500ms ease-in-out;
|
||||
}
|
||||
|
||||
.about-header .madison {
|
||||
background-image: url('/assets/WeatherDemo/img/about/madison.jpg');
|
||||
}
|
||||
|
||||
.about-header .austin {
|
||||
background-image: url('/assets/WeatherDemo/img/about/austin.jpg');
|
||||
}
|
||||
|
||||
.about-header .chicago {
|
||||
background-image: url('/assets/WeatherDemo/img/about/chicago.jpg');
|
||||
}
|
||||
|
||||
.about-header .seattle {
|
||||
background-image: url('/assets/WeatherDemo/img/about/seattle.jpg');
|
||||
}
|
||||
|
||||
.about-info {
|
||||
position: relative;
|
||||
margin-top: -10px;
|
||||
border-radius: 10px;
|
||||
background: var(--ion-background-color, #fff);
|
||||
z-index: 2; // display rounded border above header image
|
||||
}
|
||||
|
||||
.about-info h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.about-info ion-list {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.about-info p {
|
||||
line-height: 130%;
|
||||
|
||||
color: var(--ion-color-dark);
|
||||
}
|
||||
|
||||
.about-info ion-icon {
|
||||
margin-inline-end: 32px;
|
||||
}
|
||||
|
||||
/*
|
||||
* iOS Only
|
||||
*/
|
||||
|
||||
.ios .about-info {
|
||||
--ion-padding: 19px;
|
||||
}
|
||||
|
||||
.ios .about-info h3 {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
#date-input-popover {
|
||||
--offset-y: -var(--ion-safe-area-bottom);
|
||||
|
||||
--max-width: 90%;
|
||||
--width: 336px;
|
||||
}
|
249
03_source/mobile/src/pages/Demo2FaExample/theme/variables.scss
Normal file
249
03_source/mobile/src/pages/Demo2FaExample/theme/variables.scss
Normal file
@@ -0,0 +1,249 @@
|
||||
.demo-2fa-example {
|
||||
/* Ionic Variables and Theming. For more info, please see:
|
||||
http://ionicframework.com/docs/theming/ */
|
||||
|
||||
* {
|
||||
font-family: 'Lato', sans-serif;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
transition: 0.2s linear;
|
||||
}
|
||||
|
||||
/** Ionic CSS Variables **/
|
||||
:root {
|
||||
/** 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;
|
||||
}
|
||||
|
||||
.incorrect {
|
||||
-webkit-animation: incorrect-animation 0.9s both;
|
||||
animation: incorrect-animation 0.9s both;
|
||||
}
|
||||
|
||||
@-webkit-keyframes incorrect-animation {
|
||||
0% {
|
||||
-webkit-transform: scale3d(1, 1, 1);
|
||||
transform: scale3d(1, 1, 1);
|
||||
}
|
||||
|
||||
30% {
|
||||
-webkit-transform: scale3d(1.25, 0.75, 1);
|
||||
transform: scale3d(1.25, 0.75, 1);
|
||||
}
|
||||
|
||||
40% {
|
||||
-webkit-transform: scale3d(0.75, 1.25, 1);
|
||||
transform: scale3d(0.75, 1.25, 1);
|
||||
}
|
||||
|
||||
50% {
|
||||
-webkit-transform: scale3d(1.15, 0.85, 1);
|
||||
transform: scale3d(1.15, 0.85, 1);
|
||||
}
|
||||
|
||||
65% {
|
||||
-webkit-transform: scale3d(0.95, 1.05, 1);
|
||||
transform: scale3d(0.95, 1.05, 1);
|
||||
}
|
||||
|
||||
75% {
|
||||
-webkit-transform: scale3d(1.05, 0.95, 1);
|
||||
transform: scale3d(1.05, 0.95, 1);
|
||||
}
|
||||
|
||||
100% {
|
||||
-webkit-transform: scale3d(1, 1, 1);
|
||||
transform: scale3d(1, 1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes incorrect-animation {
|
||||
0% {
|
||||
-webkit-transform: scale3d(1, 1, 1);
|
||||
transform: scale3d(1, 1, 1);
|
||||
}
|
||||
|
||||
30% {
|
||||
-webkit-transform: scale3d(1.25, 0.75, 1);
|
||||
transform: scale3d(1.25, 0.75, 1);
|
||||
}
|
||||
|
||||
40% {
|
||||
-webkit-transform: scale3d(0.75, 1.25, 1);
|
||||
transform: scale3d(0.75, 1.25, 1);
|
||||
}
|
||||
|
||||
50% {
|
||||
-webkit-transform: scale3d(1.15, 0.85, 1);
|
||||
transform: scale3d(1.15, 0.85, 1);
|
||||
}
|
||||
|
||||
65% {
|
||||
-webkit-transform: scale3d(0.95, 1.05, 1);
|
||||
transform: scale3d(0.95, 1.05, 1);
|
||||
}
|
||||
|
||||
75% {
|
||||
-webkit-transform: scale3d(1.05, 0.95, 1);
|
||||
transform: scale3d(1.05, 0.95, 1);
|
||||
}
|
||||
|
||||
100% {
|
||||
-webkit-transform: scale3d(1, 1, 1);
|
||||
transform: scale3d(1, 1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.correct {
|
||||
-webkit-animation: correct-animation 1s ease-in both;
|
||||
animation: correct-animation 1s ease-in both;
|
||||
}
|
||||
|
||||
@-webkit-keyframes correct-animation {
|
||||
0% {
|
||||
-webkit-transform: translateY(0) rotateX(0) scale(1);
|
||||
transform: translateY(0) rotateX(0) scale(1);
|
||||
-webkit-transform-origin: 50% 1400px;
|
||||
transform-origin: 50% 1400px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
-webkit-transform: translateY(-600px) rotateX(-30deg) scale(0);
|
||||
transform: translateY(-600px) rotateX(-30deg) scale(0);
|
||||
-webkit-transform-origin: 50% 100%;
|
||||
transform-origin: 50% 100%;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes correct-animation {
|
||||
0% {
|
||||
-webkit-transform: translateY(0) rotateX(0) scale(1);
|
||||
transform: translateY(0) rotateX(0) scale(1);
|
||||
-webkit-transform-origin: 50% 1400px;
|
||||
transform-origin: 50% 1400px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
-webkit-transform: translateY(-600px) rotateX(-30deg) scale(0);
|
||||
transform: translateY(-600px) rotateX(-30deg) scale(0);
|
||||
-webkit-transform-origin: 50% 100%;
|
||||
transform-origin: 50% 100%;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.success {
|
||||
-webkit-animation: success-animation 0.7s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
|
||||
animation: success-animation 0.7s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
|
||||
}
|
||||
|
||||
@-webkit-keyframes success-animation {
|
||||
0% {
|
||||
-webkit-transform: translateY(-600px) rotateX(-30deg) scale(0);
|
||||
transform: translateY(-600px) rotateX(-30deg) scale(0);
|
||||
-webkit-transform-origin: 50% 100%;
|
||||
transform-origin: 50% 100%;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
-webkit-transform: translateY(0) rotateX(0) scale(1);
|
||||
transform: translateY(0) rotateX(0) scale(1);
|
||||
-webkit-transform-origin: 50% 1400px;
|
||||
transform-origin: 50% 1400px;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes success-animation {
|
||||
0% {
|
||||
-webkit-transform: translateY(-600px) rotateX(-30deg) scale(0);
|
||||
transform: translateY(-600px) rotateX(-30deg) scale(0);
|
||||
-webkit-transform-origin: 50% 100%;
|
||||
transform-origin: 50% 100%;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
-webkit-transform: translateY(0) rotateX(0) scale(1);
|
||||
transform: translateY(0) rotateX(0) scale(1);
|
||||
-webkit-transform-origin: 50% 1400px;
|
||||
transform-origin: 50% 1400px;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
@@ -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 { cloudOutline, searchOutline } from 'ionicons/icons';
|
||||
import {
|
||||
brushOutline,
|
||||
cloudOutline,
|
||||
handLeftOutline,
|
||||
informationCircle,
|
||||
searchOutline,
|
||||
} from 'ionicons/icons';
|
||||
|
||||
import { Route, Redirect } from 'react-router';
|
||||
|
||||
import Tab1 from './AppPages/Tab1';
|
||||
import Tab2 from './AppPages/Tab2';
|
||||
import './theme/variables.scss';
|
||||
|
||||
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() {
|
||||
const theme = ThemeStore.useState((s) => s.currentTheme);
|
||||
|
||||
return (
|
||||
<IonTabs>
|
||||
<IonRouterOutlet>
|
||||
<Route exact path="/demo-react-theme-switcher/tab1">
|
||||
<Tab1 />
|
||||
</Route>
|
||||
<Route exact path="/demo-react-theme-switcher/tab2">
|
||||
<Tab2 />
|
||||
</Route>
|
||||
<div className="helloworld" style={theme ? theme : {}}>
|
||||
<IonTabs>
|
||||
<IonRouterOutlet>
|
||||
<Route exact path="/demo-react-theme-switcher/info">
|
||||
<Info />
|
||||
</Route>
|
||||
<Route exact path="/demo-react-theme-switcher/themes">
|
||||
<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" />
|
||||
</IonRouterOutlet>
|
||||
<Redirect exact path="/demo-react-theme-switcher" to="/demo-react-theme-switcher/info" />
|
||||
</IonRouterOutlet>
|
||||
|
||||
{/* */}
|
||||
<IonTabBar slot="bottom">
|
||||
<IonTabButton tab="tab1" href="/demo-react-theme-switcher/tab1">
|
||||
<IonIcon icon={cloudOutline} />
|
||||
<IonLabel>Dashboard</IonLabel>
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="tab2" href="/demo-react-theme-switcher/tab2">
|
||||
<IonIcon icon={searchOutline} />
|
||||
<IonLabel>Search</IonLabel>
|
||||
</IonTabButton>
|
||||
</IonTabBar>
|
||||
</IonTabs>
|
||||
<IonTabBar slot="bottom">
|
||||
<IonTabButton tab="tab1" href="/demo-react-theme-switcher/info">
|
||||
<IonIcon icon={informationCircle} />
|
||||
<IonLabel>Info</IonLabel>
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="tab2" href="/demo-react-theme-switcher/themes">
|
||||
<IonIcon icon={brushOutline} />
|
||||
<IonLabel>Themes</IonLabel>
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="tab3" href="/demo-react-theme-switcher/examples">
|
||||
<IonIcon icon={handLeftOutline} />
|
||||
<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,97 @@
|
||||
import {
|
||||
IonButton,
|
||||
IonButtons,
|
||||
IonCardSubtitle,
|
||||
IonCardTitle,
|
||||
IonCol,
|
||||
IonContent,
|
||||
IonGrid,
|
||||
IonHeader,
|
||||
IonIcon,
|
||||
IonPage,
|
||||
IonRow,
|
||||
IonText,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
useIonRouter,
|
||||
} from '@ionic/react';
|
||||
import { useGetSelectedTheme } from '../store/ThemeStore';
|
||||
import './Info.scss';
|
||||
import React from 'react';
|
||||
import { chevronBackOutline } from 'ionicons/icons';
|
||||
|
||||
function Info(): React.JSX.Element {
|
||||
const currentTheme = useGetSelectedTheme();
|
||||
|
||||
const router = useIonRouter();
|
||||
function handleBackClick() {
|
||||
router.goBack();
|
||||
}
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Info TS</IonTitle>
|
||||
<IonButtons slot="start">
|
||||
<IonButton shape="round" onClick={handleBackClick}>
|
||||
<IonIcon icon={chevronBackOutline}></IonIcon>
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
</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;
|
||||
}
|
||||
}
|
@@ -14,9 +14,9 @@ import {
|
||||
|
||||
import { Geolocation } from '@capacitor/geolocation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { SkeletonDashboard } from '../components/SkeletonDashboard';
|
||||
import { SkeletonDashboard } from '../TestComponents/SkeletonDashboard';
|
||||
import { chevronBackOutline, refreshOutline } from 'ionicons/icons';
|
||||
import { CurrentWeather } from '../components/CurrentWeather';
|
||||
import { CurrentWeather } from '../TestComponents/CurrentWeather';
|
||||
|
||||
function Tab1() {
|
||||
const router = useIonRouter();
|
||||
|
@@ -10,7 +10,7 @@ import {
|
||||
IonToolbar,
|
||||
} from '@ionic/react';
|
||||
import { useState } from 'react';
|
||||
import { CurrentWeather } from '../components/CurrentWeather';
|
||||
import { CurrentWeather } from '../TestComponents/CurrentWeather';
|
||||
|
||||
function Tab2() {
|
||||
const [search, setSearch] = useState('');
|
||||
|
@@ -0,0 +1,29 @@
|
||||
import { IonImg, IonItem, IonLabel, IonList, IonLoading, IonThumbnail } from '@ionic/react';
|
||||
import { SkeletonPosts } from './SkeletonPosts';
|
||||
|
||||
export const Posts = ({ posts, useSkeleton }): React.JSX.Element => (
|
||||
<>
|
||||
{posts.length > 0 ? (
|
||||
<IonList>
|
||||
{posts.map((post, index) => {
|
||||
return (
|
||||
<IonItem key={index}>
|
||||
<IonThumbnail slot="start">
|
||||
<IonImg src={post.image} />
|
||||
</IonThumbnail>
|
||||
<IonLabel className="">
|
||||
<h3>{post.title}</h3>
|
||||
<p>{post.blurb}</p>
|
||||
<p>{post.date}</p>
|
||||
</IonLabel>
|
||||
</IonItem>
|
||||
);
|
||||
})}
|
||||
</IonList>
|
||||
) : useSkeleton ? (
|
||||
<SkeletonPosts />
|
||||
) : (
|
||||
<IonLoading isOpen={true} spinner="bubbles" message="Loading posts..." />
|
||||
)}
|
||||
</>
|
||||
);
|
@@ -0,0 +1,36 @@
|
||||
import {
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonList,
|
||||
IonSkeletonText,
|
||||
IonThumbnail,
|
||||
} from "@ionic/react";
|
||||
|
||||
export const SkeletonPosts = (): JSX.Element => {
|
||||
const postAmount = 10;
|
||||
|
||||
return (
|
||||
<IonList>
|
||||
{[...Array(postAmount)].map((post, index) => {
|
||||
return (
|
||||
<IonItem key={index}>
|
||||
<IonThumbnail slot="start">
|
||||
<IonSkeletonText animated />
|
||||
</IonThumbnail>
|
||||
<IonLabel>
|
||||
<h3>
|
||||
<IonSkeletonText animated style={{ width: "50%" }} />
|
||||
</h3>
|
||||
<p>
|
||||
<IonSkeletonText animated style={{ width: "100%" }} />
|
||||
</p>
|
||||
<p>
|
||||
<IonSkeletonText animated style={{ width: "30%" }} />
|
||||
</p>
|
||||
</IonLabel>
|
||||
</IonItem>
|
||||
);
|
||||
})}
|
||||
</IonList>
|
||||
);
|
||||
};
|
72
03_source/mobile/src/pages/DemoSkeletonText/data/index.ts
Normal file
72
03_source/mobile/src/pages/DemoSkeletonText/data/index.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
export const fakePosts = [
|
||||
{
|
||||
title: 'Sed ut perspiciatis unde',
|
||||
blurb:
|
||||
'Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.',
|
||||
image: '/assets/DemoSkeletonText/scenery/1.png',
|
||||
date: '01/04/2021',
|
||||
},
|
||||
{
|
||||
title: 'But I must explain to you',
|
||||
blurb:
|
||||
'But I must explain to you how all this mistaken idea of denouncing pleasure and praising pain was born and I will give you a complete account of the system, and expound the actual teachings of the great explorer of the truth',
|
||||
image: '/assets/DemoSkeletonText/scenery/2.png',
|
||||
date: '23/02/2021',
|
||||
},
|
||||
{
|
||||
title: 'Far far away, behind the word',
|
||||
blurb:
|
||||
'Far far away, behind the word mountains, far from the countries Vokalia and Consonantia, there live the blind texts. Separated they live in Bookmarksgrove right at the coast of the Semantics, a large language ocean.',
|
||||
image: '/assets/DemoSkeletonText/scenery/3.png',
|
||||
date: '18/02/2021',
|
||||
},
|
||||
{
|
||||
title: 'A wonderful serenity',
|
||||
blurb:
|
||||
'A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot.',
|
||||
image: '/assets/DemoSkeletonText/scenery/4.png',
|
||||
date: '09/02/2021',
|
||||
},
|
||||
{
|
||||
title: 'Morning troubled dreams',
|
||||
blurb:
|
||||
'One morning, when Gregor Samsa woke from troubled dreams, he found himself transformed in his bed into a horrible vermin. He lay on his armour-like back, and if he lifted his head.',
|
||||
image: '/assets/DemoSkeletonText/scenery/5.png',
|
||||
date: '01/02/2021',
|
||||
},
|
||||
{
|
||||
title: 'The quick brown fox',
|
||||
blurb:
|
||||
'The quick, brown fox jumps over a lazy dog. DJs flock by when MTV ax quiz prog. Junk MTV quiz graced by fox whelps. Bawds jog, flick quartz, vex nymphs.',
|
||||
image: '/assets/DemoSkeletonText/scenery/6.png',
|
||||
date: '14/01/2021',
|
||||
},
|
||||
{
|
||||
title: 'Lorem ipsum dolor',
|
||||
blurb:
|
||||
'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes.',
|
||||
image: '/assets/DemoSkeletonText/scenery/7.png',
|
||||
date: '04/01/2021',
|
||||
},
|
||||
{
|
||||
title: 'Lorem ipsum dolor',
|
||||
blurb:
|
||||
'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes.',
|
||||
image: '/assets/DemoSkeletonText/scenery/8.png',
|
||||
date: '04/01/2021',
|
||||
},
|
||||
{
|
||||
title: 'Lorem ipsum dolor',
|
||||
blurb:
|
||||
'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes.',
|
||||
image: '/assets/DemoSkeletonText/scenery/9.png',
|
||||
date: '04/01/2021',
|
||||
},
|
||||
{
|
||||
title: 'Lorem ipsum dolor',
|
||||
blurb:
|
||||
'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes.',
|
||||
image: '/assets/DemoSkeletonText/scenery/10.png',
|
||||
date: '04/01/2021',
|
||||
},
|
||||
];
|
@@ -3,36 +3,20 @@ import { IonIcon, IonLabel, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs }
|
||||
import { cloudOutline, searchOutline } from 'ionicons/icons';
|
||||
import { Route, Redirect } from 'react-router';
|
||||
|
||||
import Tab1 from './AppPages/Tab1';
|
||||
import Tab2 from './AppPages/Tab2';
|
||||
|
||||
import './style.scss';
|
||||
// import Tab1 from './AppPages/Tab1';
|
||||
// import Tab2 from './AppPages/Tab2';
|
||||
import Home from './pages/Home';
|
||||
|
||||
function DemoSkeletonText() {
|
||||
return (
|
||||
<IonTabs>
|
||||
<IonRouterOutlet>
|
||||
<Route exact path="/demo-weather-app/tab1">
|
||||
<Tab1 />
|
||||
</Route>
|
||||
<Route exact path="/demo-weather-app/tab2">
|
||||
<Tab2 />
|
||||
<Route exact path="/demo-skeleton-text/home">
|
||||
<Home />
|
||||
</Route>
|
||||
|
||||
<Redirect exact path="/demo-weather-app" to="/demo-weather-app/tab1" />
|
||||
<Redirect exact path="/demo-skeleton-text" to="/demo-skeleton-text/home" />
|
||||
</IonRouterOutlet>
|
||||
|
||||
{/* */}
|
||||
<IonTabBar slot="bottom">
|
||||
<IonTabButton tab="tab1" href="/demo-weather-app/tab1">
|
||||
<IonIcon icon={cloudOutline} />
|
||||
<IonLabel>Dashboard</IonLabel>
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="tab2" href="/demo-weather-app/tab2">
|
||||
<IonIcon icon={searchOutline} />
|
||||
<IonLabel>Search</IonLabel>
|
||||
</IonTabButton>
|
||||
</IonTabBar>
|
||||
</IonTabs>
|
||||
);
|
||||
}
|
||||
|
63
03_source/mobile/src/pages/DemoSkeletonText/pages/Home.tsx
Normal file
63
03_source/mobile/src/pages/DemoSkeletonText/pages/Home.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import {
|
||||
IonButton,
|
||||
IonButtons,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonIcon,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
useIonRouter,
|
||||
} from '@ionic/react';
|
||||
import './Home.scss';
|
||||
|
||||
import { fakePosts } from '../data';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Posts } from '../components/Posts';
|
||||
import { chevronBackOutline } from 'ionicons/icons';
|
||||
|
||||
const Home = (): React.JSX.Element => {
|
||||
const [posts, setPosts] = useState<typeof fakePosts>([]);
|
||||
|
||||
const router = useIonRouter();
|
||||
function handleBackClick() {
|
||||
router.goBack();
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => setPosts(fakePosts), 2000);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader className="ion-no-border">
|
||||
<IonToolbar>
|
||||
<IonTitle>Posts TS</IonTitle>
|
||||
|
||||
<IonButtons slot="start">
|
||||
<IonButton shape="round" onClick={() => handleBackClick()}>
|
||||
<IonIcon icon={chevronBackOutline} color="primary" />
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">Posts TS</IonTitle>
|
||||
|
||||
<IonButtons slot="start">
|
||||
<IonButton onClick={() => handleBackClick()}>
|
||||
<IonIcon icon={chevronBackOutline} color="primary" />
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<Posts posts={posts} useSkeleton={true} />
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
@@ -1,103 +0,0 @@
|
||||
#about-page {
|
||||
ion-toolbar {
|
||||
position: absolute;
|
||||
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
--background: transparent;
|
||||
--color: white;
|
||||
}
|
||||
|
||||
ion-toolbar ion-back-button,
|
||||
ion-toolbar ion-button,
|
||||
ion-toolbar ion-menu-button {
|
||||
--color: white;
|
||||
}
|
||||
|
||||
.about-header {
|
||||
position: relative;
|
||||
|
||||
width: 100%;
|
||||
height: 30%;
|
||||
}
|
||||
|
||||
.about-header .about-image {
|
||||
position: absolute;
|
||||
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
opacity: 0;
|
||||
|
||||
transition: opacity 500ms ease-in-out;
|
||||
}
|
||||
|
||||
.about-header .madison {
|
||||
background-image: url('/assets/WeatherDemo/img/about/madison.jpg');
|
||||
}
|
||||
|
||||
.about-header .austin {
|
||||
background-image: url('/assets/WeatherDemo/img/about/austin.jpg');
|
||||
}
|
||||
|
||||
.about-header .chicago {
|
||||
background-image: url('/assets/WeatherDemo/img/about/chicago.jpg');
|
||||
}
|
||||
|
||||
.about-header .seattle {
|
||||
background-image: url('/assets/WeatherDemo/img/about/seattle.jpg');
|
||||
}
|
||||
|
||||
.about-info {
|
||||
position: relative;
|
||||
margin-top: -10px;
|
||||
border-radius: 10px;
|
||||
background: var(--ion-background-color, #fff);
|
||||
z-index: 2; // display rounded border above header image
|
||||
}
|
||||
|
||||
.about-info h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.about-info ion-list {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.about-info p {
|
||||
line-height: 130%;
|
||||
|
||||
color: var(--ion-color-dark);
|
||||
}
|
||||
|
||||
.about-info ion-icon {
|
||||
margin-inline-end: 32px;
|
||||
}
|
||||
|
||||
/*
|
||||
* iOS Only
|
||||
*/
|
||||
|
||||
.ios .about-info {
|
||||
--ion-padding: 19px;
|
||||
}
|
||||
|
||||
.ios .about-info h3 {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
#date-input-popover {
|
||||
--offset-y: -var(--ion-safe-area-bottom);
|
||||
|
||||
--max-width: 90%;
|
||||
--width: 336px;
|
||||
}
|
@@ -0,0 +1,77 @@
|
||||
/* Ionic Variables and Theming. For more info, please see:
|
||||
http://ionicframework.com/docs/theming/ */
|
||||
|
||||
/** Ionic CSS Variables **/
|
||||
:root {
|
||||
/** 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;
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
import { IonCol, IonGrid, IonModal, IonRow, IonSearchbar } from "@ionic/react";
|
||||
import { OverlayEventDetail } from "@ionic/react/dist/types/components/react-component-lib/interfaces";
|
||||
import { useState } from "react";
|
||||
import { BottomSheetContent } from "./BottomSheetContent";
|
||||
import { DummyItem } from "./DummyItem";
|
||||
|
||||
interface BottomSheetProps {
|
||||
|
||||
isOpen: boolean,
|
||||
close: (event: CustomEvent<OverlayEventDetail<any>>) => void
|
||||
}
|
||||
|
||||
export const BottomSheet: React.FC<BottomSheetProps> = ({ isOpen, close }) => {
|
||||
|
||||
const amountOfDummyItems = 10;
|
||||
const [search, setSearch] = useState<string>("")
|
||||
|
||||
const handleChange = (e:any)=> {
|
||||
setSearch(e.target.value);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
<IonModal isOpen={isOpen} onDidDismiss={close} breakpoints={[0, 0.12, 0.5, 1]} initialBreakpoint={0.12} backdropBreakpoint={0.5}>
|
||||
<IonGrid className="ion-padding-top">
|
||||
<IonRow>
|
||||
<IonCol size="12">
|
||||
<IonSearchbar animated value={search} onIonChange={handleChange} placeholder="Search by item number" />
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
{/* Check the comments insside BottomSheetContent component */}
|
||||
{/* Regarding the scrolling of "content" inside a sheet modal */}
|
||||
<BottomSheetContent>
|
||||
{ [...Array(amountOfDummyItems)].map((e, i) => {
|
||||
|
||||
if (search.includes(i.toString()) || search === "") {
|
||||
return (
|
||||
<DummyItem key={i} number={i} />
|
||||
);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
})}
|
||||
</BottomSheetContent>
|
||||
</IonGrid>
|
||||
</IonModal>
|
||||
);
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
export const BottomSheetContent: React.FC = ({ children }) => {
|
||||
|
||||
return (
|
||||
|
||||
// Some work needed for inner content of a sheet-modal
|
||||
// Scroll is a bit buggy when sheet is full height
|
||||
// Scroll the content down, then try and scroll up
|
||||
|
||||
// Maybe some functionality needed on the Sheet Modal component to keep track of inner content scroll position and only interact with modal if scroll position of inner content is back to top OR if only interacting with the modal container (edges, top, etc)
|
||||
|
||||
// Applied this CSS just to make it work 50% of the way to test
|
||||
<div style={{
|
||||
overflow: "scroll",
|
||||
height: "100vh"
|
||||
}}>
|
||||
{ children }
|
||||
</div>
|
||||
);
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
import { IonCard, IonCardContent, IonLabel } from "@ionic/react";
|
||||
|
||||
interface DummyItemProps {
|
||||
|
||||
number: number,
|
||||
}
|
||||
|
||||
export const DummyItem: React.FC<DummyItemProps> = ({ number }) => {
|
||||
|
||||
return (
|
||||
<IonCard>
|
||||
<IonCardContent>
|
||||
<IonLabel>
|
||||
<h2>Item number {number}</h2>
|
||||
<p>This is a dummy item</p>
|
||||
</IonLabel>
|
||||
</IonCardContent>
|
||||
</IonCard>
|
||||
);
|
||||
}
|
@@ -1,38 +1,25 @@
|
||||
import { IonIcon, IonLabel, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs } from '@ionic/react';
|
||||
import { IonRouterOutlet, IonTabs } from '@ionic/react';
|
||||
|
||||
import { cloudOutline, searchOutline } from 'ionicons/icons';
|
||||
import { Route, Redirect } from 'react-router';
|
||||
|
||||
import Tab1 from './AppPages/Tab1';
|
||||
import Tab2 from './AppPages/Tab2';
|
||||
import './theme/variables.scss';
|
||||
import Home from './pages/Home';
|
||||
import React from 'react';
|
||||
|
||||
import './style.scss';
|
||||
|
||||
function DemoStickyBottomSheetExample() {
|
||||
function DemoStickyBottomSheetExample(): React.JSX.Element {
|
||||
return (
|
||||
<IonTabs>
|
||||
<IonTabs className="demo-sticky-bottom-sheet-example">
|
||||
<IonRouterOutlet>
|
||||
<Route exact path="/demo-weather-app/tab1">
|
||||
<Tab1 />
|
||||
</Route>
|
||||
<Route exact path="/demo-weather-app/tab2">
|
||||
<Tab2 />
|
||||
<Route exact path="/demo-sticky-bottom-sheet-example/home">
|
||||
<Home />
|
||||
</Route>
|
||||
|
||||
<Redirect exact path="/demo-weather-app" to="/demo-weather-app/tab1" />
|
||||
<Redirect
|
||||
exact
|
||||
path="/demo-sticky-bottom-sheet-example"
|
||||
to="/demo-sticky-bottom-sheet-example/home"
|
||||
/>
|
||||
</IonRouterOutlet>
|
||||
|
||||
{/* */}
|
||||
<IonTabBar slot="bottom">
|
||||
<IonTabButton tab="tab1" href="/demo-weather-app/tab1">
|
||||
<IonIcon icon={cloudOutline} />
|
||||
<IonLabel>Dashboard</IonLabel>
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="tab2" href="/demo-weather-app/tab2">
|
||||
<IonIcon icon={searchOutline} />
|
||||
<IonLabel>Search</IonLabel>
|
||||
</IonTabButton>
|
||||
</IonTabBar>
|
||||
</IonTabs>
|
||||
);
|
||||
}
|
||||
|
@@ -0,0 +1,66 @@
|
||||
import {
|
||||
IonButton,
|
||||
IonButtons,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonIcon,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
useIonRouter,
|
||||
} from '@ionic/react';
|
||||
import React, { useState } from 'react';
|
||||
import { BottomSheet } from '../components/BottomSheet';
|
||||
import './Home.scss';
|
||||
import { chevronBackOutline } from 'ionicons/icons';
|
||||
|
||||
const Home = (): React.JSX.Element => {
|
||||
const [showBottomSheet, setShowBottomSheet] = useState<boolean>(true);
|
||||
|
||||
const handleClose = () => {
|
||||
setShowBottomSheet(false);
|
||||
setTimeout(() => {
|
||||
setShowBottomSheet(true);
|
||||
}, 10);
|
||||
};
|
||||
|
||||
const router = useIonRouter();
|
||||
function handleBackClick() {
|
||||
router.goBack();
|
||||
}
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader className="ion-no-border">
|
||||
<IonToolbar>
|
||||
<IonTitle>Ionic Sticky Bottom Sheet</IonTitle>
|
||||
|
||||
<IonButtons slot="start">
|
||||
<IonButton shape="round" onClick={() => handleBackClick()}>
|
||||
<IonIcon icon={chevronBackOutline} color="primary" />
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large" className="ion-text-wrap">
|
||||
Sticky Bottom Sheet
|
||||
</IonTitle>
|
||||
|
||||
<IonButtons slot="start">
|
||||
<IonButton shape="round" onClick={() => handleBackClick()}>
|
||||
<IonIcon icon={chevronBackOutline} color="primary" />
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<BottomSheet isOpen={showBottomSheet} close={handleClose} />
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
@@ -1,103 +0,0 @@
|
||||
#about-page {
|
||||
ion-toolbar {
|
||||
position: absolute;
|
||||
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
--background: transparent;
|
||||
--color: white;
|
||||
}
|
||||
|
||||
ion-toolbar ion-back-button,
|
||||
ion-toolbar ion-button,
|
||||
ion-toolbar ion-menu-button {
|
||||
--color: white;
|
||||
}
|
||||
|
||||
.about-header {
|
||||
position: relative;
|
||||
|
||||
width: 100%;
|
||||
height: 30%;
|
||||
}
|
||||
|
||||
.about-header .about-image {
|
||||
position: absolute;
|
||||
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
opacity: 0;
|
||||
|
||||
transition: opacity 500ms ease-in-out;
|
||||
}
|
||||
|
||||
.about-header .madison {
|
||||
background-image: url('/assets/WeatherDemo/img/about/madison.jpg');
|
||||
}
|
||||
|
||||
.about-header .austin {
|
||||
background-image: url('/assets/WeatherDemo/img/about/austin.jpg');
|
||||
}
|
||||
|
||||
.about-header .chicago {
|
||||
background-image: url('/assets/WeatherDemo/img/about/chicago.jpg');
|
||||
}
|
||||
|
||||
.about-header .seattle {
|
||||
background-image: url('/assets/WeatherDemo/img/about/seattle.jpg');
|
||||
}
|
||||
|
||||
.about-info {
|
||||
position: relative;
|
||||
margin-top: -10px;
|
||||
border-radius: 10px;
|
||||
background: var(--ion-background-color, #fff);
|
||||
z-index: 2; // display rounded border above header image
|
||||
}
|
||||
|
||||
.about-info h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.about-info ion-list {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.about-info p {
|
||||
line-height: 130%;
|
||||
|
||||
color: var(--ion-color-dark);
|
||||
}
|
||||
|
||||
.about-info ion-icon {
|
||||
margin-inline-end: 32px;
|
||||
}
|
||||
|
||||
/*
|
||||
* iOS Only
|
||||
*/
|
||||
|
||||
.ios .about-info {
|
||||
--ion-padding: 19px;
|
||||
}
|
||||
|
||||
.ios .about-info h3 {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
#date-input-popover {
|
||||
--offset-y: -var(--ion-safe-area-bottom);
|
||||
|
||||
--max-width: 90%;
|
||||
--width: 336px;
|
||||
}
|
@@ -0,0 +1,235 @@
|
||||
.demo-sticky-bottom-sheet-example {
|
||||
/* Ionic Variables and Theming. For more info, please see:
|
||||
http://ionicframework.com/docs/theming/ */
|
||||
|
||||
/** Ionic CSS Variables **/
|
||||
/** 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;
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
/*
|
||||
* Dark Colors
|
||||
* -------------------------------------------
|
||||
*/
|
||||
|
||||
body {
|
||||
--ion-color-primary: #428cff;
|
||||
--ion-color-primary-rgb: 66, 140, 255;
|
||||
--ion-color-primary-contrast: #ffffff;
|
||||
--ion-color-primary-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-primary-shade: #3a7be0;
|
||||
--ion-color-primary-tint: #5598ff;
|
||||
|
||||
--ion-color-secondary: #50c8ff;
|
||||
--ion-color-secondary-rgb: 80, 200, 255;
|
||||
--ion-color-secondary-contrast: #ffffff;
|
||||
--ion-color-secondary-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-secondary-shade: #46b0e0;
|
||||
--ion-color-secondary-tint: #62ceff;
|
||||
|
||||
--ion-color-tertiary: #6a64ff;
|
||||
--ion-color-tertiary-rgb: 106, 100, 255;
|
||||
--ion-color-tertiary-contrast: #ffffff;
|
||||
--ion-color-tertiary-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-tertiary-shade: #5d58e0;
|
||||
--ion-color-tertiary-tint: #7974ff;
|
||||
|
||||
--ion-color-success: #2fdf75;
|
||||
--ion-color-success-rgb: 47, 223, 117;
|
||||
--ion-color-success-contrast: #000000;
|
||||
--ion-color-success-contrast-rgb: 0, 0, 0;
|
||||
--ion-color-success-shade: #29c467;
|
||||
--ion-color-success-tint: #44e283;
|
||||
|
||||
--ion-color-warning: #ffd534;
|
||||
--ion-color-warning-rgb: 255, 213, 52;
|
||||
--ion-color-warning-contrast: #000000;
|
||||
--ion-color-warning-contrast-rgb: 0, 0, 0;
|
||||
--ion-color-warning-shade: #e0bb2e;
|
||||
--ion-color-warning-tint: #ffd948;
|
||||
|
||||
--ion-color-danger: #ff4961;
|
||||
--ion-color-danger-rgb: 255, 73, 97;
|
||||
--ion-color-danger-contrast: #ffffff;
|
||||
--ion-color-danger-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-danger-shade: #e04055;
|
||||
--ion-color-danger-tint: #ff5b71;
|
||||
|
||||
--ion-color-dark: #f4f5f8;
|
||||
--ion-color-dark-rgb: 244, 245, 248;
|
||||
--ion-color-dark-contrast: #000000;
|
||||
--ion-color-dark-contrast-rgb: 0, 0, 0;
|
||||
--ion-color-dark-shade: #d7d8da;
|
||||
--ion-color-dark-tint: #f5f6f9;
|
||||
|
||||
--ion-color-medium: #989aa2;
|
||||
--ion-color-medium-rgb: 152, 154, 162;
|
||||
--ion-color-medium-contrast: #000000;
|
||||
--ion-color-medium-contrast-rgb: 0, 0, 0;
|
||||
--ion-color-medium-shade: #86888f;
|
||||
--ion-color-medium-tint: #a2a4ab;
|
||||
|
||||
--ion-color-light: #222428;
|
||||
--ion-color-light-rgb: 34, 36, 40;
|
||||
--ion-color-light-contrast: #ffffff;
|
||||
--ion-color-light-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-light-shade: #1e2023;
|
||||
--ion-color-light-tint: #383a3e;
|
||||
}
|
||||
|
||||
/*
|
||||
* iOS Dark Theme
|
||||
* -------------------------------------------
|
||||
*/
|
||||
|
||||
.ios body {
|
||||
--ion-background-color: #000000;
|
||||
--ion-background-color-rgb: 0, 0, 0;
|
||||
|
||||
--ion-text-color: #ffffff;
|
||||
--ion-text-color-rgb: 255, 255, 255;
|
||||
|
||||
--ion-color-step-50: #0d0d0d;
|
||||
--ion-color-step-100: #1a1a1a;
|
||||
--ion-color-step-150: #262626;
|
||||
--ion-color-step-200: #333333;
|
||||
--ion-color-step-250: #404040;
|
||||
--ion-color-step-300: #4d4d4d;
|
||||
--ion-color-step-350: #595959;
|
||||
--ion-color-step-400: #666666;
|
||||
--ion-color-step-450: #737373;
|
||||
--ion-color-step-500: #808080;
|
||||
--ion-color-step-550: #8c8c8c;
|
||||
--ion-color-step-600: #999999;
|
||||
--ion-color-step-650: #a6a6a6;
|
||||
--ion-color-step-700: #b3b3b3;
|
||||
--ion-color-step-750: #bfbfbf;
|
||||
--ion-color-step-800: #cccccc;
|
||||
--ion-color-step-850: #d9d9d9;
|
||||
--ion-color-step-900: #e6e6e6;
|
||||
--ion-color-step-950: #f2f2f2;
|
||||
|
||||
--ion-item-background: #000000;
|
||||
|
||||
--ion-card-background: #1c1c1d;
|
||||
}
|
||||
|
||||
.ios ion-modal {
|
||||
--ion-background-color: var(--ion-color-step-100);
|
||||
--ion-toolbar-background: var(--ion-color-step-150);
|
||||
--ion-toolbar-border-color: var(--ion-color-step-250);
|
||||
}
|
||||
|
||||
/*
|
||||
* Material Design Dark Theme
|
||||
* -------------------------------------------
|
||||
*/
|
||||
|
||||
.md body {
|
||||
--ion-background-color: #121212;
|
||||
--ion-background-color-rgb: 18, 18, 18;
|
||||
|
||||
--ion-text-color: #ffffff;
|
||||
--ion-text-color-rgb: 255, 255, 255;
|
||||
|
||||
--ion-border-color: #222222;
|
||||
|
||||
--ion-color-step-50: #1e1e1e;
|
||||
--ion-color-step-100: #2a2a2a;
|
||||
--ion-color-step-150: #363636;
|
||||
--ion-color-step-200: #414141;
|
||||
--ion-color-step-250: #4d4d4d;
|
||||
--ion-color-step-300: #595959;
|
||||
--ion-color-step-350: #656565;
|
||||
--ion-color-step-400: #717171;
|
||||
--ion-color-step-450: #7d7d7d;
|
||||
--ion-color-step-500: #898989;
|
||||
--ion-color-step-550: #949494;
|
||||
--ion-color-step-600: #a0a0a0;
|
||||
--ion-color-step-650: #acacac;
|
||||
--ion-color-step-700: #b8b8b8;
|
||||
--ion-color-step-750: #c4c4c4;
|
||||
--ion-color-step-800: #d0d0d0;
|
||||
--ion-color-step-850: #dbdbdb;
|
||||
--ion-color-step-900: #e7e7e7;
|
||||
--ion-color-step-950: #f3f3f3;
|
||||
|
||||
--ion-item-background: #1e1e1e;
|
||||
|
||||
--ion-toolbar-background: #1f1f1f;
|
||||
|
||||
--ion-tab-bar-background: #1f1f1f;
|
||||
|
||||
--ion-card-background: #1e1e1e;
|
||||
}
|
||||
}
|
||||
}
|
@@ -18,7 +18,7 @@ import { SkeletonDashboard } from '../TestComponents/SkeletonDashboard';
|
||||
import { chevronBackOutline, refreshOutline } from 'ionicons/icons';
|
||||
import { CurrentWeather } from '../TestComponents/CurrentWeather';
|
||||
|
||||
function Tab1() {
|
||||
function Tab1(): React.JSX.Element {
|
||||
const router = useIonRouter();
|
||||
|
||||
const [currentWeather, setCurrentWeather] = useState(false);
|
@@ -1,81 +0,0 @@
|
||||
import {
|
||||
IonButton,
|
||||
IonCol,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonRow,
|
||||
IonSearchbar,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
} from '@ionic/react';
|
||||
import { useState } from 'react';
|
||||
import { CurrentWeather } from '../components/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;
|
@@ -9,10 +9,10 @@ import {
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
} from '@ionic/react';
|
||||
import { useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { CurrentWeather } from '../TestComponents/CurrentWeather';
|
||||
|
||||
function Tab2() {
|
||||
function Tab2(): React.JSX.Element {
|
||||
const [search, setSearch] = useState('');
|
||||
const [currentWeather, setCurrentWeather] = useState(false);
|
||||
|
@@ -0,0 +1,61 @@
|
||||
.DemoStorageExample {
|
||||
ion-item {
|
||||
--padding-start: 0;
|
||||
--inner-padding-end: 0;
|
||||
}
|
||||
|
||||
ion-label {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
ion-item h2 {
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ion-item p {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
ion-item .date {
|
||||
float: right;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
ion-item ion-icon {
|
||||
color: #c9c9ca;
|
||||
}
|
||||
|
||||
ion-item ion-note {
|
||||
font-size: 15px;
|
||||
margin-right: 8px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
ion-item ion-note.md {
|
||||
margin-right: 14px;
|
||||
}
|
||||
|
||||
.dot {
|
||||
display: block;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
border-radius: 50%;
|
||||
align-self: start;
|
||||
margin: 16px 10px 16px 16px;
|
||||
}
|
||||
|
||||
.dot-unread {
|
||||
background: var(--ion-color-primary);
|
||||
}
|
||||
|
||||
ion-footer ion-title {
|
||||
font-size: 11px;
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
import { IonItem, IonLabel, IonNote } from '@ionic/react';
|
||||
import { getContacts } from '../data/contacts';
|
||||
import './MessageListItem.scss';
|
||||
import React from 'react';
|
||||
import { rawMessage } from '../data/messages';
|
||||
|
||||
const MessageListItem = ({ message }: { message: typeof rawMessage }): React.JSX.Element => {
|
||||
const contacts = getContacts();
|
||||
|
||||
return (
|
||||
<IonItem routerLink={`/demo-storage-example/message/${message.id}`} detail={false}>
|
||||
<div slot="start" className="dot dot-unread"></div>
|
||||
<IonLabel className="ion-text-wrap">
|
||||
<h2>
|
||||
{contacts.filter((c: any) => parseInt(c.id) === parseInt(message.fromId))[0].name}
|
||||
<span className="date">
|
||||
<IonNote>{message.date}</IonNote>
|
||||
</span>
|
||||
</h2>
|
||||
<h3>{message.subject}</h3>
|
||||
<p>{message.body}</p>
|
||||
</IonLabel>
|
||||
</IonItem>
|
||||
);
|
||||
};
|
||||
|
||||
export default MessageListItem;
|
@@ -0,0 +1,61 @@
|
||||
import { Storage, Drivers } from '@ionic/storage';
|
||||
|
||||
let storage: any;
|
||||
|
||||
export const createStore = (name = '__mydb') => {
|
||||
storage = new Storage({
|
||||
name,
|
||||
driverOrder: [Drivers.IndexedDB, Drivers.LocalStorage],
|
||||
});
|
||||
|
||||
storage.create();
|
||||
};
|
||||
|
||||
export const set = (key: any, val: any) => {
|
||||
if (storage) {
|
||||
storage.set(key, val);
|
||||
}
|
||||
};
|
||||
|
||||
export const get = async (key: string) => {
|
||||
if (storage) {
|
||||
const val = await storage.get(key);
|
||||
return val;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const remove = async (key: string) => {
|
||||
await storage.remove(key);
|
||||
};
|
||||
|
||||
export const clear = async () => {
|
||||
await storage.clear();
|
||||
};
|
||||
|
||||
export const setObject = async (key: string, id: string, val: string) => {
|
||||
const all = await storage.get(key);
|
||||
const objIndex = await all.findIndex((a: any) => parseInt(a.id) === parseInt(id));
|
||||
|
||||
all[objIndex] = val;
|
||||
set(key, all);
|
||||
};
|
||||
|
||||
export const removeObject = async (key: string, id: string) => {
|
||||
const all = await storage.get(key);
|
||||
const objIndex = await all.findIndex(
|
||||
(a: { id: string | number }) => parseInt(a.id.toString()) === parseInt(id)
|
||||
);
|
||||
|
||||
all.splice(objIndex, 1);
|
||||
set(key, all);
|
||||
};
|
||||
|
||||
export const getObject = async (key: string, id: string) => {
|
||||
const all = await storage.get(key);
|
||||
const obj = await all.filter(
|
||||
(a: { id: string | number }) => parseInt(a.id.toString()) === parseInt(id)
|
||||
)[0];
|
||||
return obj;
|
||||
};
|
@@ -0,0 +1,36 @@
|
||||
const contacts = [
|
||||
{
|
||||
name: "Matt Chorsey",
|
||||
id: 0,
|
||||
},
|
||||
{
|
||||
name: "Lauren Ruthford",
|
||||
id: 1,
|
||||
},
|
||||
{
|
||||
name: "Jordan Firth",
|
||||
id: 2,
|
||||
},
|
||||
{
|
||||
name: "Bill Thomas",
|
||||
id: 3,
|
||||
},
|
||||
{
|
||||
name: "Joanne Pollan",
|
||||
id: 4,
|
||||
},
|
||||
{
|
||||
name: "Andrea Cornerston",
|
||||
id: 5,
|
||||
},
|
||||
{
|
||||
name: "Moe Chamont",
|
||||
id: 6,
|
||||
},
|
||||
{
|
||||
name: "Kelly Richardson",
|
||||
id: 7,
|
||||
},
|
||||
];
|
||||
|
||||
export const getContacts = () => contacts;
|
@@ -0,0 +1,62 @@
|
||||
export const rawMessages = [
|
||||
{
|
||||
fromId: 0,
|
||||
subject: "New event: Trip to Vegas",
|
||||
body: "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Utenim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
|
||||
date: "9:32 AM",
|
||||
id: 0,
|
||||
},
|
||||
{
|
||||
fromId: 1,
|
||||
subject: "Long time no chat",
|
||||
body: "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Utenim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
|
||||
date: "6:12 AM",
|
||||
id: 1,
|
||||
},
|
||||
{
|
||||
fromId: 2,
|
||||
subject: "Report Results",
|
||||
body: "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Utenim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
|
||||
date: "4:55 AM",
|
||||
id: 2,
|
||||
},
|
||||
{
|
||||
fromId: 3,
|
||||
subject: "The situation",
|
||||
body: "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Utenim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
|
||||
date: "Yesterday",
|
||||
id: 3,
|
||||
},
|
||||
{
|
||||
fromId: 4,
|
||||
subject: "Updated invitation: Swim lessons",
|
||||
body: "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Utenim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
|
||||
date: "Yesterday",
|
||||
id: 4,
|
||||
},
|
||||
{
|
||||
fromId: 5,
|
||||
subject: "Last minute ask",
|
||||
body: "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Utenim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
|
||||
date: "Yesterday",
|
||||
id: 5,
|
||||
},
|
||||
{
|
||||
fromId: 6,
|
||||
subject: "Family Calendar - Version 1",
|
||||
body: "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Utenim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
|
||||
date: "Last Week",
|
||||
id: 6,
|
||||
},
|
||||
{
|
||||
fromId: 7,
|
||||
subject: "Placeholder Headhots",
|
||||
body: "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Utenim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
|
||||
date: "Last Week",
|
||||
id: 7,
|
||||
},
|
||||
];
|
||||
|
||||
export const rawMessage = rawMessages[0];
|
||||
|
||||
export const getMessages = () => rawMessages;
|
@@ -3,36 +3,27 @@ import { IonIcon, IonLabel, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs }
|
||||
import { cloudOutline, searchOutline } from 'ionicons/icons';
|
||||
import { Route, Redirect } from 'react-router';
|
||||
|
||||
import Tab1 from './AppPages/Tab1';
|
||||
import Tab2 from './AppPages/Tab2';
|
||||
// import Tab1 from './AppPages/Tab1';
|
||||
// import Tab2 from './AppPages/Tab2';
|
||||
|
||||
import './style.scss';
|
||||
import Home from './pages/Home';
|
||||
import ViewMessage from './pages/ViewMessage';
|
||||
|
||||
import './theme/variables.scss';
|
||||
|
||||
function DemoStorageExample() {
|
||||
return (
|
||||
<IonTabs>
|
||||
<IonTabs className="demo-storage-example">
|
||||
<IonRouterOutlet>
|
||||
<Route exact path="/demo-weather-app/tab1">
|
||||
<Tab1 />
|
||||
<Route path="/demo-storage-example/home" exact={true}>
|
||||
<Home />
|
||||
</Route>
|
||||
<Route exact path="/demo-weather-app/tab2">
|
||||
<Tab2 />
|
||||
<Route path="/demo-storage-example/message/:id">
|
||||
<ViewMessage />
|
||||
</Route>
|
||||
|
||||
<Redirect exact path="/demo-weather-app" to="/demo-weather-app/tab1" />
|
||||
<Redirect exact path="/demo-storage-example" to="/demo-storage-example/home" />
|
||||
</IonRouterOutlet>
|
||||
|
||||
{/* */}
|
||||
<IonTabBar slot="bottom">
|
||||
<IonTabButton tab="tab1" href="/demo-weather-app/tab1">
|
||||
<IonIcon icon={cloudOutline} />
|
||||
<IonLabel>Dashboard</IonLabel>
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="tab2" href="/demo-weather-app/tab2">
|
||||
<IonIcon icon={searchOutline} />
|
||||
<IonLabel>Search</IonLabel>
|
||||
</IonTabButton>
|
||||
</IonTabBar>
|
||||
</IonTabs>
|
||||
);
|
||||
}
|
||||
|
96
03_source/mobile/src/pages/DemoStorageExample/pages/Home.tsx
Normal file
96
03_source/mobile/src/pages/DemoStorageExample/pages/Home.tsx
Normal file
@@ -0,0 +1,96 @@
|
||||
import MessageListItem from '../components/MessageListItem';
|
||||
import React, { JSX, useState } from 'react';
|
||||
import {
|
||||
IonButton,
|
||||
IonButtons,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonIcon,
|
||||
IonList,
|
||||
IonPage,
|
||||
IonRefresher,
|
||||
IonRefresherContent,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
useIonRouter,
|
||||
useIonViewWillEnter,
|
||||
} from '@ionic/react';
|
||||
|
||||
import './Home.scss';
|
||||
|
||||
import { get, set } from '../data/IonicStorage';
|
||||
import { getMessages, rawMessages } from '../data/messages';
|
||||
import { chevronBackOutline } from 'ionicons/icons';
|
||||
|
||||
const Home = (): JSX.Element => {
|
||||
const [messages, setMessages] = useState<typeof rawMessages>([]);
|
||||
|
||||
useIonViewWillEnter(async () => {
|
||||
const exists = await get('msgs');
|
||||
|
||||
if (!exists) {
|
||||
const msgs = getMessages();
|
||||
set('msgs', msgs);
|
||||
setMessages(msgs);
|
||||
} else {
|
||||
setMessages(exists);
|
||||
}
|
||||
});
|
||||
|
||||
const refresh = (e: any) => {
|
||||
resetStore();
|
||||
setTimeout(() => {
|
||||
e.detail.complete();
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
const resetStore = async () => {
|
||||
const msgs = getMessages();
|
||||
set('msgs', msgs);
|
||||
setMessages(msgs);
|
||||
};
|
||||
|
||||
const router = useIonRouter();
|
||||
function handleBackClick() {
|
||||
router.goBack();
|
||||
}
|
||||
|
||||
return (
|
||||
<IonPage id="home-page">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Inbox</IonTitle>
|
||||
|
||||
<IonButtons slot="start">
|
||||
<IonButton shape="round" onClick={() => handleBackClick()}>
|
||||
<IonIcon icon={chevronBackOutline} color="primary" />
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent fullscreen>
|
||||
<IonRefresher slot="fixed" onIonRefresh={refresh}>
|
||||
<IonRefresherContent></IonRefresherContent>
|
||||
</IonRefresher>
|
||||
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">Inbox</IonTitle>
|
||||
|
||||
<IonButtons slot="start">
|
||||
<IonButton shape="round" onClick={() => handleBackClick()}>
|
||||
<IonIcon icon={chevronBackOutline} color="primary" />
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonList>
|
||||
{messages && messages.map((m: any) => <MessageListItem key={m.id} message={m} />)}
|
||||
</IonList>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
@@ -0,0 +1,42 @@
|
||||
.demo-storage-example {
|
||||
#view-message-page ion-item {
|
||||
--inner-padding-end: 0;
|
||||
--background: transparent;
|
||||
}
|
||||
|
||||
#view-message-page ion-label {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
#view-message-page ion-item h2 {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
#view-message-page ion-item .date {
|
||||
float: right;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#view-message-page ion-item ion-icon {
|
||||
font-size: 42px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
#view-message-page ion-item ion-note {
|
||||
font-size: 15px;
|
||||
margin-right: 12px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
#view-message-page h1 {
|
||||
margin: 0;
|
||||
font-weight: bold;
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
#view-message-page p {
|
||||
line-height: 22px;
|
||||
}
|
||||
}
|
@@ -0,0 +1,163 @@
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
IonBackButton,
|
||||
IonButton,
|
||||
IonButtons,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonIcon,
|
||||
IonInput,
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonNote,
|
||||
IonPage,
|
||||
IonSelect,
|
||||
IonSelectOption,
|
||||
IonTextarea,
|
||||
IonToast,
|
||||
IonToolbar,
|
||||
useIonViewWillEnter,
|
||||
} from '@ionic/react';
|
||||
import { checkmarkSharp, personCircle, trashOutline } from 'ionicons/icons';
|
||||
import { useHistory, useParams } from 'react-router';
|
||||
import './ViewMessage.scss';
|
||||
|
||||
import { getObject, setObject, removeObject } from '../data/IonicStorage';
|
||||
import { getContacts } from '../data/contacts';
|
||||
|
||||
function ViewMessage(): React.JSX.Element {
|
||||
const [message, setMessage] = useState<{
|
||||
id?: number;
|
||||
fromId?: number;
|
||||
subject?: string;
|
||||
body?: string;
|
||||
date?: string;
|
||||
}>();
|
||||
const [showToast, setShowToast] = useState({
|
||||
show: false,
|
||||
message: '',
|
||||
color: '',
|
||||
});
|
||||
const contacts = getContacts();
|
||||
const params = useParams<{ id: string }>();
|
||||
const history = useHistory();
|
||||
|
||||
useIonViewWillEnter(async () => {
|
||||
const msg = await getObject('msgs', params.id);
|
||||
setMessage(msg);
|
||||
});
|
||||
|
||||
const saveEmail = async () => {
|
||||
await setObject('msgs', params.id, message);
|
||||
setShowToast({
|
||||
show: true,
|
||||
message: 'Email has been saved.',
|
||||
color: 'primary',
|
||||
});
|
||||
};
|
||||
|
||||
const removeEmail = async () => {
|
||||
await removeObject('msgs', params.id);
|
||||
setShowToast({
|
||||
show: true,
|
||||
message: 'Email has been removed.',
|
||||
color: 'danger',
|
||||
});
|
||||
history.goBack();
|
||||
};
|
||||
|
||||
const handleChange = (key: any, val: any) => {
|
||||
setMessage({ ...message, [key]: val });
|
||||
};
|
||||
|
||||
return (
|
||||
<IonPage id="view-message-page">
|
||||
<IonHeader translucent>
|
||||
<IonToolbar>
|
||||
<IonButtons>
|
||||
<IonBackButton text="Inbox" defaultHref="/home"></IonBackButton>
|
||||
</IonButtons>
|
||||
|
||||
<IonButtons slot="end">
|
||||
<IonButton onClick={removeEmail}>
|
||||
<IonIcon icon={trashOutline} color="danger" />
|
||||
</IonButton>
|
||||
<IonButton onClick={saveEmail}>
|
||||
<IonIcon icon={checkmarkSharp} />
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent fullscreen>
|
||||
{message ? (
|
||||
<>
|
||||
<IonItem>
|
||||
<IonIcon icon={personCircle} color="primary"></IonIcon>
|
||||
<IonLabel className="ion-text-wrap">
|
||||
<IonItem lines="none">
|
||||
<IonLabel>From:</IonLabel>
|
||||
<IonSelect
|
||||
value={message.fromId}
|
||||
onIonChange={(e) =>
|
||||
handleChange('fromId', parseInt((e.target as HTMLIonSelectElement).value))
|
||||
}
|
||||
>
|
||||
{contacts.map((contact, index) => {
|
||||
return (
|
||||
<IonSelectOption key={index} value={contact.id}>
|
||||
{contact.name}
|
||||
</IonSelectOption>
|
||||
);
|
||||
})}
|
||||
</IonSelect>
|
||||
</IonItem>
|
||||
<span className="date">
|
||||
<IonNote>{message.date}</IonNote>
|
||||
</span>
|
||||
<h3>To: Me</h3>
|
||||
</IonLabel>
|
||||
</IonItem>
|
||||
|
||||
<div className="ion-padding">
|
||||
<IonItem lines="full">
|
||||
<IonLabel position="floating">Subject</IonLabel>
|
||||
<IonInput
|
||||
placeholder="Email subject..."
|
||||
value={message.subject}
|
||||
onKeyUp={(e) => handleChange('subject', e.currentTarget.value)}
|
||||
style={{ fontSize: '1.5rem', fontWeight: '400' }}
|
||||
/>
|
||||
</IonItem>
|
||||
|
||||
<br />
|
||||
|
||||
<IonItem lines="full">
|
||||
<IonLabel position="floating">Body</IonLabel>
|
||||
<IonTextarea
|
||||
placeholder="Enter email body..."
|
||||
rows={8}
|
||||
className="ion-text-wrap"
|
||||
value={message.body}
|
||||
onIonChange={(e) => handleChange('body', (e.target as HTMLInputElement).value)}
|
||||
/>
|
||||
</IonItem>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div>Message not found</div>
|
||||
)}
|
||||
</IonContent>
|
||||
|
||||
<IonToast
|
||||
isOpen={showToast.show}
|
||||
onDidDismiss={() => setShowToast({ show: false, message: '', color: 'red' })}
|
||||
message={showToast.message}
|
||||
duration={2000}
|
||||
color={showToast.color}
|
||||
/>
|
||||
</IonPage>
|
||||
);
|
||||
}
|
||||
|
||||
export default ViewMessage;
|
@@ -1,103 +0,0 @@
|
||||
#about-page {
|
||||
ion-toolbar {
|
||||
position: absolute;
|
||||
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
--background: transparent;
|
||||
--color: white;
|
||||
}
|
||||
|
||||
ion-toolbar ion-back-button,
|
||||
ion-toolbar ion-button,
|
||||
ion-toolbar ion-menu-button {
|
||||
--color: white;
|
||||
}
|
||||
|
||||
.about-header {
|
||||
position: relative;
|
||||
|
||||
width: 100%;
|
||||
height: 30%;
|
||||
}
|
||||
|
||||
.about-header .about-image {
|
||||
position: absolute;
|
||||
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
opacity: 0;
|
||||
|
||||
transition: opacity 500ms ease-in-out;
|
||||
}
|
||||
|
||||
.about-header .madison {
|
||||
background-image: url('/assets/WeatherDemo/img/about/madison.jpg');
|
||||
}
|
||||
|
||||
.about-header .austin {
|
||||
background-image: url('/assets/WeatherDemo/img/about/austin.jpg');
|
||||
}
|
||||
|
||||
.about-header .chicago {
|
||||
background-image: url('/assets/WeatherDemo/img/about/chicago.jpg');
|
||||
}
|
||||
|
||||
.about-header .seattle {
|
||||
background-image: url('/assets/WeatherDemo/img/about/seattle.jpg');
|
||||
}
|
||||
|
||||
.about-info {
|
||||
position: relative;
|
||||
margin-top: -10px;
|
||||
border-radius: 10px;
|
||||
background: var(--ion-background-color, #fff);
|
||||
z-index: 2; // display rounded border above header image
|
||||
}
|
||||
|
||||
.about-info h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.about-info ion-list {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.about-info p {
|
||||
line-height: 130%;
|
||||
|
||||
color: var(--ion-color-dark);
|
||||
}
|
||||
|
||||
.about-info ion-icon {
|
||||
margin-inline-end: 32px;
|
||||
}
|
||||
|
||||
/*
|
||||
* iOS Only
|
||||
*/
|
||||
|
||||
.ios .about-info {
|
||||
--ion-padding: 19px;
|
||||
}
|
||||
|
||||
.ios .about-info h3 {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
#date-input-popover {
|
||||
--offset-y: -var(--ion-safe-area-bottom);
|
||||
|
||||
--max-width: 90%;
|
||||
--width: 336px;
|
||||
}
|
@@ -0,0 +1,237 @@
|
||||
.demo-storage-example {
|
||||
/* Ionic Variables and Theming. For more info, please see:
|
||||
http://ionicframework.com/docs/theming/ */
|
||||
|
||||
/** Ionic CSS Variables **/
|
||||
:root {
|
||||
/** 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;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
/*
|
||||
* Dark Colors
|
||||
* -------------------------------------------
|
||||
*/
|
||||
|
||||
body {
|
||||
--ion-color-primary: #428cff;
|
||||
--ion-color-primary-rgb: 66, 140, 255;
|
||||
--ion-color-primary-contrast: #ffffff;
|
||||
--ion-color-primary-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-primary-shade: #3a7be0;
|
||||
--ion-color-primary-tint: #5598ff;
|
||||
|
||||
--ion-color-secondary: #50c8ff;
|
||||
--ion-color-secondary-rgb: 80, 200, 255;
|
||||
--ion-color-secondary-contrast: #ffffff;
|
||||
--ion-color-secondary-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-secondary-shade: #46b0e0;
|
||||
--ion-color-secondary-tint: #62ceff;
|
||||
|
||||
--ion-color-tertiary: #6a64ff;
|
||||
--ion-color-tertiary-rgb: 106, 100, 255;
|
||||
--ion-color-tertiary-contrast: #ffffff;
|
||||
--ion-color-tertiary-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-tertiary-shade: #5d58e0;
|
||||
--ion-color-tertiary-tint: #7974ff;
|
||||
|
||||
--ion-color-success: #2fdf75;
|
||||
--ion-color-success-rgb: 47, 223, 117;
|
||||
--ion-color-success-contrast: #000000;
|
||||
--ion-color-success-contrast-rgb: 0, 0, 0;
|
||||
--ion-color-success-shade: #29c467;
|
||||
--ion-color-success-tint: #44e283;
|
||||
|
||||
--ion-color-warning: #ffd534;
|
||||
--ion-color-warning-rgb: 255, 213, 52;
|
||||
--ion-color-warning-contrast: #000000;
|
||||
--ion-color-warning-contrast-rgb: 0, 0, 0;
|
||||
--ion-color-warning-shade: #e0bb2e;
|
||||
--ion-color-warning-tint: #ffd948;
|
||||
|
||||
--ion-color-danger: #ff4961;
|
||||
--ion-color-danger-rgb: 255, 73, 97;
|
||||
--ion-color-danger-contrast: #ffffff;
|
||||
--ion-color-danger-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-danger-shade: #e04055;
|
||||
--ion-color-danger-tint: #ff5b71;
|
||||
|
||||
--ion-color-dark: #f4f5f8;
|
||||
--ion-color-dark-rgb: 244, 245, 248;
|
||||
--ion-color-dark-contrast: #000000;
|
||||
--ion-color-dark-contrast-rgb: 0, 0, 0;
|
||||
--ion-color-dark-shade: #d7d8da;
|
||||
--ion-color-dark-tint: #f5f6f9;
|
||||
|
||||
--ion-color-medium: #989aa2;
|
||||
--ion-color-medium-rgb: 152, 154, 162;
|
||||
--ion-color-medium-contrast: #000000;
|
||||
--ion-color-medium-contrast-rgb: 0, 0, 0;
|
||||
--ion-color-medium-shade: #86888f;
|
||||
--ion-color-medium-tint: #a2a4ab;
|
||||
|
||||
--ion-color-light: #222428;
|
||||
--ion-color-light-rgb: 34, 36, 40;
|
||||
--ion-color-light-contrast: #ffffff;
|
||||
--ion-color-light-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-light-shade: #1e2023;
|
||||
--ion-color-light-tint: #383a3e;
|
||||
}
|
||||
|
||||
/*
|
||||
* iOS Dark Theme
|
||||
* -------------------------------------------
|
||||
*/
|
||||
|
||||
.ios body {
|
||||
--ion-background-color: #000000;
|
||||
--ion-background-color-rgb: 0, 0, 0;
|
||||
|
||||
--ion-text-color: #ffffff;
|
||||
--ion-text-color-rgb: 255, 255, 255;
|
||||
|
||||
--ion-color-step-50: #0d0d0d;
|
||||
--ion-color-step-100: #1a1a1a;
|
||||
--ion-color-step-150: #262626;
|
||||
--ion-color-step-200: #333333;
|
||||
--ion-color-step-250: #404040;
|
||||
--ion-color-step-300: #4d4d4d;
|
||||
--ion-color-step-350: #595959;
|
||||
--ion-color-step-400: #666666;
|
||||
--ion-color-step-450: #737373;
|
||||
--ion-color-step-500: #808080;
|
||||
--ion-color-step-550: #8c8c8c;
|
||||
--ion-color-step-600: #999999;
|
||||
--ion-color-step-650: #a6a6a6;
|
||||
--ion-color-step-700: #b3b3b3;
|
||||
--ion-color-step-750: #bfbfbf;
|
||||
--ion-color-step-800: #cccccc;
|
||||
--ion-color-step-850: #d9d9d9;
|
||||
--ion-color-step-900: #e6e6e6;
|
||||
--ion-color-step-950: #f2f2f2;
|
||||
|
||||
--ion-item-background: #000000;
|
||||
|
||||
--ion-card-background: #1c1c1d;
|
||||
}
|
||||
|
||||
.ios ion-modal {
|
||||
--ion-background-color: var(--ion-color-step-100);
|
||||
--ion-toolbar-background: var(--ion-color-step-150);
|
||||
--ion-toolbar-border-color: var(--ion-color-step-250);
|
||||
}
|
||||
|
||||
/*
|
||||
* Material Design Dark Theme
|
||||
* -------------------------------------------
|
||||
*/
|
||||
|
||||
.md body {
|
||||
--ion-background-color: #121212;
|
||||
--ion-background-color-rgb: 18, 18, 18;
|
||||
|
||||
--ion-text-color: #ffffff;
|
||||
--ion-text-color-rgb: 255, 255, 255;
|
||||
|
||||
--ion-border-color: #222222;
|
||||
|
||||
--ion-color-step-50: #1e1e1e;
|
||||
--ion-color-step-100: #2a2a2a;
|
||||
--ion-color-step-150: #363636;
|
||||
--ion-color-step-200: #414141;
|
||||
--ion-color-step-250: #4d4d4d;
|
||||
--ion-color-step-300: #595959;
|
||||
--ion-color-step-350: #656565;
|
||||
--ion-color-step-400: #717171;
|
||||
--ion-color-step-450: #7d7d7d;
|
||||
--ion-color-step-500: #898989;
|
||||
--ion-color-step-550: #949494;
|
||||
--ion-color-step-600: #a0a0a0;
|
||||
--ion-color-step-650: #acacac;
|
||||
--ion-color-step-700: #b8b8b8;
|
||||
--ion-color-step-750: #c4c4c4;
|
||||
--ion-color-step-800: #d0d0d0;
|
||||
--ion-color-step-850: #dbdbdb;
|
||||
--ion-color-step-900: #e7e7e7;
|
||||
--ion-color-step-950: #f3f3f3;
|
||||
|
||||
--ion-item-background: #1e1e1e;
|
||||
|
||||
--ion-toolbar-background: #1f1f1f;
|
||||
|
||||
--ion-tab-bar-background: #1f1f1f;
|
||||
|
||||
--ion-card-background: #1e1e1e;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,76 +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 '../components/SkeletonDashboard';
|
||||
import { chevronBackOutline, refreshOutline } from 'ionicons/icons';
|
||||
import { CurrentWeather } from '../components/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,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>
|
||||
);
|
@@ -1,40 +0,0 @@
|
||||
import { IonIcon, IonLabel, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs } from '@ionic/react';
|
||||
|
||||
import { cloudOutline, searchOutline } from 'ionicons/icons';
|
||||
import { Route, Redirect } from 'react-router';
|
||||
|
||||
import Tab1 from './AppPages/Tab1';
|
||||
import Tab2 from './AppPages/Tab2';
|
||||
|
||||
import './style.scss';
|
||||
|
||||
function DemoWeatherApp() {
|
||||
return (
|
||||
<IonTabs>
|
||||
<IonRouterOutlet>
|
||||
<Route exact path="/demo-weather-app/tab1">
|
||||
<Tab1 />
|
||||
</Route>
|
||||
<Route exact path="/demo-weather-app/tab2">
|
||||
<Tab2 />
|
||||
</Route>
|
||||
|
||||
<Redirect exact path="/demo-weather-app" to="/demo-weather-app/tab1" />
|
||||
</IonRouterOutlet>
|
||||
|
||||
{/* */}
|
||||
<IonTabBar slot="bottom">
|
||||
<IonTabButton tab="tab1" href="/demo-weather-app/tab1">
|
||||
<IonIcon icon={cloudOutline} />
|
||||
<IonLabel>Dashboard</IonLabel>
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="tab2" href="/demo-weather-app/tab2">
|
||||
<IonIcon icon={searchOutline} />
|
||||
<IonLabel>Search</IonLabel>
|
||||
</IonTabButton>
|
||||
</IonTabBar>
|
||||
</IonTabs>
|
||||
);
|
||||
}
|
||||
|
||||
export default DemoWeatherApp;
|
@@ -1,103 +0,0 @@
|
||||
#about-page {
|
||||
ion-toolbar {
|
||||
position: absolute;
|
||||
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
--background: transparent;
|
||||
--color: white;
|
||||
}
|
||||
|
||||
ion-toolbar ion-back-button,
|
||||
ion-toolbar ion-button,
|
||||
ion-toolbar ion-menu-button {
|
||||
--color: white;
|
||||
}
|
||||
|
||||
.about-header {
|
||||
position: relative;
|
||||
|
||||
width: 100%;
|
||||
height: 30%;
|
||||
}
|
||||
|
||||
.about-header .about-image {
|
||||
position: absolute;
|
||||
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
opacity: 0;
|
||||
|
||||
transition: opacity 500ms ease-in-out;
|
||||
}
|
||||
|
||||
.about-header .madison {
|
||||
background-image: url('/assets/WeatherDemo/img/about/madison.jpg');
|
||||
}
|
||||
|
||||
.about-header .austin {
|
||||
background-image: url('/assets/WeatherDemo/img/about/austin.jpg');
|
||||
}
|
||||
|
||||
.about-header .chicago {
|
||||
background-image: url('/assets/WeatherDemo/img/about/chicago.jpg');
|
||||
}
|
||||
|
||||
.about-header .seattle {
|
||||
background-image: url('/assets/WeatherDemo/img/about/seattle.jpg');
|
||||
}
|
||||
|
||||
.about-info {
|
||||
position: relative;
|
||||
margin-top: -10px;
|
||||
border-radius: 10px;
|
||||
background: var(--ion-background-color, #fff);
|
||||
z-index: 2; // display rounded border above header image
|
||||
}
|
||||
|
||||
.about-info h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.about-info ion-list {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.about-info p {
|
||||
line-height: 130%;
|
||||
|
||||
color: var(--ion-color-dark);
|
||||
}
|
||||
|
||||
.about-info ion-icon {
|
||||
margin-inline-end: 32px;
|
||||
}
|
||||
|
||||
/*
|
||||
* iOS Only
|
||||
*/
|
||||
|
||||
.ios .about-info {
|
||||
--ion-padding: 19px;
|
||||
}
|
||||
|
||||
.ios .about-info h3 {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
#date-input-popover {
|
||||
--offset-y: -var(--ion-safe-area-bottom);
|
||||
|
||||
--max-width: 90%;
|
||||
--width: 336px;
|
||||
}
|
@@ -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;
|
@@ -18,7 +18,7 @@ import { SkeletonDashboard } from '../components/SkeletonDashboard';
|
||||
import { chevronBackOutline, refreshOutline } from 'ionicons/icons';
|
||||
import { CurrentWeather } from '../components/CurrentWeather';
|
||||
|
||||
function Tab1() {
|
||||
function Tab1(): React.JSX.Element {
|
||||
const router = useIonRouter();
|
||||
|
||||
const [currentWeather, setCurrentWeather] = useState(false);
|
@@ -9,10 +9,10 @@ import {
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
} from '@ionic/react';
|
||||
import { useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { CurrentWeather } from '../components/CurrentWeather';
|
||||
|
||||
function Tab2() {
|
||||
function Tab2(): React.JSX.Element {
|
||||
const [search, setSearch] = useState('');
|
||||
const [currentWeather, setCurrentWeather] = useState(false);
|
||||
|
@@ -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>
|
||||
);
|
@@ -23,6 +23,7 @@ function DemoWeatherAppUi() {
|
||||
</IonRouterOutlet>
|
||||
|
||||
{/* */}
|
||||
|
||||
<IonTabBar slot="bottom">
|
||||
<IonTabButton tab="tab1" href="/demo-weather-app-ui/tab1">
|
||||
<IonIcon icon={cloudOutline} />
|
||||
|
Reference in New Issue
Block a user