Compare commits
3 Commits
82507b4b31
...
develop/mo
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b2e9616178 | ||
![]() |
d909805283 | ||
![]() |
8c46a93e61 |
@@ -1,6 +1,15 @@
|
||||
{
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 100
|
||||
"printWidth": 160,
|
||||
"overrides": [
|
||||
{
|
||||
"files": "src/App.tsx",
|
||||
"options": {
|
||||
"printWidth": 240
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@@ -13,6 +13,7 @@ dependencies {
|
||||
implementation project(':capacitor-clipboard')
|
||||
implementation project(':capacitor-geolocation')
|
||||
implementation project(':capacitor-preferences')
|
||||
implementation project(':capacitor-share')
|
||||
|
||||
}
|
||||
|
||||
|
@@ -13,3 +13,6 @@ project(':capacitor-geolocation').projectDir = new File('../node_modules/@capaci
|
||||
|
||||
include ':capacitor-preferences'
|
||||
project(':capacitor-preferences').projectDir = new File('../node_modules/@capacitor/preferences/android')
|
||||
|
||||
include ':capacitor-share'
|
||||
project(':capacitor-share').projectDir = new File('../node_modules/@capacitor/share/android')
|
||||
|
@@ -15,6 +15,7 @@ def capacitor_pods
|
||||
pod 'CapacitorClipboard', :path => '../../node_modules/@capacitor/clipboard'
|
||||
pod 'CapacitorGeolocation', :path => '../../node_modules/@capacitor/geolocation'
|
||||
pod 'CapacitorPreferences', :path => '../../node_modules/@capacitor/preferences'
|
||||
pod 'CapacitorShare', :path => '../../node_modules/@capacitor/share'
|
||||
end
|
||||
|
||||
target 'App' do
|
||||
|
@@ -13,6 +13,7 @@
|
||||
"@capacitor/geolocation": "^7.1.2",
|
||||
"@capacitor/ios": "7.0.1",
|
||||
"@capacitor/preferences": "^7.0.0",
|
||||
"@capacitor/share": "^7.0.1",
|
||||
"@hookform/resolvers": "^4.1.3",
|
||||
"@ionic/react": "^8.5.0",
|
||||
"@ionic/react-router": "^8.5.0",
|
||||
@@ -24,8 +25,10 @@
|
||||
"date-fns": "^2.25.0",
|
||||
"ionicons": "^7.1.2",
|
||||
"leaflet": "^1.9.4",
|
||||
"pigeon-maps": "^0.22.1",
|
||||
"pullstate": "^1",
|
||||
"react": "19.0.0",
|
||||
"react-color": "^2.19.3",
|
||||
"react-confetti": "^6.4.0",
|
||||
"react-dom": "19.0.0",
|
||||
"react-hook-form": "^7.55.0",
|
||||
@@ -40,7 +43,7 @@
|
||||
"react-use": "^17.6.0",
|
||||
"reselect": "^4.0.0",
|
||||
"sass": "^1.85.1",
|
||||
"swiper": "^9.1.1",
|
||||
"swiper": "^11.2.8",
|
||||
"use-sound": "^5.0.0",
|
||||
"zod": "^3.24.2"
|
||||
},
|
||||
@@ -73,7 +76,7 @@
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"lint-staged": "^13.2.0",
|
||||
"prettier": "^2.8.6",
|
||||
"prettier": "^3.5.3",
|
||||
"typescript": "^5.8.2",
|
||||
"vite": "^6.2.0"
|
||||
},
|
||||
|
BIN
03_source/mobile/public/assets/DemoQuizApp/icon/favicon.png
Normal file
After Width: | Height: | Size: 930 B |
BIN
03_source/mobile/public/assets/DemoQuizApp/icon/icon.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
03_source/mobile/public/assets/DemoQuizApp/main.png
Normal file
After Width: | Height: | Size: 39 KiB |
1
03_source/mobile/public/assets/DemoQuizApp/shapes.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="350" height="140" xmlns="http://www.w3.org/2000/svg" style="background:#f6f7f9"><g fill="none" fill-rule="evenodd"><path fill="#F04141" style="mix-blend-mode:multiply" d="M61.905-34.23l96.194 54.51-66.982 54.512L22 34.887z"/><circle fill="#10DC60" style="mix-blend-mode:multiply" cx="155.5" cy="135.5" r="57.5"/><path fill="#3880FF" style="mix-blend-mode:multiply" d="M208.538 9.513l84.417 15.392L223.93 93.93z"/><path fill="#FFCE00" style="mix-blend-mode:multiply" d="M268.625 106.557l46.332-26.75 46.332 26.75v53.5l-46.332 26.75-46.332-26.75z"/><circle fill="#7044FF" style="mix-blend-mode:multiply" cx="299.5" cy="9.5" r="38.5"/><rect fill="#11D3EA" style="mix-blend-mode:multiply" transform="rotate(-60 148.47 37.886)" x="143.372" y="-7.056" width="10.196" height="89.884" rx="5.098"/><path d="M-25.389 74.253l84.86 8.107c5.498.525 9.53 5.407 9.004 10.905a10 10 0 0 1-.057.477l-12.36 85.671a10.002 10.002 0 0 1-11.634 8.42l-86.351-15.226c-5.44-.959-9.07-6.145-8.112-11.584l13.851-78.551a10 10 0 0 1 10.799-8.219z" fill="#7044FF" style="mix-blend-mode:multiply"/><circle fill="#0CD1E8" style="mix-blend-mode:multiply" cx="273.5" cy="106.5" r="20.5"/></g></svg>
|
After Width: | Height: | Size: 1.1 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/autumn.png
Normal file
After Width: | Height: | Size: 235 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/avatar.jpeg
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/avatar1.png
Normal file
After Width: | Height: | Size: 358 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/avatar2.png
Normal file
After Width: | Height: | Size: 424 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/avatar3.png
Normal file
After Width: | Height: | Size: 56 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/avatar4.png
Normal file
After Width: | Height: | Size: 133 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/avatar5.png
Normal file
After Width: | Height: | Size: 264 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/avatar6.png
Normal file
After Width: | Height: | Size: 67 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/cover1.jpeg
Normal file
After Width: | Height: | Size: 249 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/cover2.jpeg
Normal file
After Width: | Height: | Size: 216 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/cover4.jpeg
Normal file
After Width: | Height: | Size: 180 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/cover5.jpeg
Normal file
After Width: | Height: | Size: 110 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/cover6.jpeg
Normal file
After Width: | Height: | Size: 124 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/flower.jpeg
Normal file
After Width: | Height: | Size: 78 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/h.jpeg
Normal file
After Width: | Height: | Size: 92 KiB |
After Width: | Height: | Size: 930 B |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/icon/icon.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/ocean.jpeg
Normal file
After Width: | Height: | Size: 64 KiB |
@@ -0,0 +1 @@
|
||||
<svg width="350" height="140" xmlns="http://www.w3.org/2000/svg" style="background:#f6f7f9"><g fill="none" fill-rule="evenodd"><path fill="#F04141" style="mix-blend-mode:multiply" d="M61.905-34.23l96.194 54.51-66.982 54.512L22 34.887z"/><circle fill="#10DC60" style="mix-blend-mode:multiply" cx="155.5" cy="135.5" r="57.5"/><path fill="#3880FF" style="mix-blend-mode:multiply" d="M208.538 9.513l84.417 15.392L223.93 93.93z"/><path fill="#FFCE00" style="mix-blend-mode:multiply" d="M268.625 106.557l46.332-26.75 46.332 26.75v53.5l-46.332 26.75-46.332-26.75z"/><circle fill="#7044FF" style="mix-blend-mode:multiply" cx="299.5" cy="9.5" r="38.5"/><rect fill="#11D3EA" style="mix-blend-mode:multiply" transform="rotate(-60 148.47 37.886)" x="143.372" y="-7.056" width="10.196" height="89.884" rx="5.098"/><path d="M-25.389 74.253l84.86 8.107c5.498.525 9.53 5.407 9.004 10.905a10 10 0 0 1-.057.477l-12.36 85.671a10.002 10.002 0 0 1-11.634 8.42l-86.351-15.226c-5.44-.959-9.07-6.145-8.112-11.584l13.851-78.551a10 10 0 0 1 10.799-8.219z" fill="#7044FF" style="mix-blend-mode:multiply"/><circle fill="#0CD1E8" style="mix-blend-mode:multiply" cx="273.5" cy="106.5" r="20.5"/></g></svg>
|
After Width: | Height: | Size: 1.1 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/spring.png
Normal file
After Width: | Height: | Size: 288 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/summer.png
Normal file
After Width: | Height: | Size: 210 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/van.jpeg
Normal file
After Width: | Height: | Size: 174 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/winter.png
Normal file
After Width: | Height: | Size: 206 KiB |
@@ -78,6 +78,20 @@ import DemoDictionaryApp from './pages/DemoDictionaryApp';
|
||||
// demo-recipe-app
|
||||
import DemoRecipeApp from './pages/DemoRecipeApp';
|
||||
|
||||
// DemoSlidingProfile
|
||||
import DemoSlidingProfile from './pages/DemoSlidingProfile';
|
||||
|
||||
// DemoQuizApp
|
||||
import DemoQuizApp from './pages/DemoQuizApp';
|
||||
import DemoBlogPostUi from './pages/DemoBlogPostUi';
|
||||
import DemoReactTravelApp from './pages/DemoReactTravelApp';
|
||||
import DemoPinterestFloatingTabBar from './pages/DemoPinterestFloatingTabBar';
|
||||
import DemoRestaurantFinder from './pages/DemoRestaurantFinder';
|
||||
import DemoReactOverlayHooks from './pages/DemoReactOverlayHooks';
|
||||
import DemoReactSwitchTabs from './pages/DemoReactSwitchTabs';
|
||||
import DemoReactPollApp from './pages/DemoReactPollApp';
|
||||
import DemoReactWhatsAppClone from './pages/DemoReactWhatsAppClone';
|
||||
|
||||
setupIonicReact();
|
||||
|
||||
const App: React.FC = () => {
|
||||
@@ -102,14 +116,7 @@ interface DispatchProps {
|
||||
|
||||
interface IonicAppProps extends StateProps, DispatchProps {}
|
||||
|
||||
const IonicApp: React.FC<IonicAppProps> = ({
|
||||
darkMode,
|
||||
schedule,
|
||||
setIsLoggedIn,
|
||||
setUsername,
|
||||
loadConfData,
|
||||
loadUserData,
|
||||
}) => {
|
||||
const IonicApp: React.FC<IonicAppProps> = ({ darkMode, schedule, setIsLoggedIn, setUsername, loadConfData, loadUserData }) => {
|
||||
useEffect(() => {
|
||||
loadUserData();
|
||||
loadConfData();
|
||||
@@ -132,19 +139,34 @@ const IonicApp: React.FC<IonicAppProps> = ({
|
||||
|
||||
<AppRoute />
|
||||
|
||||
{/* */}
|
||||
<Route path="/tabs" render={() => <MainTabs />} />
|
||||
<Route path={paths.DEMO_REACT_SHOP} render={() => <DemoReactShop />} />
|
||||
<Route path={paths.DEMO_WEATHER_APP} render={() => <DemoWeatherApp />} />
|
||||
<Route path={paths.DEMO_CLUB_HOUSE} render={() => <DemoClubHouse />} />
|
||||
<Route path={paths.DEMO_SCORE_BOARD} render={() => <DemoScoreBoard />} />
|
||||
<Route path={paths.DEMO_QUOTE_APP} render={() => <DemoQuoteApp />} />
|
||||
<Route path={paths.DEMO_QR_SCANNER} render={() => <DemoQrScanner />} />
|
||||
<Route path={paths.DEMO_SHOP_APP_UI} render={() => <DemoShopAppUi />} />
|
||||
<Route path={paths.DEMO_DICTIONARY_APP} render={() => <DemoDictionaryApp />} />
|
||||
<Route path={paths.DEMO_RECIPE_APP} render={() => <DemoRecipeApp />} />
|
||||
|
||||
{/* */}
|
||||
{/* */}
|
||||
{/* */}
|
||||
{/* */}
|
||||
<Route path={paths.DEMO_REACT_WHATSAPP_CLONE} render={() => <DemoReactWhatsAppClone />} />
|
||||
|
||||
<Route path={paths.DEMO_REACT_POLL_APP} render={() => <DemoReactPollApp />} />
|
||||
|
||||
<Route path={paths.DEMO_BLOG_POST_UI} render={() => <DemoBlogPostUi />} />
|
||||
<Route path={paths.DEMO_CLUB_HOUSE} render={() => <DemoClubHouse />} />
|
||||
<Route path={paths.DEMO_DICTIONARY_APP} render={() => <DemoDictionaryApp />} />
|
||||
<Route path={paths.DEMO_PINTEREST_FLOATING_TAB_BAR} render={() => <DemoPinterestFloatingTabBar />} />
|
||||
<Route path={paths.DEMO_QR_SCANNER} render={() => <DemoQrScanner />} />
|
||||
<Route path={paths.DEMO_QUIZ_APP} render={() => <DemoQuizApp />} />
|
||||
<Route path={paths.DEMO_QUOTE_APP} render={() => <DemoQuoteApp />} />
|
||||
<Route path={paths.DEMO_REACT_OVERLAY_HOOKS} render={() => <DemoReactOverlayHooks />} />
|
||||
<Route path={paths.DEMO_REACT_POLL_APP} render={() => <DemoReactPollApp />} />
|
||||
<Route path={paths.DEMO_REACT_SHOP} render={() => <DemoReactShop />} />
|
||||
<Route path={paths.DEMO_REACT_SWITCH_TABS} render={() => <DemoReactSwitchTabs />} />
|
||||
<Route path={paths.DEMO_REACT_TRAVEL_APP} render={() => <DemoReactTravelApp />} />
|
||||
<Route path={paths.DEMO_RECIPE_APP} render={() => <DemoRecipeApp />} />
|
||||
<Route path={paths.DEMO_RESTAURANT_FINDER} render={() => <DemoRestaurantFinder />} />
|
||||
<Route path={paths.DEMO_SCORE_BOARD} render={() => <DemoScoreBoard />} />
|
||||
<Route path={paths.DEMO_SHOP_APP_UI} render={() => <DemoShopAppUi />} />
|
||||
<Route path={paths.DEMO_SLIDING_PROFILE} render={() => <DemoSlidingProfile />} />
|
||||
|
||||
<Route path="/account" component={Account} />
|
||||
<Route path="/login" component={Login} />
|
||||
<Route path="/mylogin" component={MyLogin} />
|
||||
@@ -153,7 +175,6 @@ const IonicApp: React.FC<IonicAppProps> = ({
|
||||
<Route path="/support" component={Support} />
|
||||
<Route path="/tutorial" component={Tutorial} />
|
||||
|
||||
{/* */}
|
||||
<Route
|
||||
path="/logout"
|
||||
render={() => {
|
||||
|
@@ -0,0 +1,96 @@
|
||||
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;
|
@@ -0,0 +1,81 @@
|
||||
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;
|
@@ -0,0 +1,29 @@
|
||||
import { IonAccordion, IonAccordionGroup, IonIcon, IonItem, IonLabel, IonList } from '@ionic/react';
|
||||
import { topics } from '../data';
|
||||
|
||||
export const Accordion = () => {
|
||||
return (
|
||||
<IonAccordionGroup>
|
||||
{topics.map((topic, index) => {
|
||||
return (
|
||||
<IonAccordion key={`accordion_${index}`} value={topic.header.toLowerCase()}>
|
||||
<IonItem slot="header">
|
||||
<IonIcon icon={topic.icon} color={topic.color} />
|
||||
<IonLabel className="ion-padding-start">{topic.header}</IonLabel>
|
||||
</IonItem>
|
||||
|
||||
<IonList slot="content">
|
||||
{topic.options.map((option, index2) => {
|
||||
return (
|
||||
<IonItem key={`option_${index}_${index2}`} routerLink={`/topics/${option.label}`}>
|
||||
<IonLabel>{option.label}</IonLabel>
|
||||
</IonItem>
|
||||
);
|
||||
})}
|
||||
</IonList>
|
||||
</IonAccordion>
|
||||
);
|
||||
})}
|
||||
</IonAccordionGroup>
|
||||
);
|
||||
};
|
@@ -0,0 +1,62 @@
|
||||
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>
|
||||
);
|
||||
};
|
@@ -0,0 +1,48 @@
|
||||
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>
|
||||
);
|
@@ -0,0 +1,117 @@
|
||||
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>
|
||||
);
|
120
03_source/mobile/src/pages/DemoAccordionTutorial/data.js
Normal file
@@ -0,0 +1,120 @@
|
||||
import { bicycleOutline, fastFoodOutline, filmOutline, gameControllerOutline, libraryOutline } from 'ionicons/icons';
|
||||
|
||||
export const topics = [
|
||||
{
|
||||
header: 'Attractions',
|
||||
color: 'primary',
|
||||
icon: filmOutline,
|
||||
options: [
|
||||
{
|
||||
label: 'Cinema',
|
||||
},
|
||||
{
|
||||
label: 'Bowling Alley',
|
||||
},
|
||||
{
|
||||
label: 'Crazy Golf',
|
||||
},
|
||||
{
|
||||
label: 'Theme Park',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
header: 'Dining',
|
||||
color: 'success',
|
||||
icon: fastFoodOutline,
|
||||
options: [
|
||||
{
|
||||
label: 'Breakfast & Brunch',
|
||||
},
|
||||
{
|
||||
label: 'New American',
|
||||
},
|
||||
{
|
||||
label: 'Sushi Bars',
|
||||
},
|
||||
{
|
||||
label: 'Filipino Food',
|
||||
},
|
||||
{
|
||||
label: 'Asian Fusion',
|
||||
},
|
||||
{
|
||||
label: 'Ramen Houses',
|
||||
},
|
||||
{
|
||||
label: 'Dinner Venues',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
header: 'Gaming',
|
||||
color: 'warning',
|
||||
icon: gameControllerOutline,
|
||||
options: [
|
||||
{
|
||||
label: 'Xbox',
|
||||
},
|
||||
{
|
||||
label: 'Playstation',
|
||||
},
|
||||
{
|
||||
label: 'Nintendo Switch',
|
||||
},
|
||||
{
|
||||
label: 'PC',
|
||||
},
|
||||
{
|
||||
label: 'Mobile',
|
||||
},
|
||||
{
|
||||
label: 'Dreamcast',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
header: 'Exercise',
|
||||
color: 'secondary',
|
||||
icon: bicycleOutline,
|
||||
options: [
|
||||
{
|
||||
label: 'Yoga',
|
||||
},
|
||||
{
|
||||
label: 'Pilates',
|
||||
},
|
||||
{
|
||||
label: 'Weight Training',
|
||||
},
|
||||
{
|
||||
label: 'Cardio',
|
||||
},
|
||||
{
|
||||
label: 'Zumba',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
header: 'Education',
|
||||
color: 'danger',
|
||||
icon: libraryOutline,
|
||||
options: [
|
||||
{
|
||||
label: 'School',
|
||||
},
|
||||
{
|
||||
label: 'High School',
|
||||
},
|
||||
{
|
||||
label: 'University Bachelors',
|
||||
},
|
||||
{
|
||||
label: 'University Masters',
|
||||
},
|
||||
{
|
||||
label: 'University pHD',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
54
03_source/mobile/src/pages/DemoAccordionTutorial/index.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
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 Topic from './pages/Topic.jsx';
|
||||
import Home from './pages/Home.jsx';
|
||||
|
||||
import './style.scss';
|
||||
|
||||
function DemoAccordionTutorial() {
|
||||
return (
|
||||
<IonTabs className="demo-accordion-tutorial">
|
||||
<IonRouterOutlet>
|
||||
{/* <Route exact path="/demo-accordion-tutorial/tab1">
|
||||
<Tab1 />
|
||||
</Route>
|
||||
<Route exact path="/demo-accordion-tutorial/tab2">
|
||||
<Tab2 />
|
||||
</Route> */}
|
||||
|
||||
<Route exact path="/demo-accordion-tutorial/home">
|
||||
<Home />
|
||||
</Route>
|
||||
|
||||
<Route exact path="/demo-accordion-tutorial/topics/:topic">
|
||||
<Topic />
|
||||
</Route>
|
||||
|
||||
<Redirect exact path="/demo-accordion-tutorial" to="/home" />
|
||||
|
||||
{/* <Redirect exact path="/demo-accordion-tutorial" to="/demo-accordion-tutorial/tab1" /> */}
|
||||
</IonRouterOutlet>
|
||||
|
||||
{/*
|
||||
<IonTabBar slot="bottom">
|
||||
<IonTabButton tab="tab1" href="/demo-accordion-tutorial/tab1">
|
||||
<IonIcon icon={cloudOutline} />
|
||||
<IonLabel>Dashboard</IonLabel>
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="tab2" href="/demo-accordion-tutorial/tab2">
|
||||
<IonIcon icon={searchOutline} />
|
||||
<IonLabel>Search</IonLabel>
|
||||
</IonTabButton>
|
||||
</IonTabBar>
|
||||
*/}
|
||||
</IonTabs>
|
||||
);
|
||||
}
|
||||
|
||||
export default DemoAccordionTutorial;
|
@@ -0,0 +1,97 @@
|
||||
import {
|
||||
IonAccordion,
|
||||
IonAccordionGroup,
|
||||
IonButton,
|
||||
IonButtons,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonIcon,
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonList,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
useIonRouter,
|
||||
} from '@ionic/react';
|
||||
import { Accordion } from '../components/Accordion';
|
||||
import { chevronBackOutline } from 'ionicons/icons';
|
||||
|
||||
const Home = () => {
|
||||
const router = useIonRouter();
|
||||
|
||||
function handleBackClick() {
|
||||
router.goBack();
|
||||
}
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Accordion</IonTitle>
|
||||
|
||||
<IonButtons slot="start">
|
||||
<IonButton onClick={() => handleBackClick()}>
|
||||
<IonIcon icon={chevronBackOutline} color="primary" />
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">Accordion</IonTitle>
|
||||
|
||||
<IonButtons slot="start">
|
||||
<IonButton onClick={() => handleBackClick()}>
|
||||
<IonIcon icon={chevronBackOutline} color="primary" />
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<Accordion />
|
||||
|
||||
{/* <IonAccordionGroup>
|
||||
<IonAccordion>
|
||||
<IonItem slot="header">
|
||||
<IonLabel>Languages</IonLabel>
|
||||
</IonItem>
|
||||
|
||||
<IonList slot="content">
|
||||
<IonItem>
|
||||
<IonLabel>English</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem>
|
||||
<IonLabel>Spanish</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem>
|
||||
<IonLabel>Italian</IonLabel>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
</IonAccordion>
|
||||
|
||||
<IonAccordion>
|
||||
<IonItem slot="header">
|
||||
<IonLabel>Languages 2</IonLabel>
|
||||
</IonItem>
|
||||
|
||||
<IonList slot="content">
|
||||
<IonItem>
|
||||
<IonLabel>English</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem>
|
||||
<IonLabel>Spanish</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem>
|
||||
<IonLabel>Italian</IonLabel>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
</IonAccordion>
|
||||
</IonAccordionGroup> */}
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
@@ -0,0 +1,39 @@
|
||||
import { IonBackButton, IonButtons, IonCol, IonContent, IonGrid, IonHeader, IonLabel, IonPage, IonRow, IonTitle, IonToolbar } from '@ionic/react';
|
||||
import { useParams } from 'react-router';
|
||||
|
||||
const Topic = () => {
|
||||
|
||||
const { topic } = useParams();
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
|
||||
<IonButtons slot="start">
|
||||
<IonBackButton text="Topics" />
|
||||
</IonButtons>
|
||||
|
||||
<IonTitle>{ topic }</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">{ topic }</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonGrid>
|
||||
<IonRow>
|
||||
<IonCol size="12">
|
||||
<IonLabel>This is the page for the topic: { topic }.</IonLabel>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonGrid>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Topic;
|
237
03_source/mobile/src/pages/DemoAccordionTutorial/style.scss
Normal file
@@ -0,0 +1,237 @@
|
||||
.demo-accordion-tutorial {
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
.view-post-footer {
|
||||
|
||||
background-color: white;
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
}
|
@@ -0,0 +1,90 @@
|
||||
import {
|
||||
IonBackButton,
|
||||
IonBadge,
|
||||
IonButton,
|
||||
IonButtons,
|
||||
IonCardSubtitle,
|
||||
IonCardTitle,
|
||||
IonCol,
|
||||
IonContent,
|
||||
IonFooter,
|
||||
IonGrid,
|
||||
IonHeader,
|
||||
IonIcon,
|
||||
IonNote,
|
||||
IonPage,
|
||||
IonRow,
|
||||
IonText,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
} from '@ionic/react';
|
||||
import { bookmarkOutline, shareOutline } from 'ionicons/icons';
|
||||
import { useParams } from 'react-router';
|
||||
import { blogPosts } from '../localData';
|
||||
import './BlogPost.css';
|
||||
|
||||
const BlogPost = () => {
|
||||
const { id } = useParams();
|
||||
const post = blogPosts.filter((post) => parseInt(post.id) === parseInt(id))[0];
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Blog</IonTitle>
|
||||
<IonButtons slot="start">
|
||||
<IonBackButton text="Blog Posts" />
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent fullscreen>
|
||||
<img src={post.image} alt="post header" />
|
||||
|
||||
<IonGrid className="ion-padding-start ion-padding-end">
|
||||
<IonRow className="ion-align-items-center ion-justify-content-between">
|
||||
<IonRow className="ion-align-items-center ion-justify-content-between">
|
||||
<img src={post.authorImage} className="post-author-avatar" alt="post author" />
|
||||
<IonCardSubtitle className="ion-no-margin ion-no-padding ion-margin-start">
|
||||
{post.author}
|
||||
</IonCardSubtitle>
|
||||
</IonRow>
|
||||
<IonNote>{post.date}</IonNote>
|
||||
</IonRow>
|
||||
|
||||
<IonRow>
|
||||
<IonCol size="12">
|
||||
<IonCardTitle className="post-title">{post.title}</IonCardTitle>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<IonRow>
|
||||
<IonCol size="12">
|
||||
<IonText color="medium">{post.content}</IonText>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonGrid>
|
||||
</IonContent>
|
||||
|
||||
<IonFooter className="view-post-footer">
|
||||
<IonRow className="post-footer ion-align-self-center ion-justify-content-between">
|
||||
<div>
|
||||
<IonButton fill="clear" color="primary">
|
||||
<IonIcon icon={shareOutline} />
|
||||
</IonButton>
|
||||
<IonButton fill="clear" color="primary">
|
||||
<IonIcon icon={bookmarkOutline} />
|
||||
</IonButton>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<IonBadge color="primary" className="post-category">
|
||||
{post.category}
|
||||
</IonBadge>
|
||||
</div>
|
||||
</IonRow>
|
||||
</IonFooter>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default BlogPost;
|
51
03_source/mobile/src/pages/DemoBlogPostUi/AppPages/Home.jsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import {
|
||||
IonButton,
|
||||
IonButtons,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonIcon,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
useIonRouter,
|
||||
} from '@ionic/react';
|
||||
import { Post } from '../components/Post';
|
||||
import { blogPosts } from '../localData';
|
||||
import { chevronBackOutline } from 'ionicons/icons';
|
||||
|
||||
const Home = () => {
|
||||
const router = useIonRouter();
|
||||
|
||||
function handleBackClick() {
|
||||
router.goBack();
|
||||
}
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Ionic Blog</IonTitle>
|
||||
|
||||
<IonButtons slot="start">
|
||||
<IonButton onClick={() => handleBackClick()}>
|
||||
<IonIcon icon={chevronBackOutline} color="primary" />
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">Ionic Blog</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
{blogPosts.map((post, index) => (
|
||||
<Post post={post} key={`post_${index}`} />
|
||||
))}
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
@@ -0,0 +1,37 @@
|
||||
.post-author-avatar {
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
border-radius: 500px;
|
||||
}
|
||||
|
||||
.post-title {
|
||||
font-size: 1.4rem;
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.post-content {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.post-footer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-content: center;
|
||||
width: 100%;
|
||||
border-top: 2px solid rgb(245, 245, 245);
|
||||
margin-top: 2rem;
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.post-category {
|
||||
margin-top: 1.1rem;
|
||||
}
|
||||
|
||||
.post-image {
|
||||
width: 100%;
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
import {
|
||||
IonBadge,
|
||||
IonButton,
|
||||
IonCard,
|
||||
IonCardContent,
|
||||
IonCardHeader,
|
||||
IonCardSubtitle,
|
||||
IonCardTitle,
|
||||
IonIcon,
|
||||
IonNote,
|
||||
IonRow,
|
||||
} from '@ionic/react';
|
||||
import { bookmarkOutline, shareOutline } from 'ionicons/icons';
|
||||
|
||||
import './Post.css';
|
||||
|
||||
export const Post = ({ post }) => {
|
||||
return (
|
||||
<IonCard routerLink={`/demo-blog-post-ui/post/${post.id}`}>
|
||||
<img src={post.image} alt="main post" className="post-image" />
|
||||
|
||||
<IonCardHeader>
|
||||
<IonRow className="ion-align-items-center ion-justify-content-between">
|
||||
<IonRow className="ion-align-items-center ion-justify-content-between">
|
||||
<img src={post.authorImage} className="post-author-avatar" alt="post author" />
|
||||
<IonCardSubtitle className="ion-no-margin ion-no-padding ion-margin-start">
|
||||
{post.author}
|
||||
</IonCardSubtitle>
|
||||
</IonRow>
|
||||
<IonNote>{post.date}</IonNote>
|
||||
</IonRow>
|
||||
<IonCardTitle className="post-title">{post.title}</IonCardTitle>
|
||||
</IonCardHeader>
|
||||
|
||||
<IonCardContent>
|
||||
<p className="post-content">{post.content}</p>
|
||||
|
||||
<IonRow className="post-footer ion-align-self-center ion-justify-content-between">
|
||||
<div>
|
||||
<IonButton fill="clear" color="primary">
|
||||
<IonIcon icon={shareOutline} />
|
||||
</IonButton>
|
||||
<IonButton fill="clear" color="primary">
|
||||
<IonIcon icon={bookmarkOutline} />
|
||||
</IonButton>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<IonBadge color="primary" className="post-category">
|
||||
{post.category}
|
||||
</IonBadge>
|
||||
</div>
|
||||
</IonRow>
|
||||
</IonCardContent>
|
||||
</IonCard>
|
||||
);
|
||||
};
|
27
03_source/mobile/src/pages/DemoBlogPostUi/index.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { IonIcon, IonLabel, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs } from '@ionic/react';
|
||||
|
||||
import { cloudOutline, searchOutline } from 'ionicons/icons';
|
||||
import { Route, Redirect } from 'react-router';
|
||||
|
||||
import Home from './AppPages/Home';
|
||||
import BlogPost from './AppPages/BlogPost';
|
||||
|
||||
import './style.scss';
|
||||
|
||||
function DemoBlogPostUi() {
|
||||
return (
|
||||
<IonRouterOutlet className="demo-blog-post-ui">
|
||||
<Route exact path="/demo-blog-post-ui/home">
|
||||
<Home />
|
||||
</Route>
|
||||
|
||||
<Route exact path="/demo-blog-post-ui/post/:id">
|
||||
<BlogPost />
|
||||
</Route>
|
||||
|
||||
<Redirect exact path="/demo-blog-post-ui" to="/demo-blog-post-ui/home" />
|
||||
</IonRouterOutlet>
|
||||
);
|
||||
}
|
||||
|
||||
export default DemoBlogPostUi;
|
98
03_source/mobile/src/pages/DemoBlogPostUi/localData/index.js
Normal file
@@ -0,0 +1,98 @@
|
||||
export const blogPosts = [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "How to Convince Your Boss to Choose Ionic",
|
||||
"title_link": "https://ionicframework.com/blog/convince-boss-choose-ionic-app-development/",
|
||||
"date": "August 3, 2021",
|
||||
"author": "By Kim Maida",
|
||||
"authorImage": "https://ionicframework.com/blog/wp-content/uploads/2021/07/kim-maida-150x150.jpg",
|
||||
"category": "ANNOUNCEMENTS",
|
||||
"category_link": "https://ionicframework.com/blog//blog/category/announcements",
|
||||
"image": "https://ionicframework.com/blog/wp-content/uploads/2021/07/how-to-convince-your-boss_image_1aug2021.png",
|
||||
"content": "Greetings, friend! You’re a web developer, team lead, or engineering manager who has discovered that Ionic products are awesome. They have helped you build cross-platform applications quickly, made the app development process enjoyable, and solved important mobile development problems. You can see that Ionic would be extremely beneficial in your daily job, but are wondering how to convince your boss to endorse the adoption of new software. In a nutshell:"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"title": "Ioniconf 2021 Conference Recap",
|
||||
"title_link": "https://ionicframework.com/blog/ioniconf-2021-conference-recap/",
|
||||
"date": "July 29, 2021",
|
||||
"author": "By Mike Hartington",
|
||||
"authorImage": "https://ionicframework.com/blog/wp-content/uploads/2018/08/mike-headshot-2-smaller-150x150.png",
|
||||
"category": "ANNOUNCEMENTS",
|
||||
"category_link": "https://ionicframework.com/blog//blog/category/announcements",
|
||||
"image": "https://ionicframework.com/blog/wp-content/uploads/2021/06/og-imgx2.png",
|
||||
"content": "And with that, Ioniconf 2021 has concluded! Ioniconf, our online conference for Ionic developers and the wider web development community, featured twelve expert Ionic speakers and was attended by many thousands of Ionic community members. We’re thrilled by the community’s reception to the event and are already looking forward to our next event taking place in September. Read on for a recap and links to all recorded talks."
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"title": "Announcing Identity Vault 5.0",
|
||||
"title_link": "https://ionicframework.com/blog/announcing-identity-vault-5-0/",
|
||||
"date": "July 28, 2021",
|
||||
"author": "By Dallas James",
|
||||
"authorImage": "https://ionicframework.com/blog/wp-content/uploads/2021/07/dallas-james-150x150.jpg",
|
||||
"category": "PRODUCT",
|
||||
"category_link": "https://ionicframework.com/blog//blog/category/announcements",
|
||||
"image": "https://ionicframework.com/blog/wp-content/uploads/2021/07/iv-5-feature-image.png",
|
||||
"content": "Today I’m excited to announce Identity Vault 5.0, the newest version of Ionic’s mobile biometrics solution. Featuring the latest in native security best practices, Identity Vault improves frontend security in any Ionic app by making it easy to add secure biometric authentication in minutes."
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"title": "Building with Stencil: Clock Component",
|
||||
"title_link": "https://ionicframework.com/blog/building-with-stencil-clock-component/",
|
||||
"date": "July 22, 2021",
|
||||
"author": "By Kevin Hoyt",
|
||||
"authorImage": "https://ionicframework.com/blog/wp-content/uploads/2021/07/2520666-150x150.jpg",
|
||||
"category": "ANNOUNCEMENTS",
|
||||
"category_link": "https://ionicframework.com/blog//blog/category/announcements",
|
||||
"image": "https://ionicframework.com/blog/wp-content/uploads/2021/07/Image-from-iOS.png",
|
||||
"content": "I have not seen a clock in a web-based user interface in a long time. This makes sense — they are pretty redundant these days. You have a clock on your watch, on your mobile device, and on your desktop, and those are just the digital versions available at a glance. Nonetheless, the process of building a clock can reveal a lot about how a platform works."
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"title": "Building with Stencil: Calendar Component",
|
||||
"title_link": "https://ionicframework.com/blog/building-with-stencil-calendar-component/",
|
||||
"date": "July 19, 2021",
|
||||
"author": "By Kevin Hoyt",
|
||||
"authorImage": "https://ionicframework.com/blog/wp-content/uploads/2021/07/2520666-150x150.jpg",
|
||||
"category": "TUTORIALS",
|
||||
"category_link": null,
|
||||
"image": "https://ionicframework.com/blog/wp-content/uploads/2021/07/ionic-blog-post-image_first-look-01.png",
|
||||
"content": "Take a look at the month view of a calendar and you will see several rows of numbers. The numbers themselves, increasing in value one after the other, are arranged in columns. HTML and CSS provide us with a number of tools to display content in rows and columns. Making a calendar component should be easy, right? Right?"
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"title": "Introducing the New Overlay Hooks for Ionic React",
|
||||
"title_link": "https://ionicframework.com/blog/introducing-the-new-overlay-hooks-for-ionic-react/",
|
||||
"date": "July 14, 2021",
|
||||
"author": "By Ely Lucas",
|
||||
"authorImage": "https://secure.gravatar.com/avatar/45ad19965b4bde97e9f4396ea01ed184?s=32&r=g",
|
||||
"category": "ENGINEERING",
|
||||
"category_link": null,
|
||||
"image": "https://ionicframework.com/blog/wp-content/uploads/2021/07/react-overlay-hooks-feature-image.png",
|
||||
"content": "Hello Friends! We know everyone is excited about the new features in Ionic Framework 6.0 beta, but that doesn’t mean we’re done with V5! In Ionic React 5.6, we packaged up a new set of hooks for controlling our overlay components that we think you might like. What is an overlay you ask? It’s the term we give components that display over your current content, such as alerts, modals, toasts, etc."
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"title": "The Future of Stencil: Expanded Team, New Software Platform, and More",
|
||||
"title_link": "https://ionicframework.com/blog/the-future-of-stencil-expanded-team-new-software-platform-and-more/",
|
||||
"date": "July 7, 2021",
|
||||
"author": "By Nick Hyatt",
|
||||
"authorImage": "https://ionicframework.com/blog/wp-content/uploads/2018/11/Nick-Hyatt-Headshot-150x150.jpeg",
|
||||
"category": "ANNOUNCEMENTS",
|
||||
"category_link": null,
|
||||
"image": "https://ionicframework.com/blog/wp-content/uploads/2021/07/stencil-future-feature-image.png",
|
||||
"content": "Today I’m excited to share some news about Stencil, Ionic’s open source toolchain that generates small, fast, and 100% standards-based Web Components that run in every browser. As you might have noticed, we’ve been actively increasing our investments across the entire Ionic App Platform, including the recent launch of Capacitor 3.0, Ionic Portals, tons of Appflow improvements, and the upcoming Ionic Framework v6."
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"title": "Announcing the Ionic Framework v6 Beta",
|
||||
"title_link": "https://ionicframework.com/blog/announcing-the-ionic-framework-v6-beta/",
|
||||
"date": "June 29, 2021",
|
||||
"author": "By Liam DeBeasi",
|
||||
"authorImage": "https://ionicframework.com/blog/wp-content/uploads/2020/01/ZNK4lRAJ_400x400-150x150.jpg",
|
||||
"category": "ANNOUNCEMENTS",
|
||||
"category_link": null,
|
||||
"image": "https://ionicframework.com/blog/wp-content/uploads/2021/06/framework6-feature-image.png",
|
||||
"content": "Earlier this week I had the privilege of giving the Ionic Framework Update at Ioniconf 2021 where we announced the Ionic Framework v6 beta. Ionic Framework has come far from its roots as an AngularJS-only UI library to a truly cross-platform framework for building Web Native applications. As we look to the future of Ionic Framework, let’s talk about some of the improvements coming in Framework v6 and how you can get access to these improvements today."
|
||||
}
|
||||
];
|
79
03_source/mobile/src/pages/DemoBlogPostUi/style.scss
Normal file
@@ -0,0 +1,79 @@
|
||||
.demo-blog-post-ui {
|
||||
/* 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;
|
||||
}
|
||||
}
|
@@ -48,23 +48,33 @@ import {
|
||||
alertOutline,
|
||||
apps,
|
||||
appsOutline,
|
||||
book,
|
||||
car,
|
||||
cart,
|
||||
chatbubbleEllipses,
|
||||
chatbubbleOutline,
|
||||
chevronBackOutline,
|
||||
chevronForward,
|
||||
chevronForwardOutline,
|
||||
createOutline,
|
||||
document,
|
||||
documentTextOutline,
|
||||
gift,
|
||||
giftOutline,
|
||||
globeSharp,
|
||||
heart,
|
||||
languageOutline,
|
||||
layers,
|
||||
listCircle,
|
||||
menuOutline,
|
||||
people,
|
||||
person,
|
||||
restaurant,
|
||||
settingsOutline,
|
||||
shareSocialOutline,
|
||||
statsChart,
|
||||
sunny,
|
||||
swapHorizontal,
|
||||
trashOutline,
|
||||
} from 'ionicons/icons';
|
||||
import AboutPopover from '../../components/AboutPopover';
|
||||
@@ -87,13 +97,7 @@ interface DispatchProps {
|
||||
|
||||
interface SettingsProps extends OwnProps, StateProps, DispatchProps {}
|
||||
|
||||
const SettingsPage: React.FC<SettingsProps> = ({
|
||||
speakers,
|
||||
speakerSessions,
|
||||
logoutUser,
|
||||
setAccessToken,
|
||||
setIsLoggedIn,
|
||||
}) => {
|
||||
const SettingsPage: React.FC<SettingsProps> = ({ speakers, speakerSessions, logoutUser, setAccessToken, setIsLoggedIn }) => {
|
||||
const [events, setEvents] = useState<Event[] | []>([]);
|
||||
const [showPopover, setShowPopover] = useState(false);
|
||||
const [popoverEvent, setPopoverEvent] = useState<MouseEvent>();
|
||||
@@ -190,6 +194,72 @@ const SettingsPage: React.FC<SettingsProps> = ({
|
||||
</IonItem>
|
||||
</IonList>
|
||||
|
||||
{/* */}
|
||||
{/* */}
|
||||
{/* */}
|
||||
{/* */}
|
||||
{/* */}
|
||||
{/* */}
|
||||
{/* */}
|
||||
{/* */}
|
||||
<IonList inset={false}>
|
||||
<IonItem button={true} onClick={() => router.push(paths.DEMO_REACT_WHATSAPP_CLONE, 'forward')}>
|
||||
<IonIcon slot="start" icon={chatbubbleEllipses} size="large"></IonIcon>
|
||||
<IonLabel>
|
||||
Demo React WhatsApp Clone <span style={{ fontWeight: 'bold' }}>(need to resolve path problem)</span>
|
||||
</IonLabel>
|
||||
<IonIcon icon={chevronForwardOutline}></IonIcon>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
|
||||
<IonList inset={false}>
|
||||
<IonItem button={true} onClick={() => router.push(paths.DEMO_REACT_POLL_APP, 'forward')}>
|
||||
<IonIcon slot="start" icon={statsChart} size="large"></IonIcon>
|
||||
<IonLabel>
|
||||
Demo React Poll App <span style={{ fontWeight: 'bold' }}>(css temporary broken, ignored)</span>
|
||||
</IonLabel>
|
||||
<IonIcon icon={chevronForwardOutline}></IonIcon>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
|
||||
<IonList inset={false}>
|
||||
<IonItem button={true} onClick={() => router.push(paths.DEMO_REACT_SWITCH_TABS, 'forward')}>
|
||||
<IonIcon slot="start" icon={swapHorizontal} size="large"></IonIcon>
|
||||
<IonLabel>
|
||||
Demo React Switch Tabs <span style={{ fontWeight: 'bold' }}>(hardcoded back button)</span>
|
||||
</IonLabel>
|
||||
<IonIcon icon={chevronForwardOutline}></IonIcon>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
|
||||
<IonList inset={false}>
|
||||
<IonItem button={true} onClick={() => router.push(paths.DEMO_REACT_OVERLAY_HOOKS, 'forward')}>
|
||||
<IonIcon slot="start" icon={layers} size="large"></IonIcon>
|
||||
<IonLabel>Demo React Overlay Hooks</IonLabel>
|
||||
<IonIcon icon={chevronForwardOutline}></IonIcon>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
|
||||
<IonList inset={false}>
|
||||
<IonItem button={true} onClick={() => router.push(paths.DEMO_PINTEREST_FLOATING_TAB_BAR, 'forward')}>
|
||||
<IonIcon slot="start" icon={people} size="large"></IonIcon>
|
||||
<IonLabel>
|
||||
Demo Pinterest Floating Tab Bar <span style={{ fontWeight: 'bold' }}>(css not work well)</span>
|
||||
</IonLabel>
|
||||
<IonIcon icon={chevronForwardOutline}></IonIcon>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
|
||||
<IonList inset={false}>
|
||||
<IonItem button={true} onClick={() => router.push(paths.DEMO_RESTAURANT_FINDER, 'forward')}>
|
||||
<IonIcon slot="start" icon={restaurant} size="large"></IonIcon>
|
||||
<IonLabel>
|
||||
Demo Restaurant Finder <span style={{ fontWeight: 'bold' }}>need server for map showing</span>
|
||||
</IonLabel>
|
||||
<IonIcon icon={chevronForwardOutline}></IonIcon>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
|
||||
<IonList inset={false}>
|
||||
<IonItem button={true} onClick={() => handleDemoReactShopClick()}>
|
||||
<IonIcon slot="start" icon={cart} size="large"></IonIcon>
|
||||
@@ -227,8 +297,6 @@ const SettingsPage: React.FC<SettingsProps> = ({
|
||||
</IonItem>
|
||||
</IonList>
|
||||
|
||||
{/* */}
|
||||
|
||||
<IonList inset={false}>
|
||||
<IonItem button={true} onClick={() => router.push(paths.DEMO_QUOTE_APP, 'forward')}>
|
||||
<IonIcon slot="start" icon={car} size="large"></IonIcon>
|
||||
@@ -261,7 +329,6 @@ const SettingsPage: React.FC<SettingsProps> = ({
|
||||
</IonItem>
|
||||
</IonList>
|
||||
|
||||
{/* */}
|
||||
<IonList inset={false}>
|
||||
<IonItem button={true} onClick={() => router.push(paths.DEMO_RECIPE_APP, 'forward')}>
|
||||
<IonIcon slot="start" icon={cart} size="large"></IonIcon>
|
||||
@@ -269,14 +336,44 @@ const SettingsPage: React.FC<SettingsProps> = ({
|
||||
<IonIcon icon={chevronForwardOutline}></IonIcon>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
|
||||
<IonList inset={false}>
|
||||
<IonItem button={true} onClick={() => router.push(paths.DEMO_SLIDING_PROFILE, 'forward')}>
|
||||
<IonIcon slot="start" icon={person} size="large"></IonIcon>
|
||||
<IonLabel>Demo Sliding Profile</IonLabel>
|
||||
<IonIcon icon={chevronForwardOutline}></IonIcon>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
|
||||
<IonList inset={false}>
|
||||
<IonItem button={true} onClick={() => router.push(paths.DEMO_QUIZ_APP, 'forward')}>
|
||||
<IonIcon slot="start" icon={book} size="large"></IonIcon>
|
||||
<IonLabel>Demo Quiz App</IonLabel>
|
||||
<IonIcon icon={chevronForwardOutline}></IonIcon>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
|
||||
<IonList inset={false}>
|
||||
<IonItem button={true} onClick={() => router.push(paths.DEMO_BLOG_POST_UI, 'forward')}>
|
||||
<IonIcon slot="start" icon={document} size="large"></IonIcon>
|
||||
<IonLabel>Demo Blog Post UI</IonLabel>
|
||||
<IonIcon icon={chevronForwardOutline}></IonIcon>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
|
||||
<IonList inset={false}>
|
||||
<IonItem button={true} onClick={() => router.push(paths.DEMO_REACT_TRAVEL_APP, 'forward')}>
|
||||
<IonIcon slot="start" icon={globeSharp} size="large"></IonIcon>
|
||||
<IonLabel>
|
||||
Demo React Travel App <span style={{ fontWeight: 'bold' }}>(on hold)</span>
|
||||
</IonLabel>
|
||||
<IonIcon icon={chevronForwardOutline}></IonIcon>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
</IonContent>
|
||||
|
||||
{/* REQ0058/logout */}
|
||||
<IonModal
|
||||
isOpen={showLogoutConfirmModal}
|
||||
initialBreakpoint={0.5}
|
||||
breakpoints={[0, 0.25, 0.5, 0.75]}
|
||||
>
|
||||
<IonModal isOpen={showLogoutConfirmModal} initialBreakpoint={0.5} breakpoints={[0, 0.25, 0.5, 0.75]}>
|
||||
<IonContent
|
||||
className="ion-padding"
|
||||
style={{
|
||||
|
@@ -0,0 +1,49 @@
|
||||
import {
|
||||
IonButton,
|
||||
IonButtons,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonIcon,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
useIonRouter,
|
||||
} from '@ionic/react';
|
||||
import ExploreContainer from '../components/ExploreContainer';
|
||||
|
||||
import './Tab1.css';
|
||||
import { chevronBackOutline } from 'ionicons/icons';
|
||||
|
||||
const Tab1 = () => {
|
||||
const router = useIonRouter();
|
||||
|
||||
function handleBackClick() {
|
||||
router.goBack();
|
||||
}
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Tab 1</IonTitle>
|
||||
{/* */}
|
||||
<IonButtons slot="start">
|
||||
<IonButton onClick={() => handleBackClick()}>
|
||||
<IonIcon icon={chevronBackOutline} color="primary" />
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">Tab 1</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<ExploreContainer name="Tab 1 page" />
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Tab1;
|
@@ -0,0 +1,25 @@
|
||||
import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from '@ionic/react';
|
||||
import ExploreContainer from '../components/ExploreContainer';
|
||||
import './Tab2.css';
|
||||
|
||||
const Tab2 = () => {
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Tab 2</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">Tab 2</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<ExploreContainer name="Tab 2 page" />
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Tab2;
|
@@ -0,0 +1,25 @@
|
||||
import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from '@ionic/react';
|
||||
import ExploreContainer from '../components/ExploreContainer';
|
||||
import './Tab3.css';
|
||||
|
||||
const Tab3 = () => {
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Tab 3</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">Tab 3</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<ExploreContainer name="Tab 3 page" />
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Tab3;
|
@@ -0,0 +1,25 @@
|
||||
import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from '@ionic/react';
|
||||
import ExploreContainer from '../components/ExploreContainer';
|
||||
import './Tab3.css';
|
||||
|
||||
const Tab4 = () => {
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Tab 3</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">Tab 4</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<ExploreContainer name="Tab 4 page" />
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Tab4;
|
@@ -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,12 @@
|
||||
import './ExploreContainer.css';
|
||||
|
||||
const ExploreContainer = ({ name }) => {
|
||||
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;
|
@@ -0,0 +1,29 @@
|
||||
.custom-tab-bar {
|
||||
* {
|
||||
/* --ion-background-color: white; */
|
||||
--ion-tab-bar-color: var(--tab-color);
|
||||
--ion-tab-bar-color-selected: var(--tab-color-selected);
|
||||
}
|
||||
|
||||
ion-tab-bar {
|
||||
--background: var(--tab-background);
|
||||
box-shadow: 0px 1px 8px rgba(0, 0, 0, 0.4);
|
||||
border-radius: 50px !important;
|
||||
|
||||
height: 50px;
|
||||
width: 50%;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
|
||||
bottom: 20px;
|
||||
position: relative;
|
||||
margin: 0 auto !important;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
ion-tab-button {
|
||||
border-radius: 16px !important;
|
||||
}
|
||||
}
|
@@ -0,0 +1,54 @@
|
||||
import { IonIcon, IonLabel, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs } from '@ionic/react';
|
||||
|
||||
import { chatbubble, cloudOutline, home, person, search, searchOutline } from 'ionicons/icons';
|
||||
import { Route, Redirect } from 'react-router';
|
||||
|
||||
import Tab1 from './AppPages/Tab1';
|
||||
import Tab2 from './AppPages/Tab2';
|
||||
import Tab3 from './AppPages/Tab3';
|
||||
import Tab4 from './AppPages/Tab4';
|
||||
|
||||
import './style.scss';
|
||||
import './custom-tab-bar.scss';
|
||||
|
||||
function DemoPinterestFloatingTabBar() {
|
||||
return (
|
||||
<IonTabs>
|
||||
<IonRouterOutlet className="demo-pinterest-floating-tab-bar">
|
||||
<Route exact path="/demo-pinterest-floating-tab-bar/tab1">
|
||||
<Tab1 />
|
||||
</Route>
|
||||
<Route exact path="/demo-pinterest-floating-tab-bar/tab2">
|
||||
<Tab2 />
|
||||
</Route>
|
||||
<Route path="/demo-pinterest-floating-tab-bar/tab3">
|
||||
<Tab3 />
|
||||
</Route>
|
||||
<Route path="/demo-pinterest-floating-tab-bar/tab4">
|
||||
<Tab4 />
|
||||
</Route>
|
||||
<Route exact path="/demo-pinterest-floating-tab-bar/">
|
||||
<Redirect to="/tab1" />
|
||||
</Route>
|
||||
</IonRouterOutlet>
|
||||
{/* */}
|
||||
<IonTabBar slot="bottom">
|
||||
<IonTabButton tab="tab1" href="/demo-pinterest-floating-tab-bar/tab1">
|
||||
<IonIcon icon={home} />
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="tab2" href="/demo-pinterest-floating-tab-bar/tab2">
|
||||
<IonIcon icon={search} />
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="tab3" href="/demo-pinterest-floating-tab-bar/tab3">
|
||||
<IonIcon icon={chatbubble} />
|
||||
</IonTabButton>
|
||||
|
||||
<IonTabButton tab="tab4" href="/demo-pinterest-floating-tab-bar/tab4">
|
||||
<IonIcon icon={person} />
|
||||
</IonTabButton>
|
||||
</IonTabBar>
|
||||
</IonTabs>
|
||||
);
|
||||
}
|
||||
|
||||
export default DemoPinterestFloatingTabBar;
|
@@ -0,0 +1,253 @@
|
||||
/* Ionic Variables and Theming. For more info, please see:
|
||||
http://ionicframework.com/docs/theming/ */
|
||||
|
||||
/** Ionic CSS Variables **/
|
||||
.demo-pinterest-floating-tab-bar {
|
||||
* {
|
||||
/** 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;
|
||||
}
|
||||
}
|
||||
|
||||
:root {
|
||||
/* Custom tab bar */
|
||||
--tab-background: rgb(251, 251, 251);
|
||||
--tab-color: rgb(153, 153, 153);
|
||||
--tab-color-selected: black;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
/* Custom tab bar */
|
||||
--tab-background: rgb(53, 53, 53);
|
||||
--tab-color: rgb(83, 83, 83);
|
||||
--tab-color-selected: white;
|
||||
}
|
||||
}
|
||||
}
|
63
03_source/mobile/src/pages/DemoQuizApp/AppPages/Home.jsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import {
|
||||
IonButton,
|
||||
IonCol,
|
||||
IonContent,
|
||||
IonGrid,
|
||||
IonHeader,
|
||||
IonIcon,
|
||||
IonPage,
|
||||
IonRow,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
useIonActionSheet,
|
||||
} from '@ionic/react';
|
||||
import styles from './Home.module.scss';
|
||||
|
||||
import { informationCircleOutline } from 'ionicons/icons';
|
||||
|
||||
const Home = () => {
|
||||
const [show, hide] = useIonActionSheet();
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonContent fullscreen>
|
||||
<IonGrid>
|
||||
<IonRow>
|
||||
<IonCol size="12" className="ion-text-center">
|
||||
<img src="/assets/DemoQuizApp/main.png" alt="title" className={styles.title} />
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonGrid>
|
||||
<IonRow className={styles.buttons}>
|
||||
<IonCol size="12">
|
||||
<IonButton
|
||||
routerLink="/demo-quiz-app/quiz"
|
||||
color="light"
|
||||
expand="block"
|
||||
className={styles.playButton}
|
||||
>
|
||||
Start Playing
|
||||
</IonButton>
|
||||
|
||||
<IonButton
|
||||
color="dark"
|
||||
className={styles.helpButton}
|
||||
onClick={() =>
|
||||
show({
|
||||
buttons: [{ text: 'Close' }],
|
||||
header: 'How to play',
|
||||
subHeader:
|
||||
'Pick a category and difficulty, then proceed to answer each question. You will gain a score by getting an answer right and you will also be indicated whether your answer was correct or incorrect. Have fun!',
|
||||
})
|
||||
}
|
||||
>
|
||||
<IonIcon icon={informationCircleOutline} /> How to play
|
||||
</IonButton>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
@@ -0,0 +1,43 @@
|
||||
.title {
|
||||
|
||||
height: 10rem;
|
||||
margin-top: 30%;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
|
||||
position: absolute;
|
||||
bottom: 3rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.playButton {
|
||||
|
||||
height: 4rem;
|
||||
--border-radius: 500px;
|
||||
width: fit-content;
|
||||
--padding-start: 5rem;
|
||||
--padding-end: 5rem;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.helpButton {
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
text-align: center;
|
||||
width: fit-content;
|
||||
margin: 0 auto;
|
||||
margin-top: 3rem;
|
||||
opacity: 70%;
|
||||
--border-radius: 10rem !important;
|
||||
--padding-end: 1.25rem;
|
||||
|
||||
ion-icon {
|
||||
|
||||
margin-top: 0.2rem;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
193
03_source/mobile/src/pages/DemoQuizApp/AppPages/Questions.jsx
Normal file
@@ -0,0 +1,193 @@
|
||||
import {
|
||||
IonBadge,
|
||||
IonButton,
|
||||
IonCard,
|
||||
IonCardContent,
|
||||
IonCardHeader,
|
||||
IonCardSubtitle,
|
||||
IonCardTitle,
|
||||
IonCol,
|
||||
IonContent,
|
||||
IonGrid,
|
||||
IonHeader,
|
||||
IonIcon,
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonNote,
|
||||
IonPage,
|
||||
IonRow,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
useIonRouter,
|
||||
useIonViewDidEnter,
|
||||
} from '@ionic/react';
|
||||
|
||||
import styles from './Quiz.module.scss';
|
||||
import { useStoreState } from 'pullstate';
|
||||
import { SettingsStore } from '../store';
|
||||
import {
|
||||
getCategories,
|
||||
getChosenCategory,
|
||||
getChosenDifficulty,
|
||||
getDifficulties,
|
||||
} from '../store/Selectors';
|
||||
import { Category, Difficulty } from '../components/Settings';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { fetchQuestions } from '../questions';
|
||||
|
||||
// Import Swiper React components
|
||||
import { Swiper, SwiperSlide } from 'swiper/react';
|
||||
|
||||
// Import Swiper styles
|
||||
// import 'swiper/swiper.scss';
|
||||
import 'swiper/css';
|
||||
|
||||
import { useRef } from 'react';
|
||||
import { updateChosenCategory, updateChosenDifficulty } from '../store/SettingsStore';
|
||||
import { Answer } from '../components/Answer';
|
||||
import { CompletedCard } from '../components/CompletedCard';
|
||||
import { QuizStats } from '../components/QuizStats';
|
||||
|
||||
const Questions = () => {
|
||||
const mainContainerRef = useRef();
|
||||
const completionContainerRef = useRef();
|
||||
const swiperRef = useRef(null);
|
||||
|
||||
const router = useIonRouter();
|
||||
const chosenCategory = useStoreState(SettingsStore, getChosenCategory);
|
||||
const chosenDifficulty = useStoreState(SettingsStore, getChosenDifficulty);
|
||||
|
||||
const [currentQuestion, setCurrentQuestion] = useState(1);
|
||||
const [score, setScore] = useState(0);
|
||||
const [completed, setCompleted] = useState(false);
|
||||
|
||||
const [questions, setQuestions] = useState(false);
|
||||
const [slideSpace, setSlideSpace] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const getQuestions = async () => {
|
||||
const fetchedQuestions = await fetchQuestions(chosenCategory, chosenDifficulty);
|
||||
setQuestions(fetchedQuestions);
|
||||
};
|
||||
|
||||
getQuestions();
|
||||
}, []);
|
||||
|
||||
useIonViewDidEnter(() => {
|
||||
setSlideSpace(40);
|
||||
});
|
||||
|
||||
const handleAnswerClick = (event, answer, question) => {
|
||||
const isCorrect = question.correct_answers[`${answer}_correct`] === 'true';
|
||||
|
||||
if (isCorrect) {
|
||||
event.target.setAttribute('color', 'success');
|
||||
} else {
|
||||
event.target.setAttribute('color', 'danger');
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
isCorrect && setScore((score) => score + 1);
|
||||
event.target.setAttribute('color', 'light');
|
||||
swiperRef.current.swiper.slideNext();
|
||||
checkIfComplete();
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
const checkIfComplete = () => {
|
||||
if (currentQuestion === questions.length) {
|
||||
// Quiz has finished
|
||||
// Hide Slides and show completion screen
|
||||
mainContainerRef.current.classList.add('animate__zoomOutDown');
|
||||
|
||||
setTimeout(() => {
|
||||
setCompleted(true);
|
||||
completionContainerRef.current.classList.add('animate__zoomInUp');
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>
|
||||
<img src="/assets/DemoQuizApp/main.png" style={{ width: '30%' }} alt="logo" />
|
||||
</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent fullscreen className="background">
|
||||
{!completed && (
|
||||
<IonGrid className={`${styles.mainGrid} animate__animated`} ref={mainContainerRef}>
|
||||
<QuizStats
|
||||
chosenCategory={chosenCategory}
|
||||
chosenDifficulty={chosenDifficulty}
|
||||
questionsLength={questions.length}
|
||||
currentQuestion={currentQuestion}
|
||||
score={score}
|
||||
/>
|
||||
|
||||
<IonRow className={styles.mainRow}>
|
||||
<IonCol size="12">
|
||||
<IonRow>
|
||||
<Swiper
|
||||
ref={swiperRef}
|
||||
spaceBetween={slideSpace}
|
||||
slidesPerView={1}
|
||||
onSlideChange={(e) => setCurrentQuestion(e.activeIndex + 1)}
|
||||
>
|
||||
{questions &&
|
||||
questions.map((question, index) => {
|
||||
return (
|
||||
<SwiperSlide key={`question_${index}`}>
|
||||
<IonCard id="questionCard" className="animate__animated">
|
||||
<IonCardHeader className="ion-text-center">
|
||||
<IonCardSubtitle>{question.category}</IonCardSubtitle>
|
||||
{question.tags.length > 0 && (
|
||||
<IonBadge color="success">{question.tags[0].name}</IonBadge>
|
||||
)}
|
||||
<IonCardTitle className={styles.questionTitle}>
|
||||
{question.question}
|
||||
</IonCardTitle>
|
||||
</IonCardHeader>
|
||||
|
||||
<IonCardContent>
|
||||
{Object.keys(question.answers).map((answer, index) => {
|
||||
if (question.answers[answer] !== null) {
|
||||
return (
|
||||
<Answer
|
||||
key={`answer_${index}`}
|
||||
answer={answer}
|
||||
question={question}
|
||||
handleAnswerClick={handleAnswerClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</IonCardContent>
|
||||
</IonCard>
|
||||
</SwiperSlide>
|
||||
);
|
||||
})}
|
||||
</Swiper>
|
||||
</IonRow>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonGrid>
|
||||
)}
|
||||
|
||||
{completed && (
|
||||
<CompletedCard
|
||||
completionContainerRef={completionContainerRef}
|
||||
score={score}
|
||||
questionsLength={questions.length}
|
||||
/>
|
||||
)}
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Questions;
|
153
03_source/mobile/src/pages/DemoQuizApp/AppPages/Quiz.jsx
Normal file
@@ -0,0 +1,153 @@
|
||||
import {
|
||||
IonButton,
|
||||
IonCard,
|
||||
IonCardContent,
|
||||
IonCardHeader,
|
||||
IonCardSubtitle,
|
||||
IonCol,
|
||||
IonContent,
|
||||
IonGrid,
|
||||
IonHeader,
|
||||
IonIcon,
|
||||
IonPage,
|
||||
IonRow,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
useIonRouter,
|
||||
useIonToast,
|
||||
} from '@ionic/react';
|
||||
|
||||
import styles from './Quiz.module.scss';
|
||||
import { useStoreState } from 'pullstate';
|
||||
import { SettingsStore } from '../store';
|
||||
import {
|
||||
getCategories,
|
||||
getChosenCategory,
|
||||
getChosenDifficulty,
|
||||
getDifficulties,
|
||||
} from '../store/Selectors';
|
||||
import { Category, Difficulty } from '../components/Settings';
|
||||
|
||||
const Quiz = () => {
|
||||
const router = useIonRouter();
|
||||
const categories = useStoreState(SettingsStore, getCategories);
|
||||
const difficulties = useStoreState(SettingsStore, getDifficulties);
|
||||
|
||||
const chosenCategory = useStoreState(SettingsStore, getChosenCategory);
|
||||
const chosenDifficulty = useStoreState(SettingsStore, getChosenDifficulty);
|
||||
|
||||
const [show, hide] = useIonToast();
|
||||
|
||||
const startQuiz = async () => {
|
||||
if (chosenCategory && chosenDifficulty) {
|
||||
const chosenCategoryElement = document.getElementById(`categoryButton_${chosenCategory}`);
|
||||
const chosenDifficultyElement = document.getElementById(
|
||||
`difficultyButton_${chosenDifficulty}`
|
||||
);
|
||||
|
||||
const categoriesCardElement = document.getElementById('categoriesCard');
|
||||
const difficultiesCardElement = document.getElementById('difficultiesCard');
|
||||
|
||||
chosenCategoryElement.classList.add('ontop');
|
||||
chosenDifficultyElement.classList.add('ontop');
|
||||
|
||||
chosenCategoryElement.classList.add('animate__heartBeat');
|
||||
chosenDifficultyElement.classList.add('animate__heartBeat');
|
||||
|
||||
setTimeout(() => {
|
||||
chosenCategoryElement.classList.remove('animate__heartBeat');
|
||||
chosenDifficultyElement.classList.remove('animate__heartBeat');
|
||||
chosenCategoryElement.classList.remove('ontop');
|
||||
chosenDifficultyElement.classList.remove('ontop');
|
||||
}, 1000);
|
||||
|
||||
setTimeout(() => {
|
||||
categoriesCardElement.classList.add('animate__slideOutRight');
|
||||
difficultiesCardElement.classList.add('animate__slideOutLeft');
|
||||
|
||||
setTimeout(() => {
|
||||
categoriesCardElement.classList.remove('animate__slideOutRight');
|
||||
difficultiesCardElement.classList.remove('animate__slideOutLeft');
|
||||
}, 1000);
|
||||
}, 1100);
|
||||
|
||||
setTimeout(() => {
|
||||
router.push('/questions');
|
||||
}, 1700);
|
||||
} else {
|
||||
show({
|
||||
header: 'Hang on there!',
|
||||
message: 'You must choose a category and difficulty!',
|
||||
duration: 3000,
|
||||
color: 'warning',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>
|
||||
<img src="/assets/DemoQuizApp/main.png" style={{ width: '30%' }} alt="logo" />
|
||||
</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent fullscreen className="background">
|
||||
<IonGrid className={styles.mainGrid}>
|
||||
<IonRow className={styles.mainRow}>
|
||||
<IonCol size="12">
|
||||
<IonCard id="categoriesCard" className="animate__animated">
|
||||
<IonCardHeader className="ion-text-center">
|
||||
<IonCardSubtitle>Choose a category</IonCardSubtitle>
|
||||
</IonCardHeader>
|
||||
|
||||
<IonCardContent>
|
||||
<IonRow>
|
||||
{categories.map((category, index) => {
|
||||
const chosen = category.value === chosenCategory;
|
||||
|
||||
return <Category key={`category_${index}`} {...category} chosen={chosen} />;
|
||||
})}
|
||||
</IonRow>
|
||||
</IonCardContent>
|
||||
</IonCard>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<IonRow className={styles.difficultyContainer}>
|
||||
<IonCol size="12">
|
||||
<IonCard id="difficultiesCard" className="animate__animated">
|
||||
<IonCardHeader className="ion-text-center">
|
||||
<IonCardSubtitle>Choose a difficulty</IonCardSubtitle>
|
||||
</IonCardHeader>
|
||||
|
||||
<IonCardContent>
|
||||
<IonRow>
|
||||
{difficulties.map((difficulty, index) => {
|
||||
const chosen = difficulty.value === chosenDifficulty;
|
||||
|
||||
return (
|
||||
<Difficulty key={`difficulty_${index}`} {...difficulty} chosen={chosen} />
|
||||
);
|
||||
})}
|
||||
</IonRow>
|
||||
</IonCardContent>
|
||||
</IonCard>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<IonRow>
|
||||
<IonCol size="12">
|
||||
<div className={styles.startButton} onClick={startQuiz}>
|
||||
Start Quiz!
|
||||
</div>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonGrid>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Quiz;
|
@@ -0,0 +1,46 @@
|
||||
.difficultyContainer {
|
||||
|
||||
margin-top: -2rem !important;
|
||||
}
|
||||
|
||||
.startButton {
|
||||
|
||||
background-color: #994ec1;
|
||||
padding: 1.25rem;
|
||||
margin: 1rem;
|
||||
margin-top: -1rem;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
color: white;
|
||||
border: 2px solid #632485;
|
||||
}
|
||||
|
||||
.questionTitle {
|
||||
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.answerButton {
|
||||
|
||||
height: fit-content;
|
||||
--padding-top: 1rem;
|
||||
--padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.mainGrid {
|
||||
|
||||
// margin-top: -2rem;
|
||||
}
|
||||
|
||||
.mainRow {
|
||||
|
||||
margin-top: -2rem;
|
||||
}
|
||||
|
||||
.emoji {
|
||||
|
||||
font-size: 4rem;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
padding-top: 1rem;
|
||||
}
|
17
03_source/mobile/src/pages/DemoQuizApp/components/Answer.jsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { IonButton, IonCol, IonRow } from '@ionic/react';
|
||||
import styles from './Quiz.module.scss';
|
||||
|
||||
export const Answer = ({ answer, handleAnswerClick, question }) => (
|
||||
<IonRow>
|
||||
<IonCol size="12">
|
||||
<IonButton
|
||||
onClick={(e) => handleAnswerClick(e, answer, question)}
|
||||
expand="block"
|
||||
color="light"
|
||||
className={`ion-text-wrap ${styles.answerButton}`}
|
||||
>
|
||||
{question.answers[answer]}
|
||||
</IonButton>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
);
|
@@ -0,0 +1,58 @@
|
||||
import {
|
||||
IonButton,
|
||||
IonCard,
|
||||
IonCardContent,
|
||||
IonCardHeader,
|
||||
IonCardSubtitle,
|
||||
IonCardTitle,
|
||||
IonCol,
|
||||
IonGrid,
|
||||
IonNote,
|
||||
IonRow,
|
||||
useIonRouter,
|
||||
} from '@ionic/react';
|
||||
import styles from './Quiz.module.scss';
|
||||
import { updateChosenCategory, updateChosenDifficulty } from '../store/SettingsStore';
|
||||
|
||||
export const CompletedCard = ({ completionContainerRef, score, questionsLength }) => {
|
||||
const router = useIonRouter();
|
||||
|
||||
const playAgain = () => {
|
||||
updateChosenCategory(false);
|
||||
updateChosenDifficulty(false);
|
||||
router.push('/');
|
||||
};
|
||||
|
||||
return (
|
||||
<IonGrid className="animate__animated" ref={completionContainerRef}>
|
||||
<IonRow className="ion-text-center">
|
||||
<IonCol size="12">
|
||||
<IonCard>
|
||||
<IonCardHeader>
|
||||
<IonCardSubtitle>Congratulations</IonCardSubtitle>
|
||||
<IonCardTitle>Quiz Complete!</IonCardTitle>
|
||||
<p className={styles.emoji}>🎉</p>
|
||||
</IonCardHeader>
|
||||
|
||||
<IonCardContent>
|
||||
<IonNote>You scored</IonNote>
|
||||
|
||||
<IonCardTitle className="ion-margin-bottom">
|
||||
{score}/{questionsLength}
|
||||
</IonCardTitle>
|
||||
|
||||
<IonButton
|
||||
onClick={playAgain}
|
||||
color="success"
|
||||
expand="block"
|
||||
className="ion-margin-top"
|
||||
>
|
||||
Play Again!
|
||||
</IonButton>
|
||||
</IonCardContent>
|
||||
</IonCard>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonGrid>
|
||||
);
|
||||
};
|
@@ -0,0 +1,46 @@
|
||||
.difficultyContainer {
|
||||
|
||||
margin-top: -2rem !important;
|
||||
}
|
||||
|
||||
.startButton {
|
||||
|
||||
background-color: #994ec1;
|
||||
padding: 1.25rem;
|
||||
margin: 1rem;
|
||||
margin-top: -1rem;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
color: white;
|
||||
border: 2px solid #632485;
|
||||
}
|
||||
|
||||
.questionTitle {
|
||||
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.answerButton {
|
||||
|
||||
height: fit-content;
|
||||
--padding-top: 1rem;
|
||||
--padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.mainGrid {
|
||||
|
||||
// margin-top: -2rem;
|
||||
}
|
||||
|
||||
.mainRow {
|
||||
|
||||
margin-top: -2rem;
|
||||
}
|
||||
|
||||
.emoji {
|
||||
|
||||
font-size: 4rem;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
padding-top: 1rem;
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
import { IonCard, IonCardContent, IonCardSubtitle, IonCol, IonItem, IonLabel, IonNote, IonRow } from "@ionic/react";
|
||||
|
||||
export const QuizStats = ({ chosenCategory, chosenDifficulty, currentQuestion, questionsLength, score }) => (
|
||||
|
||||
<IonRow>
|
||||
<IonCol size="12">
|
||||
<IonCard>
|
||||
<IonCardContent className="ion-text-center">
|
||||
<IonCardSubtitle>{ chosenCategory } | { chosenDifficulty }</IonCardSubtitle>
|
||||
<IonItem lines="none">
|
||||
<IonLabel className="ion-text-center">
|
||||
<IonCardSubtitle>Question</IonCardSubtitle>
|
||||
<IonNote>{ currentQuestion } / { questionsLength }</IonNote>
|
||||
</IonLabel>
|
||||
|
||||
<IonLabel className="ion-text-center">
|
||||
<IonCardSubtitle>Score</IonCardSubtitle>
|
||||
<IonNote>{ score }</IonNote>
|
||||
</IonLabel>
|
||||
</IonItem>
|
||||
</IonCardContent>
|
||||
</IonCard>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
);
|
@@ -0,0 +1,17 @@
|
||||
import { IonCol } from "@ionic/react";
|
||||
import { updateChosenCategory, updateChosenDifficulty } from "../store/SettingsStore";
|
||||
import styles from "./Settings.module.scss";
|
||||
|
||||
export const Category = ({ label, value, set, chosen }) => (
|
||||
|
||||
<IonCol id={ `categoryButton_${ value }` } size="6" className={ `${styles.category} ${chosen && styles.chosen} animate__animated` } onClick={ () => updateChosenCategory(value) }>
|
||||
<p>{ label }</p>
|
||||
</IonCol>
|
||||
);
|
||||
|
||||
export const Difficulty = ({ label, value, set, chosen }) => (
|
||||
|
||||
<IonCol id={ `difficultyButton_${ value }` } size="4" className={ `${ styles.category } ${ chosen && styles.chosen } animate__animated` } onClick={ () => updateChosenDifficulty(value) }>
|
||||
<p>{ label }</p>
|
||||
</IonCol>
|
||||
);
|
@@ -0,0 +1,20 @@
|
||||
.category {
|
||||
|
||||
height: 4rem;
|
||||
border: 5px solid rgb(255, 255, 255);
|
||||
background-color: #994ec1;
|
||||
color: white;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.chosen {
|
||||
|
||||
border: 2px solid #3a1d49;
|
||||
font-weight: 700;
|
||||
}
|
33
03_source/mobile/src/pages/DemoQuizApp/index.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { IonRouterOutlet, IonTabs } from '@ionic/react';
|
||||
|
||||
import { Route, Redirect } from 'react-router';
|
||||
|
||||
import Home from './AppPages/Home';
|
||||
import Quiz from './AppPages/Quiz';
|
||||
import Questions from './AppPages/Questions';
|
||||
|
||||
import './style.scss';
|
||||
|
||||
function DemoQuizApp() {
|
||||
return (
|
||||
<IonTabs className="demo-quiz-app">
|
||||
<IonRouterOutlet>
|
||||
<Route exact path="/demo-quiz-app/home">
|
||||
<Home />
|
||||
</Route>
|
||||
|
||||
<Route exact path="/demo-quiz-app/quiz">
|
||||
<Quiz />
|
||||
</Route>
|
||||
|
||||
<Route exact path="/demo-quiz-app/questions">
|
||||
<Questions />
|
||||
</Route>
|
||||
|
||||
<Redirect exact path="/demo-quiz-app" to="/demo-quiz-app/home" />
|
||||
</IonRouterOutlet>
|
||||
</IonTabs>
|
||||
);
|
||||
}
|
||||
|
||||
export default DemoQuizApp;
|
11
03_source/mobile/src/pages/DemoQuizApp/questions/index.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const API_URL = 'https://quizapi.io/api/v1/questions';
|
||||
const API_KEY = 'B27jnk1wmfEOQ42FtmrgBogiNTLLhOArJj29y24a';
|
||||
|
||||
export const fetchQuestions = async (category, difficulty) => {
|
||||
const response = await fetch(
|
||||
`${API_URL}?apiKey=${API_KEY}&category=${category}&difficulty=${difficulty}&limit=10`
|
||||
);
|
||||
const questions = await response.json();
|
||||
|
||||
return questions;
|
||||
};
|
10
03_source/mobile/src/pages/DemoQuizApp/store/Selectors.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import { createSelector } from "reselect";
|
||||
|
||||
const getState = state => state;
|
||||
|
||||
// Getters
|
||||
export const getCategories = createSelector(getState, state => state.categories);
|
||||
export const getDifficulties = createSelector(getState, state => state.difficulties);
|
||||
|
||||
export const getChosenCategory = createSelector(getState, state => state.chosenCategory);
|
||||
export const getChosenDifficulty = createSelector(getState, state => state.chosenDifficulty);
|
@@ -0,0 +1,60 @@
|
||||
import { Store } from "pullstate";
|
||||
|
||||
const SettingsStore = new Store({
|
||||
|
||||
categories: [
|
||||
{
|
||||
label: "Code",
|
||||
value: "code",
|
||||
},
|
||||
{
|
||||
label: "Linux",
|
||||
value: "linux"
|
||||
},
|
||||
{
|
||||
label: "Dev Ops",
|
||||
value: "devops"
|
||||
},
|
||||
{
|
||||
label: "Authentication",
|
||||
value: "authentication"
|
||||
},
|
||||
{
|
||||
label: "Bash",
|
||||
value: "bash"
|
||||
},
|
||||
{
|
||||
label: "SQL",
|
||||
value: "sql"
|
||||
}
|
||||
],
|
||||
difficulties: [
|
||||
{
|
||||
label: "Easy",
|
||||
value: "easy"
|
||||
},
|
||||
{
|
||||
label: "Medium",
|
||||
value: "medium"
|
||||
},
|
||||
{
|
||||
label: "Hard",
|
||||
value: "hard"
|
||||
}
|
||||
],
|
||||
|
||||
chosenCategory: false,
|
||||
chosenDifficulty: false
|
||||
});
|
||||
|
||||
export default SettingsStore;
|
||||
|
||||
export const updateChosenCategory = category => {
|
||||
|
||||
SettingsStore.update(s => { s.chosenCategory = category });
|
||||
}
|
||||
|
||||
export const updateChosenDifficulty = difficulty => {
|
||||
|
||||
SettingsStore.update(s => { s.chosenDifficulty = difficulty });
|
||||
}
|
1
03_source/mobile/src/pages/DemoQuizApp/store/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export { default as SettingsStore } from "./SettingsStore";
|
113
03_source/mobile/src/pages/DemoQuizApp/style.scss
Normal file
@@ -0,0 +1,65 @@
|
||||
import {
|
||||
IonButton,
|
||||
IonButtons,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonMenuButton,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
useIonActionSheet,
|
||||
} from '@ionic/react';
|
||||
|
||||
const ActionSheet = () => {
|
||||
const [present, dismiss] = useIonActionSheet();
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonMenuButton />
|
||||
</IonButtons>
|
||||
<IonTitle>Action Sheet</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">Action Sheet</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonButton
|
||||
expand="block"
|
||||
onClick={() =>
|
||||
present({
|
||||
buttons: [{ text: 'Ok' }, { text: 'Cancel' }],
|
||||
header: 'Action Sheet',
|
||||
})
|
||||
}
|
||||
>
|
||||
Show ActionSheet
|
||||
</IonButton>
|
||||
<IonButton
|
||||
expand="block"
|
||||
onClick={() => present([{ text: 'Ok' }, { text: 'Cancel' }], 'Action Sheet')}
|
||||
>
|
||||
Show ActionSheet using params
|
||||
</IonButton>
|
||||
<IonButton
|
||||
expand="block"
|
||||
onClick={() => {
|
||||
present([{ text: 'Ok' }, { text: 'Cancel' }], 'Action Sheet');
|
||||
setTimeout(dismiss, 3000);
|
||||
}}
|
||||
>
|
||||
Show ActionSheet, hide after 3 seconds
|
||||
</IonButton>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default ActionSheet;
|
@@ -0,0 +1,56 @@
|
||||
import {
|
||||
IonButton,
|
||||
IonButtons,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonMenuButton,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
useIonAlert,
|
||||
} from '@ionic/react';
|
||||
|
||||
const Alert = () => {
|
||||
const [present] = useIonAlert();
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonMenuButton />
|
||||
</IonButtons>
|
||||
<IonTitle>Alert</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">Alert</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonButton
|
||||
expand="block"
|
||||
onClick={() =>
|
||||
present({
|
||||
cssClass: 'my-css',
|
||||
header: 'Alert',
|
||||
message: 'alert from hook',
|
||||
buttons: ['Cancel', { text: 'Ok', handler: (d) => console.log('ok pressed') }],
|
||||
onDidDismiss: (e) => console.log('did dismiss'),
|
||||
})
|
||||
}
|
||||
>
|
||||
Show Alert
|
||||
</IonButton>
|
||||
<IonButton expand="block" onClick={() => present('hello with params', [{ text: 'Ok' }])}>
|
||||
Show Alert using params
|
||||
</IonButton>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Alert;
|
@@ -0,0 +1,76 @@
|
||||
import {
|
||||
IonButtons,
|
||||
IonCard,
|
||||
IonCardHeader,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonMenuButton,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
IonCardTitle,
|
||||
IonCardSubtitle,
|
||||
IonCardContent,
|
||||
IonText,
|
||||
} from '@ionic/react';
|
||||
|
||||
const All = () => {
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonMenuButton />
|
||||
</IonButtons>
|
||||
<IonTitle>All</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">All</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonCard>
|
||||
<IonCardHeader>
|
||||
<IonCardSubtitle>Sample usage</IonCardSubtitle>
|
||||
<IonCardTitle>Overlay Hooks</IonCardTitle>
|
||||
</IonCardHeader>
|
||||
|
||||
<IonCardContent>
|
||||
<IonText>
|
||||
<p>
|
||||
In Ionic React 5.6, the team packaged up a new set of hooks for controlling overlay
|
||||
components that they thought we might like. What is an overlay you ask? It’s the
|
||||
term that Ionic give components that display over your current content, such as
|
||||
alerts, modals, toasts, etc.
|
||||
</p>
|
||||
</IonText>
|
||||
<br />
|
||||
<IonText>
|
||||
<p>
|
||||
All of the code is taken from the Ionic Framework docs. You can find the blog post
|
||||
outlining these new overlay hooks{' '}
|
||||
<a
|
||||
href="https://ionicframework.com/blog/introducing-the-new-overlay-hooks-for-ionic-react/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
here.
|
||||
</a>
|
||||
</p>
|
||||
</IonText>
|
||||
<br />
|
||||
<IonText>
|
||||
<p>Check out the samples by navigating to a respective one in the side menu.</p>
|
||||
</IonText>
|
||||
</IonCardContent>
|
||||
</IonCard>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default All;
|
@@ -0,0 +1,52 @@
|
||||
import {
|
||||
IonButton,
|
||||
IonButtons,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonMenuButton,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
useIonLoading,
|
||||
} from '@ionic/react';
|
||||
|
||||
const Loading = () => {
|
||||
const [present] = useIonLoading();
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonMenuButton />
|
||||
</IonButtons>
|
||||
<IonTitle>Loading</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">Loading</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonButton
|
||||
expand="block"
|
||||
onClick={() =>
|
||||
present({
|
||||
duration: 3000,
|
||||
})
|
||||
}
|
||||
>
|
||||
Show Loading
|
||||
</IonButton>
|
||||
<IonButton expand="block" onClick={() => present('Loading', 2000, 'dots')}>
|
||||
Show Loading using params
|
||||
</IonButton>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Loading;
|
@@ -0,0 +1,80 @@
|
||||
import {
|
||||
IonButton,
|
||||
IonButtons,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonMenuButton,
|
||||
IonPage,
|
||||
IonText,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
useIonModal,
|
||||
} from '@ionic/react';
|
||||
import { useState } from 'react';
|
||||
|
||||
const Modal = () => {
|
||||
const Body = ({ count, onDismiss, onIncrement }) => (
|
||||
<div className="ion-text-center">
|
||||
<IonText color="dark" className="ion-text-center">
|
||||
Count: {count}
|
||||
</IonText>
|
||||
<IonButton expand="block" onClick={() => onIncrement()}>
|
||||
Increment Count
|
||||
</IonButton>
|
||||
<IonButton expand="block" onClick={() => onDismiss()}>
|
||||
Close
|
||||
</IonButton>
|
||||
</div>
|
||||
);
|
||||
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
const handleIncrement = () => {
|
||||
setCount(count + 1);
|
||||
};
|
||||
|
||||
const handleDismiss = () => {
|
||||
dismiss();
|
||||
};
|
||||
|
||||
const [present, dismiss] = useIonModal(Body, {
|
||||
count,
|
||||
onDismiss: handleDismiss,
|
||||
onIncrement: handleIncrement,
|
||||
});
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonMenuButton />
|
||||
</IonButtons>
|
||||
<IonTitle>Modal</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">Modal</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonButton
|
||||
expand="block"
|
||||
onClick={() => {
|
||||
present({
|
||||
cssClass: 'my-class',
|
||||
});
|
||||
}}
|
||||
>
|
||||
Show Modal
|
||||
</IonButton>
|
||||
<div>Count: {count}</div>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Modal;
|
@@ -0,0 +1,104 @@
|
||||
import {
|
||||
IonButton,
|
||||
IonButtons,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonMenuButton,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
useIonPicker,
|
||||
} from '@ionic/react';
|
||||
import { useState } from 'react';
|
||||
|
||||
const Picker = () => {
|
||||
const [present] = useIonPicker();
|
||||
const [value, setValue] = useState('');
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonMenuButton />
|
||||
</IonButtons>
|
||||
<IonTitle>Picker</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">Picker</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonButton
|
||||
expand="block"
|
||||
onClick={() =>
|
||||
present({
|
||||
buttons: [
|
||||
{
|
||||
text: 'Confirm',
|
||||
handler: (selected) => {
|
||||
setValue(selected.animal.value);
|
||||
},
|
||||
},
|
||||
],
|
||||
columns: [
|
||||
{
|
||||
name: 'animal',
|
||||
options: [
|
||||
{ text: 'Dog', value: 'dog' },
|
||||
{ text: 'Cat', value: 'cat' },
|
||||
{ text: 'Bird', value: 'bird' },
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
>
|
||||
Show Picker
|
||||
</IonButton>
|
||||
<IonButton
|
||||
expand="block"
|
||||
onClick={() =>
|
||||
present(
|
||||
[
|
||||
{
|
||||
name: 'animal',
|
||||
options: [
|
||||
{ text: 'Dog', value: 'dog' },
|
||||
{ text: 'Cat', value: 'cat' },
|
||||
{ text: 'Bird', value: 'bird' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'vehicle',
|
||||
options: [
|
||||
{ text: 'Car', value: 'car' },
|
||||
{ text: 'Truck', value: 'truck' },
|
||||
{ text: 'Bike', value: 'bike' },
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
text: 'Confirm',
|
||||
handler: (selected) => {
|
||||
setValue(`${selected.animal.value}, ${selected.vehicle.value}`);
|
||||
},
|
||||
},
|
||||
]
|
||||
)
|
||||
}
|
||||
>
|
||||
Show Picker using params
|
||||
</IonButton>
|
||||
{value && <div>Selected Value: {value}</div>}
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Picker;
|
@@ -0,0 +1,65 @@
|
||||
import {
|
||||
IonButtons,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonItem,
|
||||
IonListHeader,
|
||||
IonMenuButton,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
IonList,
|
||||
useIonPopover,
|
||||
IonButton,
|
||||
} from '@ionic/react';
|
||||
|
||||
const Popover = () => {
|
||||
const PopoverList = ({ onHide }) => (
|
||||
<IonList>
|
||||
<IonListHeader>Ionic</IonListHeader>
|
||||
<IonItem button>Learn Ionic</IonItem>
|
||||
<IonItem button>Documentation</IonItem>
|
||||
<IonItem button>Showcase</IonItem>
|
||||
<IonItem button>GitHub Repo</IonItem>
|
||||
<IonItem lines="none" detail={false} button onClick={onHide}>
|
||||
Close
|
||||
</IonItem>
|
||||
</IonList>
|
||||
);
|
||||
|
||||
const [present, dismiss] = useIonPopover(PopoverList, { onHide: () => dismiss() });
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonMenuButton />
|
||||
</IonButtons>
|
||||
<IonTitle>Popover</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">Popover</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonButton
|
||||
expand="block"
|
||||
onClick={(e) =>
|
||||
present({
|
||||
event: e.nativeEvent,
|
||||
})
|
||||
}
|
||||
>
|
||||
Show Popover
|
||||
</IonButton>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Popover;
|
@@ -0,0 +1,58 @@
|
||||
import {
|
||||
IonButton,
|
||||
IonButtons,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonMenuButton,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
useIonToast,
|
||||
} from '@ionic/react';
|
||||
|
||||
const Toast = () => {
|
||||
const [present, dismiss] = useIonToast();
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonMenuButton />
|
||||
</IonButtons>
|
||||
<IonTitle>Toast</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">Toast</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonButton
|
||||
expand="block"
|
||||
onClick={() =>
|
||||
present({
|
||||
buttons: [{ text: 'hide', handler: () => dismiss() }],
|
||||
message: 'toast from hook, click hide to dismiss',
|
||||
onDidDismiss: () => console.log('dismissed'),
|
||||
onWillDismiss: () => console.log('will dismiss'),
|
||||
})
|
||||
}
|
||||
>
|
||||
Show Toast
|
||||
</IonButton>
|
||||
<IonButton expand="block" onClick={() => present('hello from hook', 3000)}>
|
||||
Show Toast using params, closes in 3 secs
|
||||
</IonButton>
|
||||
<IonButton expand="block" onClick={dismiss}>
|
||||
Hide Toast
|
||||
</IonButton>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Toast;
|
@@ -0,0 +1,113 @@
|
||||
ion-menu ion-content {
|
||||
--background: var(--ion-item-background, var(--ion-background-color, #fff));
|
||||
}
|
||||
|
||||
ion-menu.md ion-content {
|
||||
--padding-start: 8px;
|
||||
--padding-end: 8px;
|
||||
--padding-top: 20px;
|
||||
--padding-bottom: 20px;
|
||||
}
|
||||
|
||||
ion-menu.md ion-list {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
ion-menu.md ion-note {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
ion-menu.md ion-list-header, ion-menu.md ion-note {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
ion-menu.md ion-list#inbox-list {
|
||||
border-bottom: 1px solid var(--ion-color-step-150, #d7d8da);
|
||||
}
|
||||
|
||||
ion-menu.md ion-list#inbox-list ion-list-header {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
ion-menu.md ion-list#labels-list ion-list-header {
|
||||
font-size: 16px;
|
||||
margin-bottom: 18px;
|
||||
color: #757575;
|
||||
min-height: 26px;
|
||||
}
|
||||
|
||||
ion-menu.md ion-item {
|
||||
--padding-start: 10px;
|
||||
--padding-end: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
ion-menu.md ion-item.selected {
|
||||
--background: rgba(var(--ion-color-primary-rgb), 0.14);
|
||||
}
|
||||
|
||||
ion-menu.md ion-item.selected ion-icon {
|
||||
color: var(--ion-color-primary);
|
||||
}
|
||||
|
||||
ion-menu.md ion-item ion-icon {
|
||||
color: #616e7e;
|
||||
}
|
||||
|
||||
ion-menu.md ion-item ion-label {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
ion-menu.ios ion-content {
|
||||
--padding-bottom: 20px;
|
||||
}
|
||||
|
||||
ion-menu.ios ion-list {
|
||||
padding: 20px 0 0 0;
|
||||
}
|
||||
|
||||
ion-menu.ios ion-note {
|
||||
line-height: 24px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
ion-menu.ios ion-item {
|
||||
--padding-start: 16px;
|
||||
--padding-end: 16px;
|
||||
--min-height: 50px;
|
||||
}
|
||||
|
||||
ion-menu.ios ion-item ion-icon {
|
||||
font-size: 24px;
|
||||
color: #73849a;
|
||||
}
|
||||
|
||||
ion-menu.ios ion-item .selected ion-icon {
|
||||
color: var(--ion-color-primary);
|
||||
}
|
||||
|
||||
ion-menu.ios ion-list#labels-list ion-list-header {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
ion-menu.ios ion-list-header,
|
||||
ion-menu.ios ion-note {
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
ion-menu.ios ion-note {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
ion-note {
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
color: var(--ion-color-medium-shade);
|
||||
}
|
||||
|
||||
ion-item.selected {
|
||||
--color: var(--ion-color-primary);
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
import { IonContent, IonIcon, IonItem, IonLabel, IonList, IonListHeader, IonMenu, IonMenuToggle, IonNote } from '@ionic/react';
|
||||
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { star, starOutline } from 'ionicons/icons';
|
||||
import './Menu.css';
|
||||
|
||||
const Menu = ({ pages }) => {
|
||||
|
||||
const location = useLocation();
|
||||
|
||||
return (
|
||||
<IonMenu contentId="main" type="overlay">
|
||||
<IonContent>
|
||||
<IonList id="inbox-list">
|
||||
<IonListHeader>Overlay Hooks</IonListHeader>
|
||||
<IonNote>Choose one below to see a demo</IonNote>
|
||||
|
||||
{ pages.map((appPage, index) => {
|
||||
|
||||
const isSelected = location.pathname === appPage.url;
|
||||
|
||||
return (
|
||||
<IonMenuToggle key={ index } autoHide={false}>
|
||||
<IonItem className={ isSelected ? 'selected' : '' } routerLink={ appPage.url } routerDirection="none" lines="none" detail={false}>
|
||||
<IonIcon slot="start" icon={ isSelected ? star : starOutline } />
|
||||
<IonLabel>{ appPage.label }</IonLabel>
|
||||
</IonItem>
|
||||
</IonMenuToggle>
|
||||
);
|
||||
})}
|
||||
</IonList>
|
||||
</IonContent>
|
||||
</IonMenu>
|
||||
);
|
||||
};
|
||||
|
||||
export default Menu;
|
62
03_source/mobile/src/pages/DemoReactOverlayHooks/index.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import {
|
||||
IonIcon,
|
||||
IonLabel,
|
||||
IonRouterOutlet,
|
||||
IonSplitPane,
|
||||
IonTabBar,
|
||||
IonTabButton,
|
||||
IonTabs,
|
||||
} from '@ionic/react';
|
||||
|
||||
import { cloudOutline, searchOutline } from 'ionicons/icons';
|
||||
import { Route, Redirect } from 'react-router';
|
||||
import Menu from './components/Menu';
|
||||
|
||||
import All from './AppPages/All';
|
||||
import ActionSheet from './AppPages/ActionSheet';
|
||||
import Alert from './AppPages/Alert';
|
||||
import Loading from './AppPages/Loading';
|
||||
import Modal from './AppPages/Modal';
|
||||
import Picker from './AppPages/Picker';
|
||||
import Popover from './AppPages/Popover';
|
||||
import Toast from './AppPages/Toast';
|
||||
import './style.scss';
|
||||
|
||||
function DemoReactOverlayHooks() {
|
||||
const pages = [
|
||||
{ label: 'All', url: '/overlay/all', component: All },
|
||||
{ label: 'Action Sheet', url: '/overlay/action-sheet', component: ActionSheet },
|
||||
{ label: 'Alert', url: '/overlay/alert', component: Alert },
|
||||
{ label: 'Loading', url: '/overlay/loading', component: Loading },
|
||||
{ label: 'Modal', url: '/overlay/modal', component: Modal },
|
||||
{ label: 'Picker', url: '/overlay/picker', component: Picker },
|
||||
{ label: 'Popover', url: '/overlay/popover', component: Popover },
|
||||
{ label: 'Toast', url: '/overlay/toast', component: Toast },
|
||||
];
|
||||
|
||||
return (
|
||||
<IonSplitPane contentId="main">
|
||||
<Menu pages={pages} />
|
||||
<IonRouterOutlet id="main">
|
||||
<Route path="/demo-react-overlay-hooks" exact={true}>
|
||||
<Redirect to="/demo-react-overlay-hooks/overlay/all" />
|
||||
</Route>
|
||||
|
||||
{pages.map((page, index) => {
|
||||
const pageComponent = page.component;
|
||||
|
||||
return (
|
||||
<Route
|
||||
key={index}
|
||||
path={`/demo-react-overlay-hooks${page.url}`}
|
||||
exact={true}
|
||||
component={pageComponent}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</IonRouterOutlet>
|
||||
</IonSplitPane>
|
||||
);
|
||||
}
|
||||
|
||||
export default DemoReactOverlayHooks;
|
103
03_source/mobile/src/pages/DemoReactOverlayHooks/style.scss
Normal file
@@ -0,0 +1,103 @@
|
||||
#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;
|
||||
}
|
198
03_source/mobile/src/pages/DemoReactPollApp/AppPages/Add.jsx
Normal file
@@ -0,0 +1,198 @@
|
||||
import { IonBackButton, IonButton, IonButtons, IonCardTitle, IonCol, IonContent, IonFooter, IonGrid, IonHeader, IonIcon, IonItem, IonLabel, IonMenuButton, IonPage, IonRow, IonTextarea, IonTitle, IonToolbar, useIonRouter, useIonViewDidEnter } from '@ionic/react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { GithubPicker } from 'react-color';
|
||||
import { getColors } from '../helpers/utils';
|
||||
import { addOutline } from 'ionicons/icons';
|
||||
import { PollDuration } from '../components/PollDuration';
|
||||
import { PollAnswer } from '../components/PollAnswer';
|
||||
import { addPoll } from '../store/PollStore';
|
||||
|
||||
const Add = () => {
|
||||
|
||||
const router = useIonRouter();
|
||||
const [ showPicker, setShowPicker ] = useState(false);
|
||||
const [ pollQuestion, setPollQuestion ] = useState("");
|
||||
const [ pollColor, setPollColor ] = useState("#427ed8");
|
||||
const [ pollAnswers, setPollAnswers ] = useState([]);
|
||||
|
||||
const [ pollDays, setPollDays ] = useState(0);
|
||||
const [ pollHours, setPollHours ] = useState(0);
|
||||
const [ pollMins, setPollMins ] = useState(0);
|
||||
|
||||
const pickerColors = [
|
||||
|
||||
"#759dc7",
|
||||
"#68bd8d",
|
||||
"#bd7368",
|
||||
"#8d68bd",
|
||||
"#bd68ac",
|
||||
"#6868bd",
|
||||
"#68a8bd",
|
||||
"#68bda5",
|
||||
"#bd9868",
|
||||
"#d84848",
|
||||
"#d87c48",
|
||||
"#d8bb48",
|
||||
"#7c7c7c"
|
||||
];
|
||||
|
||||
const colors = pollColor && getColors(pollColor);
|
||||
|
||||
useIonViewDidEnter(() => {
|
||||
|
||||
setShowPicker(false);
|
||||
setPollQuestion("");
|
||||
setPollColor("#427ed8");
|
||||
setPollAnswers([]);
|
||||
|
||||
setPollDays(0);
|
||||
setPollHours(0);
|
||||
setPollMins(0);
|
||||
});
|
||||
|
||||
const handleAdd = async () => {
|
||||
|
||||
const timeLeftDays = pollDays !== 0 && pollDays !== "" ? `${ pollDays } days, ` : "";
|
||||
const timeLeftHours = pollHours !== 0 && pollHours !== "" ? `${ pollHours } hours, ` : "";
|
||||
const timeLeftMins = pollMins !== 0 && pollMins !== "" ? `${ pollMins } mins` : "";
|
||||
|
||||
const timeLeft = `${ timeLeftDays }${ timeLeftHours }${ timeLeftMins }`;
|
||||
|
||||
const poll = {
|
||||
|
||||
id: Date.now(),
|
||||
question: pollQuestion,
|
||||
color: pollColor,
|
||||
timeLeft,
|
||||
answers: pollAnswers,
|
||||
totalVotes: 0,
|
||||
voted: false
|
||||
};
|
||||
|
||||
addPoll(poll);
|
||||
router.push("/page/view");
|
||||
}
|
||||
|
||||
const addAnswer = () => {
|
||||
|
||||
const answer = {
|
||||
|
||||
id: Date.now(),
|
||||
answer: "",
|
||||
votes: 0,
|
||||
voted: false,
|
||||
percent: 0
|
||||
};
|
||||
|
||||
setPollAnswers(prev => [...prev, answer ] );
|
||||
}
|
||||
|
||||
const removeAnswer = answer => {
|
||||
|
||||
const newAnswers = pollAnswers.filter((p) => p !== answer);
|
||||
setPollAnswers(newAnswers);
|
||||
}
|
||||
|
||||
const handleChange = (e, index) => {
|
||||
|
||||
const newAnswers = [ ...pollAnswers ];
|
||||
newAnswers[index].answer = e.target.value;
|
||||
setPollAnswers(newAnswers);
|
||||
}
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonBackButton text="Ionic Polls" style={{ color: pollColor ? pollColor : "" }} />
|
||||
</IonButtons>
|
||||
<IonTitle>Add Poll</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">Add Poll</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonGrid className="animate__animated animate__fadeIn">
|
||||
<IonRow>
|
||||
<IonCol size="12">
|
||||
<IonButton expand="block" onClick={ () => setShowPicker(!showPicker) } size="large" fill="solid" style={ colors.votedButtonStyle }>Poll Color</IonButton>
|
||||
{ showPicker && <GithubPicker colors={ pickerColors } color={ pollColor } onChange={ color => setPollColor(color.hex) } onChangeComplete={ () => setShowPicker(false) } /> }
|
||||
</IonCol>
|
||||
<IonCol size="12">
|
||||
<IonItem lines="full">
|
||||
<IonLabel position="floating">Poll Question</IonLabel>
|
||||
<IonTextarea rows="2" value={ pollQuestion } onIonChange={ e => setPollQuestion(e.target.value) } placeholder="A question to ask..." />
|
||||
</IonItem>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<IonRow className="ion-margin-top">
|
||||
<IonCol size="12" className="ion-padding-start">
|
||||
<IonCardTitle>Poll Duration</IonCardTitle>
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="12">
|
||||
<IonRow className="ion-justify-content-center ion-align-items-center ion-text-center">
|
||||
<PollDuration label="Days" value={ pollDays } setter={ setPollDays } />
|
||||
<PollDuration label="Hours" value={ pollHours } setter={ setPollHours } />
|
||||
<PollDuration label="Mins" value={ pollMins } setter={ setPollMins } />
|
||||
</IonRow>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<IonRow className="ion-margin-top ion-align-items-center">
|
||||
<IonCol size="10" className="ion-padding-start">
|
||||
<IonCardTitle className="ion-justify-content-between">
|
||||
Poll Answers
|
||||
</IonCardTitle>
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="2">
|
||||
<IonButton onClick={ addAnswer } disabled={ !pollAnswers.length > 0 }>
|
||||
<IonIcon icon={ addOutline } />
|
||||
</IonButton>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
{ pollAnswers.length > 0 && pollAnswers.map((answer, index) => {
|
||||
|
||||
return <PollAnswer key={ `pollAnswer_${ index }` } index={ index } value={ answer } remove={ removeAnswer } change={ handleChange } />;
|
||||
})}
|
||||
|
||||
<IonRow>
|
||||
{ !pollAnswers.length &&
|
||||
|
||||
<IonCol size="12">
|
||||
<IonItem lines="full" className="ion-justify-content-center ion-align-items-center">
|
||||
<IonLabel className="ion-text-center">
|
||||
<p>There are currenty no answers added for this poll.</p>
|
||||
<IonButton color="success" onClick={ addAnswer }>Add one now</IonButton>
|
||||
</IonLabel>
|
||||
</IonItem>
|
||||
</IonCol>
|
||||
}
|
||||
</IonRow>
|
||||
</IonGrid>
|
||||
</IonContent>
|
||||
|
||||
<IonFooter className="ion-padding-bottom">
|
||||
<IonRow className="ion-padding-start ion-padding-end ion-padding-bottom ion-padding-top">
|
||||
<IonCol size="12">
|
||||
<IonButton fill="outline" expand="block" onClick={ handleAdd }>
|
||||
Save
|
||||
</IonButton>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonFooter>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Add;
|
@@ -0,0 +1,96 @@
|
||||
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;
|
@@ -0,0 +1,81 @@
|
||||
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;
|
@@ -0,0 +1,91 @@
|
||||
import {
|
||||
IonButton,
|
||||
IonButtons,
|
||||
IonCard,
|
||||
IonCardHeader,
|
||||
IonCardSubtitle,
|
||||
IonCardTitle,
|
||||
IonCol,
|
||||
IonContent,
|
||||
IonFooter,
|
||||
IonHeader,
|
||||
IonIcon,
|
||||
IonMenuButton,
|
||||
IonPage,
|
||||
IonRow,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
} from '@ionic/react';
|
||||
import { arrowForwardOutline } from 'ionicons/icons';
|
||||
import { getCardStyle } from '../helpers/utils';
|
||||
import { PollStore } from '../store';
|
||||
import { getPolls } from '../store/Selectors';
|
||||
import styles from './View.module.scss';
|
||||
|
||||
const View = () => {
|
||||
const polls = PollStore.useState(getPolls);
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonMenuButton />
|
||||
</IonButtons>
|
||||
<IonTitle>Ionic Polls</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">Ionic Polls</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
{polls.map((poll) => {
|
||||
const colors = getCardStyle(poll.color);
|
||||
|
||||
return (
|
||||
<IonCard
|
||||
className={`${styles.pollQuestion} animate__animated animate__fadeIn`}
|
||||
style={{ backgroundColor: colors.backgroundColor }}
|
||||
routerLink={`/demo-react-poll-app/page/view/${poll.id}`}
|
||||
routerDirection="forward"
|
||||
>
|
||||
<IonRow className="ion-align-items-center">
|
||||
<IonCol size="9">
|
||||
<IonCardHeader>
|
||||
<IonCardTitle style={{ color: colors.textColor }}>{poll.question}</IonCardTitle>
|
||||
<IonCardSubtitle style={{ color: colors.subTextColor }}>{poll.timeLeft} left</IonCardSubtitle>
|
||||
<p style={{ color: colors.textColor }}>{poll.totalVotes} votes already</p>
|
||||
<p style={colors.statusBadge}>{poll.voted ? 'You have voted on this poll' : "You haven't voted on this poll"}</p>
|
||||
</IonCardHeader>
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="3">
|
||||
<IonButton style={colors.buttonStyle}>
|
||||
View
|
||||
<IonIcon icon={arrowForwardOutline} />
|
||||
</IonButton>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonCard>
|
||||
);
|
||||
})}
|
||||
</IonContent>
|
||||
|
||||
<IonFooter className="ion-padding-bottom">
|
||||
<IonRow className="ion-padding-start ion-padding-end ion-padding-bottom ion-padding-top">
|
||||
<IonCol size="12">
|
||||
<IonButton expand="block" routerLink="/demo-react-poll-app/page/add">
|
||||
Add new poll
|
||||
</IonButton>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonFooter>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default View;
|