mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-09 21:07:46 +00:00
Add API for frameworks and examples (#3514)
* Add API for frameworks and examples * Adjust headers * Update frameworks list * Always use latest * Add types * Use now repo for downloading and listing * Use .existsSync * Remove unused packages * Use 307 for redirect * Add examples * Update tsconfig.json Co-Authored-By: Steven <steven@ceriously.com> * Make examples unique * Remove detectors from frameworks API * Use /api instead of Next.js * Install dependencies * Rename project * Change name * Empty * Change name * Update api/tsconfig.json Co-Authored-By: Steven <steven@ceriously.com> * Update examples Co-authored-by: Steven <steven@ceriously.com>
This commit is contained in:
15
examples/ionic-react/.editorconfig
Normal file
15
examples/ionic-react/.editorconfig
Normal file
@@ -0,0 +1,15 @@
|
||||
# http://editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
insert_final_newline = false
|
||||
trim_trailing_whitespace = false
|
||||
83
examples/ionic-react/.gitignore
vendored
Normal file
83
examples/ionic-react/.gitignore
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
# Logs
|
||||
.firebase
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
.stencil/
|
||||
8
examples/ionic-react/capacitor.config.json
Normal file
8
examples/ionic-react/capacitor.config.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"appId": "io.ionic.starter",
|
||||
"appName": "ionic-react-conference-app",
|
||||
"bundledWebRuntime": false,
|
||||
"npmClient": "npm",
|
||||
"webDir": "build",
|
||||
"cordova": {}
|
||||
}
|
||||
7
examples/ionic-react/ionic.config.json
Normal file
7
examples/ionic-react/ionic.config.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "ionic-react-conference-app",
|
||||
"integrations": {
|
||||
"capacitor": {}
|
||||
},
|
||||
"type": "react"
|
||||
}
|
||||
44
examples/ionic-react/package.json
Normal file
44
examples/ionic-react/package.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "ionic-react-conference-app",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@capacitor/core": "1.3.0",
|
||||
"@ionic/react": "^4.11.4",
|
||||
"@ionic/react-router": "^4.11.4",
|
||||
"@types/jest": "24.0.18",
|
||||
"@types/node": "12.7.5",
|
||||
"@types/react": "^16.9.2",
|
||||
"@types/react-dom": "^16.9.0",
|
||||
"@types/react-router": "^5.0.3",
|
||||
"@types/react-router-dom": "^4.3.1",
|
||||
"date-fns": "^2.6.0",
|
||||
"ionicons": "^4.6.3",
|
||||
"node-sass": "^4.13.0",
|
||||
"react": "^16.9.0",
|
||||
"react-dom": "^16.9.0",
|
||||
"react-router": "^5.0.1",
|
||||
"react-router-dom": "^5.0.1",
|
||||
"react-scripts": "3.2.0",
|
||||
"reselect": "^4.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"browserslist": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not ie <= 11",
|
||||
"not op_mini all"
|
||||
],
|
||||
"description": "An Ionic project",
|
||||
"devDependencies": {
|
||||
"@capacitor/cli": "1.3.0",
|
||||
"@testing-library/react": "^9.3.1",
|
||||
"@types/googlemaps": "^3.38.0",
|
||||
"typescript": "3.6.3"
|
||||
}
|
||||
}
|
||||
27
examples/ionic-react/readme.md
Normal file
27
examples/ionic-react/readme.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Ionic React Example
|
||||
|
||||
This directory is a brief example of an [Ionic React](https://ionicframework.com/docs/react/overview) app that can be deployed with ZEIT Now and zero configuration
|
||||
|
||||
## Deploy Your Own
|
||||
|
||||
Deploy your own Ionic React project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now-examples/tree/master/ionic-react)
|
||||
|
||||
_Live Example: https://ionic-react.now-examples.now.sh_
|
||||
|
||||
### How We Created This Example
|
||||
|
||||
To get started with Ionic React deployed with ZEIT Now, you can use the [Ionic CLI](https://ionicframework.com/docs/cli) to initialize the project:
|
||||
|
||||
```shell
|
||||
$ npx ionic start [project-name] conference --type react && cd [project-name]
|
||||
```
|
||||
|
||||
### Deploying From Your Terminal
|
||||
|
||||
You can deploy your new Ionic React project with a single command from your terminal using [Now CLI](https://zeit.co/download):
|
||||
|
||||
```shell
|
||||
$ now
|
||||
```
|
||||
12
examples/ionic-react/src/App.test.tsx
Normal file
12
examples/ionic-react/src/App.test.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
import { render, fireEvent, waitForElement } from '@testing-library/react'
|
||||
|
||||
it('renders without crashing', () => {
|
||||
// const div = document.createElement('div');
|
||||
// ReactDOM.render(<App />, div);
|
||||
// ReactDOM.unmountComponentAtNode(div);
|
||||
const { asFragment, container } = render(<App />);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
107
examples/ionic-react/src/App.tsx
Normal file
107
examples/ionic-react/src/App.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { Redirect, Route } from 'react-router-dom';
|
||||
import { IonApp, IonRouterOutlet, IonSplitPane } from '@ionic/react';
|
||||
import { IonReactRouter } from '@ionic/react-router';
|
||||
|
||||
import Menu from './components/Menu';
|
||||
|
||||
/* Core CSS required for Ionic components to work properly */
|
||||
import '@ionic/react/css/core.css';
|
||||
|
||||
/* Basic CSS for apps built with Ionic */
|
||||
import '@ionic/react/css/normalize.css';
|
||||
import '@ionic/react/css/structure.css';
|
||||
import '@ionic/react/css/typography.css';
|
||||
|
||||
/* Optional CSS utils that can be commented out */
|
||||
import '@ionic/react/css/padding.css';
|
||||
import '@ionic/react/css/float-elements.css';
|
||||
import '@ionic/react/css/text-alignment.css';
|
||||
import '@ionic/react/css/text-transformation.css';
|
||||
import '@ionic/react/css/flex-utils.css';
|
||||
import '@ionic/react/css/display.css';
|
||||
|
||||
/* Theme variables */
|
||||
import './theme/variables.css';
|
||||
import MainTabs from './pages/MainTabs';
|
||||
import { connect } from './data/connect';
|
||||
import { AppContextProvider } from './data/AppContext';
|
||||
import { loadConfData } from './data/sessions/sessions.actions';
|
||||
import { setIsLoggedIn, setUsername, loadUserData } from './data/user/user.actions';
|
||||
import Account from './pages/Account';
|
||||
import Login from './pages/Login';
|
||||
import Signup from './pages/Signup';
|
||||
import Support from './pages/Support';
|
||||
import Tutorial from './pages/Tutorial';
|
||||
import HomeOrTutorial from './components/HomeOrTutorial';
|
||||
import { Session } from "./models/Session";
|
||||
|
||||
const App: React.FC = () => {
|
||||
return (
|
||||
<AppContextProvider>
|
||||
<IonicAppConnected />
|
||||
</AppContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
interface StateProps {
|
||||
darkMode: boolean,
|
||||
sessions: Session[],
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
loadConfData: typeof loadConfData;
|
||||
loadUserData: typeof loadUserData;
|
||||
setIsLoggedIn: typeof setIsLoggedIn;
|
||||
setUsername: typeof setUsername;
|
||||
}
|
||||
|
||||
interface IonicAppProps extends StateProps, DispatchProps { }
|
||||
|
||||
const IonicApp: React.FC<IonicAppProps> = ({ darkMode, sessions, setIsLoggedIn, setUsername, loadConfData, loadUserData }) => {
|
||||
|
||||
useEffect(() => {
|
||||
loadUserData();
|
||||
loadConfData();
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
|
||||
return (
|
||||
sessions.length === 0 ? (
|
||||
<div></div>
|
||||
) : (
|
||||
<IonApp className={`${darkMode ? 'dark-theme' : ''}`}>
|
||||
<IonReactRouter>
|
||||
<IonSplitPane contentId="main">
|
||||
<Menu />
|
||||
<IonRouterOutlet id="main">
|
||||
<Route path="/tabs" component={MainTabs} />
|
||||
<Route path="/account" component={Account} />
|
||||
<Route path="/login" component={Login} />
|
||||
<Route path="/signup" component={Signup} />
|
||||
<Route path="/support" component={Support} />
|
||||
<Route path="/tutorial" component={Tutorial} />
|
||||
<Route path="/logout" render={() => {
|
||||
setIsLoggedIn(false);
|
||||
setUsername(undefined);
|
||||
return <Redirect to="/tabs" />
|
||||
}} />
|
||||
<Route path="/" component={HomeOrTutorial} exact />
|
||||
</IonRouterOutlet>
|
||||
</IonSplitPane>
|
||||
</IonReactRouter>
|
||||
</IonApp>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
||||
const IonicAppConnected = connect<{}, StateProps, DispatchProps>({
|
||||
mapStateToProps: (state) => ({
|
||||
darkMode: state.user.darkMode,
|
||||
sessions: state.data.sessions
|
||||
}),
|
||||
mapDispatchToProps: { loadConfData, loadUserData, setIsLoggedIn, setUsername },
|
||||
component: IonicApp
|
||||
});
|
||||
280
examples/ionic-react/src/__snapshots__/App.test.tsx.snap
Normal file
280
examples/ionic-react/src/__snapshots__/App.test.tsx.snap
Normal file
@@ -0,0 +1,280 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders without crashing 1`] = `
|
||||
<DocumentFragment>
|
||||
<ion-app>
|
||||
<ion-split-pane
|
||||
content-id="main"
|
||||
>
|
||||
<ion-menu
|
||||
content-id="main"
|
||||
>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>
|
||||
Menu
|
||||
</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content
|
||||
class="outer-content"
|
||||
>
|
||||
<ion-list>
|
||||
<ion-list-header>
|
||||
Navigate
|
||||
</ion-list-header>
|
||||
<ion-menu-toggle
|
||||
auto-hide="false"
|
||||
>
|
||||
<ion-item>
|
||||
<ion-icon
|
||||
slot="start"
|
||||
/>
|
||||
<ion-label>
|
||||
Schedule
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-menu-toggle>
|
||||
<ion-menu-toggle
|
||||
auto-hide="false"
|
||||
>
|
||||
<ion-item>
|
||||
<ion-icon
|
||||
slot="start"
|
||||
/>
|
||||
<ion-label>
|
||||
Speakers
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-menu-toggle>
|
||||
<ion-menu-toggle
|
||||
auto-hide="false"
|
||||
>
|
||||
<ion-item>
|
||||
<ion-icon
|
||||
slot="start"
|
||||
/>
|
||||
<ion-label>
|
||||
Map
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-menu-toggle>
|
||||
<ion-menu-toggle
|
||||
auto-hide="false"
|
||||
>
|
||||
<ion-item>
|
||||
<ion-icon
|
||||
slot="start"
|
||||
/>
|
||||
<ion-label>
|
||||
About
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-menu-toggle>
|
||||
</ion-list>
|
||||
<ion-list>
|
||||
<ion-list-header>
|
||||
Account
|
||||
</ion-list-header>
|
||||
<ion-menu-toggle
|
||||
auto-hide="false"
|
||||
>
|
||||
<ion-item>
|
||||
<ion-icon
|
||||
slot="start"
|
||||
/>
|
||||
<ion-label>
|
||||
Account
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-menu-toggle>
|
||||
<ion-menu-toggle
|
||||
auto-hide="false"
|
||||
>
|
||||
<ion-item>
|
||||
<ion-icon
|
||||
slot="start"
|
||||
/>
|
||||
<ion-label>
|
||||
Support
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-menu-toggle>
|
||||
<ion-menu-toggle
|
||||
auto-hide="false"
|
||||
>
|
||||
<ion-item>
|
||||
<ion-icon
|
||||
slot="start"
|
||||
/>
|
||||
<ion-label>
|
||||
Logout
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-menu-toggle>
|
||||
</ion-list>
|
||||
<ion-list>
|
||||
<ion-list-header>
|
||||
Tutorial
|
||||
</ion-list-header>
|
||||
<ion-item>
|
||||
<ion-icon
|
||||
slot="start"
|
||||
/>
|
||||
Show Tutorial
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-content>
|
||||
</ion-menu>
|
||||
<ion-router-outlet
|
||||
id="main"
|
||||
>
|
||||
<div
|
||||
style="display: flex; position: absolute; top: 0px; left: 0px; right: 0px; bottom: 0px; flex-direction: column; width: 100%; height: 100%; contain: layout size style;"
|
||||
>
|
||||
<div
|
||||
class="tabs-inner"
|
||||
style="position: relative; flex: 1; contain: layout size style;"
|
||||
>
|
||||
<ion-router-outlet>
|
||||
<div
|
||||
class="ion-page ion-page-invisible"
|
||||
>
|
||||
<ion-header>
|
||||
<ion-toolbar
|
||||
color="primary"
|
||||
>
|
||||
<ion-buttons
|
||||
slot="start"
|
||||
>
|
||||
<ion-menu-button />
|
||||
</ion-buttons>
|
||||
<ion-segment>
|
||||
<ion-segment-button
|
||||
value="all"
|
||||
>
|
||||
All
|
||||
</ion-segment-button>
|
||||
<ion-segment-button
|
||||
value="favorites"
|
||||
>
|
||||
Favorites
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
<ion-buttons
|
||||
slot="end"
|
||||
>
|
||||
<ion-button>
|
||||
<ion-icon
|
||||
slot="icon-only"
|
||||
/>
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
<ion-toolbar
|
||||
color="primary"
|
||||
>
|
||||
<ion-searchbar
|
||||
placeholder="Search"
|
||||
/>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-refresher
|
||||
slot="fixed"
|
||||
>
|
||||
<ion-refresher-content />
|
||||
</ion-refresher>
|
||||
<ion-list>
|
||||
<ion-list-header>
|
||||
No Sessions Found
|
||||
</ion-list-header>
|
||||
</ion-list>
|
||||
<ion-list
|
||||
style="display: none;"
|
||||
/>
|
||||
</ion-content>
|
||||
<ion-fab
|
||||
horizontal="end"
|
||||
slot="fixed"
|
||||
vertical="bottom"
|
||||
>
|
||||
<ion-fab-button>
|
||||
<ion-icon />
|
||||
</ion-fab-button>
|
||||
<ion-fab-list
|
||||
side="top"
|
||||
>
|
||||
<ion-fab-button
|
||||
color="vimeo"
|
||||
>
|
||||
<ion-icon />
|
||||
</ion-fab-button>
|
||||
<ion-fab-button
|
||||
color="google"
|
||||
>
|
||||
<ion-icon />
|
||||
</ion-fab-button>
|
||||
<ion-fab-button
|
||||
color="twitter"
|
||||
>
|
||||
<ion-icon />
|
||||
</ion-fab-button>
|
||||
<ion-fab-button
|
||||
color="facebook"
|
||||
>
|
||||
<ion-icon />
|
||||
</ion-fab-button>
|
||||
</ion-fab-list>
|
||||
</ion-fab>
|
||||
</div>
|
||||
</ion-router-outlet>
|
||||
</div>
|
||||
<ion-tab-bar
|
||||
current-path="/tabs/schedule"
|
||||
selected-tab="schedule"
|
||||
slot="bottom"
|
||||
>
|
||||
<ion-tab-button
|
||||
href="/tabs/schedule"
|
||||
tab="schedule"
|
||||
>
|
||||
<ion-icon />
|
||||
<ion-label>
|
||||
Schedule
|
||||
</ion-label>
|
||||
</ion-tab-button>
|
||||
<ion-tab-button
|
||||
href="/tabs/speakers"
|
||||
tab="speakers"
|
||||
>
|
||||
<ion-icon />
|
||||
<ion-label>
|
||||
Speakers
|
||||
</ion-label>
|
||||
</ion-tab-button>
|
||||
<ion-tab-button
|
||||
href="/tabs/map"
|
||||
tab="map"
|
||||
>
|
||||
<ion-icon />
|
||||
<ion-label>
|
||||
Map
|
||||
</ion-label>
|
||||
</ion-tab-button>
|
||||
<ion-tab-button
|
||||
href="/tabs/about"
|
||||
tab="about"
|
||||
>
|
||||
<ion-icon />
|
||||
<ion-label>
|
||||
About
|
||||
</ion-label>
|
||||
</ion-tab-button>
|
||||
</ion-tab-bar>
|
||||
</div>
|
||||
</ion-router-outlet>
|
||||
</ion-split-pane>
|
||||
</ion-app>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
36
examples/ionic-react/src/components/AboutPopover.tsx
Normal file
36
examples/ionic-react/src/components/AboutPopover.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import { IonList, IonItem, IonLabel } from '@ionic/react';
|
||||
|
||||
interface AboutPopoverProps {
|
||||
dismiss: () => void;
|
||||
};
|
||||
|
||||
const AboutPopover: React.FC<AboutPopoverProps> = ({dismiss}) => {
|
||||
|
||||
const close = (url: string) => {
|
||||
window.open(url, '_blank');
|
||||
dismiss();
|
||||
};
|
||||
|
||||
return (
|
||||
<IonList>
|
||||
<IonItem button onClick={() => close('https://ionicframework.com/getting-started')}>
|
||||
<IonLabel>Learn Ionic</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem button onClick={() => close('https://ionicframework.com/docs/react')}>
|
||||
<IonLabel>Documentation</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem button onClick={() => close('https://showcase.ionicframework.com')}>
|
||||
<IonLabel>Showcase</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem button onClick={() => close('https://github.com/ionic-team/ionic')}>
|
||||
<IonLabel>GitHub Repo</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem button onClick={dismiss}>
|
||||
<IonLabel>Support</IonLabel>
|
||||
</IonItem>
|
||||
</IonList >
|
||||
)
|
||||
}
|
||||
|
||||
export default AboutPopover;
|
||||
18
examples/ionic-react/src/components/HomeOrTutorial.tsx
Normal file
18
examples/ionic-react/src/components/HomeOrTutorial.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import { connect } from '../data/connect';
|
||||
import { Redirect } from 'react-router';
|
||||
|
||||
interface StateProps {
|
||||
hasSeenTutorial: boolean;
|
||||
}
|
||||
|
||||
const HomeOrTutorial: React.FC<StateProps> = ({ hasSeenTutorial }) => {
|
||||
return hasSeenTutorial ? <Redirect to="/tabs/schedule" /> : <Redirect to="/tutorial" />
|
||||
};
|
||||
|
||||
export default connect<{}, StateProps, {}>({
|
||||
mapStateToProps: (state) => ({
|
||||
hasSeenTutorial: state.user.hasSeenTutorial
|
||||
}),
|
||||
component: HomeOrTutorial
|
||||
});
|
||||
56
examples/ionic-react/src/components/Map.tsx
Normal file
56
examples/ionic-react/src/components/Map.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
import { Location } from '../models/Location';
|
||||
|
||||
interface MapProps {
|
||||
locations: Location[]
|
||||
mapCenter: Location
|
||||
}
|
||||
|
||||
const Map: React.FC<MapProps> = ({ mapCenter, locations }) => {
|
||||
const mapEle = useRef<HTMLDivElement>(null);
|
||||
const map = useRef<google.maps.Map>();
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
map.current = new google.maps.Map(mapEle.current, {
|
||||
center: {
|
||||
lat: mapCenter.lat,
|
||||
lng: mapCenter.lng
|
||||
},
|
||||
zoom: 16
|
||||
});
|
||||
|
||||
addMarkers();
|
||||
|
||||
google.maps.event.addListenerOnce(map.current, 'idle', () => {
|
||||
if (mapEle.current) {
|
||||
mapEle.current.classList.add('show-map');
|
||||
}
|
||||
});
|
||||
|
||||
function addMarkers() {
|
||||
locations.forEach((markerData) => {
|
||||
let infoWindow = new google.maps.InfoWindow({
|
||||
content: `<h5>${markerData.name}</h5>`
|
||||
});
|
||||
|
||||
let marker = new google.maps.Marker({
|
||||
position: new google.maps.LatLng(markerData.lat, markerData.lng),
|
||||
map: map.current!,
|
||||
title: markerData.name
|
||||
});
|
||||
|
||||
marker.addListener('click', () => {
|
||||
infoWindow.open(map.current!, marker);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}, [mapCenter, locations]);
|
||||
|
||||
return (
|
||||
<div ref={mapEle} className="map-canvas"></div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Map;
|
||||
119
examples/ionic-react/src/components/Menu.tsx
Normal file
119
examples/ionic-react/src/components/Menu.tsx
Normal file
@@ -0,0 +1,119 @@
|
||||
import {
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonIcon,
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonList,
|
||||
IonListHeader,
|
||||
IonMenu,
|
||||
IonMenuToggle,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
IonToggle
|
||||
} from '@ionic/react';
|
||||
import { calendar, contacts, hammer, help, informationCircle, logIn, logOut, map, person, personAdd } from 'ionicons/icons';
|
||||
import React, { useState } from 'react';
|
||||
import { connect } from '../data/connect';
|
||||
import { RouteComponentProps, withRouter } from 'react-router';
|
||||
import { setDarkMode } from '../data/user/user.actions';
|
||||
|
||||
const routes = {
|
||||
appPages: [
|
||||
{ title: 'Schedule', path: '/tabs/schedule', icon: calendar },
|
||||
{ title: 'Speakers', path: '/tabs/speakers', icon: contacts },
|
||||
{ title: 'Map', path: '/tabs/map', icon: map },
|
||||
{ title: 'About', path: '/tabs/about', icon: informationCircle }
|
||||
],
|
||||
loggedInPages: [
|
||||
{ title: 'Account', path: '/account', icon: person },
|
||||
{ title: 'Support', path: '/support', icon: help },
|
||||
{ title: 'Logout', path: '/logout', icon: logOut }
|
||||
],
|
||||
loggedOutPages: [
|
||||
{ title: 'Login', path: '/login', icon: logIn },
|
||||
{ title: 'Support', path: '/support', icon: help },
|
||||
{ title: 'Signup', path: '/signup', icon: personAdd }
|
||||
]
|
||||
};
|
||||
|
||||
interface Pages {
|
||||
title: string,
|
||||
path: string,
|
||||
icon: { ios: string, md: string },
|
||||
routerDirection?: string
|
||||
}
|
||||
interface StateProps {
|
||||
darkMode: boolean;
|
||||
isAuthenticated: boolean;
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
setDarkMode: typeof setDarkMode
|
||||
}
|
||||
|
||||
interface MenuProps extends RouteComponentProps, StateProps, DispatchProps { }
|
||||
|
||||
const Menu: React.FC<MenuProps> = ({ darkMode, history, isAuthenticated, setDarkMode }) => {
|
||||
const [disableMenu, setDisableMenu] = useState(false);
|
||||
|
||||
function renderlistItems(list: Pages[]) {
|
||||
return list
|
||||
.filter(route => !!route.path)
|
||||
.map(p => (
|
||||
<IonMenuToggle key={p.title} auto-hide="false">
|
||||
<IonItem button routerLink={p.path} routerDirection="none">
|
||||
<IonIcon slot="start" icon={p.icon} />
|
||||
<IonLabel>{p.title}</IonLabel>
|
||||
</IonItem>
|
||||
</IonMenuToggle>
|
||||
));
|
||||
}
|
||||
|
||||
return (
|
||||
<IonMenu type="overlay" disabled={disableMenu} contentId="main">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Menu</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent class="outer-content">
|
||||
<IonList>
|
||||
<IonListHeader>Navigate</IonListHeader>
|
||||
{renderlistItems(routes.appPages)}
|
||||
</IonList>
|
||||
<IonList>
|
||||
<IonListHeader>Account</IonListHeader>
|
||||
{isAuthenticated ? renderlistItems(routes.loggedInPages) : renderlistItems(routes.loggedOutPages)}
|
||||
</IonList>
|
||||
<IonList>
|
||||
<IonListHeader>Tutorial</IonListHeader>
|
||||
<IonItem onClick={() => {
|
||||
setDisableMenu(true);
|
||||
history.push('/tutorial');
|
||||
}}>
|
||||
<IonIcon slot="start" icon={hammer} />
|
||||
Show Tutorial
|
||||
</IonItem>
|
||||
</IonList>
|
||||
<IonList>
|
||||
<IonItem>
|
||||
<IonLabel>Dark Theme</IonLabel>
|
||||
<IonToggle checked={darkMode} onClick={() => setDarkMode(!darkMode)} />
|
||||
</IonItem>
|
||||
</IonList>
|
||||
</IonContent>
|
||||
</IonMenu>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect<{}, StateProps, {}>({
|
||||
mapStateToProps: (state) => ({
|
||||
darkMode: state.user.darkMode,
|
||||
isAuthenticated: state.user.isLoggedin
|
||||
}),
|
||||
mapDispatchToProps: ({
|
||||
setDarkMode
|
||||
}),
|
||||
component: withRouter(Menu)
|
||||
})
|
||||
92
examples/ionic-react/src/components/SessionList.tsx
Normal file
92
examples/ionic-react/src/components/SessionList.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
import { IonItemDivider, IonItemGroup, IonLabel, IonList, IonListHeader, IonAlert, AlertButton } from '@ionic/react';
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { Session } from '../models/Session';
|
||||
import SessionListItem from './SessionListItem';
|
||||
import { SessionGroup } from '../models/SessionGroup';
|
||||
import { Time } from '../components/Time';
|
||||
import { connect } from '../data/connect';
|
||||
import { addFavorite, removeFavorite } from '../data/sessions/sessions.actions';
|
||||
|
||||
interface OwnProps {
|
||||
sessionGroups: SessionGroup[]
|
||||
listType: 'all' | 'favorites'
|
||||
hide: boolean;
|
||||
}
|
||||
|
||||
interface StateProps {
|
||||
favoriteSessions: number[];
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
addFavorite: typeof addFavorite;
|
||||
removeFavorite: typeof removeFavorite;
|
||||
}
|
||||
|
||||
interface SessionListProps extends OwnProps, StateProps, DispatchProps { };
|
||||
|
||||
const SessionList: React.FC<SessionListProps> = ({ addFavorite, removeFavorite, favoriteSessions, hide, sessionGroups, listType }) => {
|
||||
|
||||
const [showAlert, setShowAlert] = useState(false);
|
||||
const [alertHeader, setAlertHeader] = useState('');
|
||||
const [alertButtons, setAlertButtons] = useState<(AlertButton | string)[]>([]);
|
||||
|
||||
const handleShowAlert = useCallback((header: string, buttons: AlertButton[]) => {
|
||||
setAlertHeader(header);
|
||||
setAlertButtons(buttons);
|
||||
setShowAlert(true);
|
||||
}, []);
|
||||
|
||||
if (sessionGroups.length === 0 && !hide) {
|
||||
return (
|
||||
<IonList>
|
||||
<IonListHeader>
|
||||
No Sessions Found
|
||||
</IonListHeader>
|
||||
</IonList>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<IonList style={hide ? { display: 'none' } : {}}>
|
||||
{sessionGroups.map((group, index: number) => (
|
||||
<IonItemGroup key={`group-${index}`}>
|
||||
<IonItemDivider sticky>
|
||||
<IonLabel>
|
||||
<Time date={group.startTime} />
|
||||
</IonLabel>
|
||||
</IonItemDivider>
|
||||
{group.sessions.map((session: Session, sessionIndex: number) => (
|
||||
<SessionListItem
|
||||
onShowAlert={handleShowAlert}
|
||||
isFavorite={favoriteSessions.indexOf(session.id) > -1}
|
||||
onAddFavorite={addFavorite}
|
||||
onRemoveFavorite={removeFavorite}
|
||||
key={`group-${index}-${sessionIndex}`}
|
||||
session={session}
|
||||
listType={listType}
|
||||
/>
|
||||
))}
|
||||
</IonItemGroup>
|
||||
))}
|
||||
</IonList>
|
||||
<IonAlert
|
||||
isOpen={showAlert}
|
||||
header={alertHeader}
|
||||
buttons={alertButtons}
|
||||
onDidDismiss={() => setShowAlert(false)}
|
||||
></IonAlert>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect({
|
||||
mapStateToProps: (state) => ({
|
||||
favoriteSessions: state.data.favorites
|
||||
}),
|
||||
mapDispatchToProps: ({
|
||||
addFavorite,
|
||||
removeFavorite
|
||||
}),
|
||||
component: SessionList
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
.filter-icon {
|
||||
margin: 7px 16px 7px 0;
|
||||
}
|
||||
108
examples/ionic-react/src/components/SessionListFilter.tsx
Normal file
108
examples/ionic-react/src/components/SessionListFilter.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
import React from 'react';
|
||||
import { IonHeader, IonToolbar, IonButtons, IonButton, IonTitle, IonContent, IonList, IonListHeader, IonItem, IonLabel, IonToggle, IonFooter, IonIcon } from '@ionic/react';
|
||||
import { logoAngular, call, document, logoIonic, hammer, restaurant, cog, colorPalette, construct, compass } from 'ionicons/icons';
|
||||
import './SessionListFilter.css'
|
||||
import { connect } from '../data/connect';
|
||||
import { updateFilteredTracks } from '../data/sessions/sessions.actions';
|
||||
|
||||
interface OwnProps {
|
||||
onDismissModal: () => void;
|
||||
}
|
||||
|
||||
interface StateProps {
|
||||
allTracks: string[],
|
||||
filteredTracks: string[]
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
updateFilteredTracks: typeof updateFilteredTracks;
|
||||
}
|
||||
|
||||
type SessionListFilterProps = OwnProps & StateProps & DispatchProps;
|
||||
|
||||
const SessionListFilter: React.FC<SessionListFilterProps> = ({ allTracks, filteredTracks, onDismissModal, updateFilteredTracks }) => {
|
||||
|
||||
const toggleTrackFilter = (track: string) => {
|
||||
if (filteredTracks.indexOf(track) > -1) {
|
||||
updateFilteredTracks(filteredTracks.filter(x => x !== track));
|
||||
} else {
|
||||
updateFilteredTracks([...filteredTracks, track]);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeselectAll = () => {
|
||||
updateFilteredTracks([]);
|
||||
};
|
||||
|
||||
const handleSelectAll = () => {
|
||||
updateFilteredTracks([...allTracks]);
|
||||
};
|
||||
|
||||
const iconMap: { [key: string]: any } = {
|
||||
'Angular': logoAngular,
|
||||
'Documentation': document,
|
||||
'Food': restaurant,
|
||||
'Ionic': logoIonic,
|
||||
'Tooling': hammer,
|
||||
'Design': colorPalette,
|
||||
'Services': cog,
|
||||
'Workshop': construct,
|
||||
'Navigation': compass,
|
||||
'Communication': call
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>
|
||||
Filter Sessions
|
||||
</IonTitle>
|
||||
<IonButtons slot="end">
|
||||
<IonButton onClick={onDismissModal} strong>Done</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent class="outer-content">
|
||||
<IonList>
|
||||
<IonListHeader>Tracks</IonListHeader>
|
||||
{allTracks.map((track, index) => (
|
||||
<IonItem key={track}>
|
||||
<IonIcon className="filter-icon" icon={iconMap[track]} color="medium" />
|
||||
<IonLabel>{track}</IonLabel>
|
||||
<IonToggle
|
||||
onClick={() => toggleTrackFilter(track)}
|
||||
checked={filteredTracks.indexOf(track) !== -1}
|
||||
color="success"
|
||||
value={track}
|
||||
></IonToggle>
|
||||
</IonItem>
|
||||
))}
|
||||
</IonList>
|
||||
</IonContent>
|
||||
|
||||
<IonFooter>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonButton onClick={handleDeselectAll}>Deselect All</IonButton>
|
||||
</IonButtons>
|
||||
<IonButtons slot="end">
|
||||
<IonButton onClick={handleSelectAll}>Select All</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonFooter>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect<OwnProps, StateProps, DispatchProps>({
|
||||
mapStateToProps: (state) => ({
|
||||
allTracks: state.data.allTracks,
|
||||
filteredTracks: state.data.filteredTracks
|
||||
}),
|
||||
mapDispatchToProps: {
|
||||
updateFilteredTracks
|
||||
},
|
||||
component: SessionListFilter
|
||||
})
|
||||
83
examples/ionic-react/src/components/SessionListItem.tsx
Normal file
83
examples/ionic-react/src/components/SessionListItem.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { IonItemSliding, IonAlert, IonItem, IonLabel, IonItemOptions, IonItemOption, AlertButton } from '@ionic/react';
|
||||
import { Time } from './Time';
|
||||
import { Session } from '../models/Session';
|
||||
|
||||
interface SessionListItemProps {
|
||||
session: Session;
|
||||
listType: "all" | "favorites";
|
||||
onAddFavorite: (id: number) => void;
|
||||
onRemoveFavorite: (id: number) => void;
|
||||
onShowAlert: (header: string, buttons: AlertButton[]) => void;
|
||||
isFavorite: boolean;
|
||||
}
|
||||
|
||||
const SessionListItem: React.FC<SessionListItemProps> = ({ isFavorite, onAddFavorite, onRemoveFavorite, onShowAlert, session, listType }) => {
|
||||
const ionItemSlidingRef = useRef<HTMLIonItemSlidingElement>(null)
|
||||
|
||||
const dismissAlert = () => {
|
||||
ionItemSlidingRef.current && ionItemSlidingRef.current.close();
|
||||
}
|
||||
|
||||
const removeFavoriteSession = () => {
|
||||
onAddFavorite(session.id);
|
||||
onShowAlert('Favorite already added', [
|
||||
{
|
||||
text: 'Cancel',
|
||||
handler: dismissAlert
|
||||
},
|
||||
{
|
||||
text: 'Remove',
|
||||
handler: () => {
|
||||
onRemoveFavorite(session.id);
|
||||
dismissAlert();
|
||||
}
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
const addFavoriteSession = () => {
|
||||
if (isFavorite) {
|
||||
// woops, they already favorited it! What shall we do!?
|
||||
// prompt them to remove it
|
||||
removeFavoriteSession();
|
||||
} else {
|
||||
// remember this session as a user favorite
|
||||
onAddFavorite(session.id);
|
||||
onShowAlert('Favorite Added', [
|
||||
{
|
||||
text: 'OK',
|
||||
handler: dismissAlert
|
||||
}
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<IonItemSliding ref={ionItemSlidingRef} class={'track-' + session.tracks[0].toLowerCase()}>
|
||||
<IonItem routerLink={`/tabs/schedule/${session.id}`}>
|
||||
<IonLabel>
|
||||
<h3>{session.name}</h3>
|
||||
<p>
|
||||
<Time date={session.dateTimeStart} /> —
|
||||
<Time date={session.dateTimeEnd} /> —
|
||||
{session.location}
|
||||
</p>
|
||||
</IonLabel>
|
||||
</IonItem>
|
||||
<IonItemOptions>
|
||||
{listType === "favorites" ?
|
||||
<IonItemOption color="danger" onClick={() => removeFavoriteSession()}>
|
||||
Remove
|
||||
</IonItemOption>
|
||||
:
|
||||
<IonItemOption color="favorite" onClick={addFavoriteSession}>
|
||||
Favorite
|
||||
</IonItemOption>
|
||||
}
|
||||
</IonItemOptions>
|
||||
</IonItemSliding>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(SessionListItem);
|
||||
46
examples/ionic-react/src/components/ShareSocialFab.tsx
Normal file
46
examples/ionic-react/src/components/ShareSocialFab.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { IonLoading, IonFab, IonFabButton, IonIcon, IonFabList } from "@ionic/react"
|
||||
import { share, logoVimeo, logoGoogleplus, logoTwitter, logoFacebook } from "ionicons/icons"
|
||||
import React, { useState } from "react"
|
||||
|
||||
const ShareSocialFab: React.FC = () => {
|
||||
const [loadingMessage, setLoadingMessage] = useState('')
|
||||
const [showLoading, setShowLoading] = useState(false);
|
||||
|
||||
const openSocial = (network: string) => {
|
||||
setLoadingMessage(`Posting to ${network}`);
|
||||
setShowLoading(true);
|
||||
};
|
||||
|
||||
return(
|
||||
<>
|
||||
<IonLoading
|
||||
isOpen={showLoading}
|
||||
message={loadingMessage}
|
||||
duration={2000}
|
||||
spinner="crescent"
|
||||
onDidDismiss={() => setShowLoading(false)}
|
||||
/>
|
||||
<IonFab slot="fixed" vertical="bottom" horizontal="end">
|
||||
<IonFabButton>
|
||||
<IonIcon icon={share} />
|
||||
</IonFabButton>
|
||||
<IonFabList side="top">
|
||||
<IonFabButton color="vimeo" onClick={() => openSocial('Vimeo')}>
|
||||
<IonIcon icon={logoVimeo} />
|
||||
</IonFabButton>
|
||||
<IonFabButton color="google" onClick={() => openSocial('Google+')}>
|
||||
<IonIcon icon={logoGoogleplus} />
|
||||
</IonFabButton>
|
||||
<IonFabButton color="twitter" onClick={() => openSocial('Twitter')}>
|
||||
<IonIcon icon={logoTwitter} />
|
||||
</IonFabButton>
|
||||
<IonFabButton color="facebook" onClick={() => openSocial('Facebook')}>
|
||||
<IonIcon icon={logoFacebook} />
|
||||
</IonFabButton>
|
||||
</IonFabList>
|
||||
</IonFab>
|
||||
</>
|
||||
)
|
||||
};
|
||||
|
||||
export default ShareSocialFab;
|
||||
125
examples/ionic-react/src/components/SpeakerItem.tsx
Normal file
125
examples/ionic-react/src/components/SpeakerItem.tsx
Normal file
@@ -0,0 +1,125 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Session } from '../models/Session';
|
||||
import { Speaker } from '../models/Speaker';
|
||||
import { IonCard, IonCardHeader, IonItem, IonAvatar, IonCardContent, IonList, IonRow, IonCol, IonButton, IonIcon, IonActionSheet } from '@ionic/react';
|
||||
import { logoTwitter, shareAlt, chatboxes } from 'ionicons/icons';
|
||||
import { ActionSheetButton } from '@ionic/core';
|
||||
|
||||
interface SpeakerItemProps {
|
||||
speaker: Speaker;
|
||||
sessions: Session[];
|
||||
}
|
||||
|
||||
const SpeakerItem: React.FC<SpeakerItemProps> = ({ speaker, sessions }) => {
|
||||
const [showActionSheet, setShowActionSheet] = useState(false);
|
||||
const [actionSheetButtons, setActionSheetButtons] = useState<ActionSheetButton[]>([]);
|
||||
const [actionSheetHeader, setActionSheetHeader] = useState('');
|
||||
|
||||
function openSpeakerShare(speaker: Speaker) {
|
||||
setActionSheetButtons([
|
||||
{
|
||||
text: 'Copy Link',
|
||||
handler: () => {
|
||||
console.log('Copy Link clicked');
|
||||
}
|
||||
},
|
||||
{
|
||||
text: 'Share via ...',
|
||||
handler: () => {
|
||||
console.log('Share via clicked');
|
||||
}
|
||||
},
|
||||
{
|
||||
text: 'Cancel',
|
||||
role: 'cancel',
|
||||
handler: () => {
|
||||
console.log('Cancel clicked');
|
||||
}
|
||||
}
|
||||
]);
|
||||
setActionSheetHeader(`Share ${speaker.name}`);
|
||||
setShowActionSheet(true);
|
||||
}
|
||||
|
||||
function openContact(speaker: Speaker) {
|
||||
setActionSheetButtons([
|
||||
{
|
||||
text: `Email ( ${speaker.email} )`,
|
||||
handler: () => {
|
||||
window.open('mailto:' + speaker.email);
|
||||
}
|
||||
},
|
||||
{
|
||||
text: `Call ( ${speaker.phone} )`,
|
||||
handler: () => {
|
||||
window.open('tel:' + speaker.phone);
|
||||
}
|
||||
}
|
||||
]);
|
||||
setActionSheetHeader(`Share ${speaker.name}`);
|
||||
setShowActionSheet(true);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<IonCard className="speaker-card">
|
||||
<IonCardHeader>
|
||||
<IonItem button detail={false} routerLink={`/tabs/speakers/${speaker.id}`} lines="none">
|
||||
<IonAvatar slot="start">
|
||||
<img src={process.env.PUBLIC_URL + speaker.profilePic} alt="Speaker profile pic" />
|
||||
</IonAvatar>
|
||||
{speaker.name}
|
||||
</IonItem>
|
||||
</IonCardHeader>
|
||||
|
||||
<IonCardContent class="outer-content">
|
||||
<IonList>
|
||||
{sessions.map(session => (
|
||||
<IonItem routerLink={`/tabs/speakers/sessions/${session.id}`} key={session.name}>
|
||||
<h3>{session.name}</h3>
|
||||
</IonItem>
|
||||
))}
|
||||
<IonItem button routerLink={`/tabs/speakers/${speaker.id}`}>
|
||||
<h3>About {speaker.name}</h3>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
</IonCardContent>
|
||||
|
||||
<IonRow justify-content-center>
|
||||
<IonCol text-left size="4">
|
||||
<IonButton
|
||||
fill="clear"
|
||||
size="small"
|
||||
color="primary"
|
||||
href={`https://www.twitter.com/${speaker.twitter}`}
|
||||
target="_blank"
|
||||
>
|
||||
<IonIcon slot="start" icon={logoTwitter} />
|
||||
Tweet
|
||||
</IonButton>
|
||||
</IonCol>
|
||||
<IonCol text-left size="4">
|
||||
<IonButton fill="clear" size="small" color="primary" onClick={() => openSpeakerShare(speaker)}>
|
||||
<IonIcon slot="start" icon={shareAlt} />
|
||||
Share
|
||||
</IonButton>
|
||||
</IonCol>
|
||||
<IonCol text-left size="4">
|
||||
<IonButton fill="clear" size="small" color="primary" onClick={() => openContact(speaker)}>
|
||||
<IonIcon slot="start" icon={chatboxes} />
|
||||
Contact
|
||||
</IonButton>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonCard>
|
||||
<IonActionSheet
|
||||
isOpen={showActionSheet}
|
||||
header={actionSheetHeader}
|
||||
onDidDismiss={() => setShowActionSheet(false)}
|
||||
buttons={actionSheetButtons}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SpeakerItem;
|
||||
8
examples/ionic-react/src/components/Time.tsx
Normal file
8
examples/ionic-react/src/components/Time.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
import { format, parseISO as parseDate } from 'date-fns';
|
||||
|
||||
export const Time: React.FC<{ date: string }> = ({ date }) => (
|
||||
<>
|
||||
{format(parseDate(date), "h:mm aaaaa")}m
|
||||
</>
|
||||
)
|
||||
26
examples/ionic-react/src/data/AppContext.tsx
Normal file
26
examples/ionic-react/src/data/AppContext.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React, { createContext, useReducer } from 'react';
|
||||
import { initialState, AppState, reducers } from './state'
|
||||
|
||||
export interface AppContextState {
|
||||
state: AppState;
|
||||
dispatch: React.Dispatch<any>;
|
||||
}
|
||||
|
||||
export const AppContext = createContext<AppContextState>({
|
||||
state: initialState,
|
||||
dispatch: () => undefined
|
||||
});
|
||||
|
||||
export const AppContextProvider: React.FC = (props => {
|
||||
|
||||
const [store, dispatch] = useReducer(reducers, initialState);
|
||||
|
||||
return (
|
||||
<AppContext.Provider value={{
|
||||
state: store,
|
||||
dispatch
|
||||
}}>
|
||||
{props.children}
|
||||
</AppContext.Provider>
|
||||
)
|
||||
});
|
||||
14
examples/ionic-react/src/data/combineReducers.ts
Normal file
14
examples/ionic-react/src/data/combineReducers.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export function combineReducers<R extends any>(reducers: R) {
|
||||
type keys = keyof typeof reducers;
|
||||
type returnType = { [K in keys]: ReturnType<typeof reducers[K]> };
|
||||
const combinedReducer = (state: any, action: any) => {
|
||||
const newState: returnType = {} as any;
|
||||
const keys = Object.keys(reducers);
|
||||
keys.forEach(key => {
|
||||
const result = reducers[key](state[key], action);
|
||||
newState[key as keys] = result || state[key];
|
||||
});
|
||||
return newState;
|
||||
};
|
||||
return combinedReducer;
|
||||
}
|
||||
49
examples/ionic-react/src/data/connect.tsx
Normal file
49
examples/ionic-react/src/data/connect.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import React, { useContext, useEffect, useState, useMemo } from 'react';
|
||||
import { AppContext } from './AppContext';
|
||||
import { DispatchObject } from '../util/types';
|
||||
import { AppState } from './state';
|
||||
|
||||
interface ConnectParams<TOwnProps, TStateProps, TDispatchProps> {
|
||||
mapStateToProps?: (state: AppState, props: TOwnProps) => TStateProps,
|
||||
mapDispatchToProps?: TDispatchProps,
|
||||
component: React.ComponentType<any>
|
||||
};
|
||||
|
||||
export function connect<TOwnProps = any, TStateProps = any, TDispatchProps = any>({ mapStateToProps = () => ({} as TStateProps), mapDispatchToProps = {} as TDispatchProps, component }: ConnectParams<TOwnProps, TStateProps, TDispatchProps>): React.FunctionComponent<TOwnProps> {
|
||||
|
||||
const Connect = (ownProps: TOwnProps) => {
|
||||
const context = useContext(AppContext);
|
||||
|
||||
const dispatchFuncs = useMemo(() => {
|
||||
const dispatchFuncs: { [key: string]: any } = {};
|
||||
Object.keys(mapDispatchToProps).forEach((key) => {
|
||||
const oldFunc = (mapDispatchToProps as any)[key];
|
||||
const newFunc = (...args: any) => {
|
||||
const dispatchFunc = oldFunc(...args);
|
||||
if (typeof dispatchFunc === 'object') {
|
||||
context.dispatch(dispatchFunc);
|
||||
} else {
|
||||
const result = dispatchFunc(context.dispatch)
|
||||
if (typeof result === 'object' && result.then) {
|
||||
result.then((dispatchObject?: DispatchObject) => {
|
||||
if (dispatchObject && dispatchObject.type) {
|
||||
context.dispatch(dispatchObject);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
dispatchFuncs[key] = newFunc
|
||||
});
|
||||
return dispatchFuncs;
|
||||
}, [mapDispatchToProps])
|
||||
|
||||
|
||||
const props = useMemo(() => {
|
||||
return Object.assign({}, ownProps, mapStateToProps(context.state, ownProps), dispatchFuncs);
|
||||
}, [ownProps, context.state]);
|
||||
|
||||
return React.createElement<TOwnProps>(component, props);
|
||||
}
|
||||
return React.memo(Connect as any);
|
||||
}
|
||||
73
examples/ionic-react/src/data/dataApi.ts
Normal file
73
examples/ionic-react/src/data/dataApi.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { Plugins } from '@capacitor/core';
|
||||
import { Session } from '../models/Session';
|
||||
import { Speaker } from '../models/Speaker';
|
||||
import { Location } from '../models/Location';
|
||||
|
||||
const { Storage } = Plugins;
|
||||
|
||||
const locationsUrl = '/assets/data/locations.json';
|
||||
const sessionsUrl = '/assets/data/sessions.json';
|
||||
const speakersUrl = '/assets/data/speakers.json';
|
||||
|
||||
const HAS_LOGGED_IN = 'hasLoggedIn';
|
||||
const HAS_SEEN_TUTORIAL = 'hasSeenTutorial';
|
||||
const USERNAME = 'username';
|
||||
|
||||
export const getConfData = async () => {
|
||||
const response = await Promise.all([
|
||||
fetch(sessionsUrl),
|
||||
fetch(locationsUrl),
|
||||
fetch(speakersUrl),
|
||||
]);
|
||||
const sessions = (await response[0].json()) as Session[];
|
||||
const locations = (await response[1].json()) as Location[];
|
||||
const speakers = (await response[2].json()) as Speaker[];
|
||||
const allTracks = sessions
|
||||
.reduce((all, session) => all.concat(session.tracks), [] as string[])
|
||||
.filter((trackName, index, array) => array.indexOf(trackName) === index)
|
||||
.sort();
|
||||
const data = {
|
||||
sessions,
|
||||
locations,
|
||||
speakers,
|
||||
allTracks,
|
||||
filteredTracks: [...allTracks],
|
||||
};
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getUserData = async () => {
|
||||
const response = await Promise.all([
|
||||
Storage.get({ key: HAS_LOGGED_IN }),
|
||||
Storage.get({ key: HAS_SEEN_TUTORIAL }),
|
||||
Storage.get({ key: USERNAME }),
|
||||
]);
|
||||
const isLoggedin = (await response[0].value) === 'true';
|
||||
const hasSeenTutorial = (await response[1].value) === 'true';
|
||||
const username = (await response[2].value) || undefined;
|
||||
const data = {
|
||||
isLoggedin,
|
||||
hasSeenTutorial,
|
||||
username,
|
||||
};
|
||||
return data;
|
||||
};
|
||||
|
||||
export const setIsLoggedInData = async (isLoggedIn: boolean) => {
|
||||
await Storage.set({ key: HAS_LOGGED_IN, value: JSON.stringify(isLoggedIn) });
|
||||
};
|
||||
|
||||
export const setHasSeenTutorialData = async (hasSeenTutorial: boolean) => {
|
||||
await Storage.set({
|
||||
key: HAS_SEEN_TUTORIAL,
|
||||
value: JSON.stringify(hasSeenTutorial),
|
||||
});
|
||||
};
|
||||
|
||||
export const setUsernameData = async (username?: string) => {
|
||||
if (!username) {
|
||||
await Storage.remove({ key: USERNAME });
|
||||
} else {
|
||||
await Storage.set({ key: USERNAME, value: username });
|
||||
}
|
||||
};
|
||||
139
examples/ionic-react/src/data/selectors.ts
Normal file
139
examples/ionic-react/src/data/selectors.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
import { createSelector } from 'reselect';
|
||||
import { parseISO as parseDate } from 'date-fns';
|
||||
import { Session } from '../models/Session';
|
||||
import { SessionGroup } from '../models/SessionGroup';
|
||||
import { AppState } from './state';
|
||||
|
||||
const getSessions = (state: AppState) => state.data.sessions;
|
||||
export const getSpeakers = (state: AppState) => state.data.speakers;
|
||||
const getFilteredTracks = (state: AppState) => state.data.filteredTracks;
|
||||
const getFavoriteIds = (state: AppState) => state.data.favorites;
|
||||
const getSearchText = (state: AppState) => state.data.searchText;
|
||||
|
||||
export const getFilteredSessions = createSelector(
|
||||
getSessions,
|
||||
getFilteredTracks,
|
||||
(sessions, filteredTracks) => {
|
||||
return sessions.filter(session => {
|
||||
let include = false;
|
||||
session.tracks.forEach(track => {
|
||||
if (filteredTracks.indexOf(track) > -1) {
|
||||
include = true;
|
||||
}
|
||||
});
|
||||
return include;
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
export const getSearchedSessions = createSelector(
|
||||
getFilteredSessions,
|
||||
getSearchText,
|
||||
(sessions, searchText) => {
|
||||
if (!searchText) {
|
||||
return sessions;
|
||||
}
|
||||
return sessions.filter(
|
||||
session =>
|
||||
session.name.toLowerCase().indexOf(searchText.toLowerCase()) > -1
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export const getGroupedSessions = createSelector(
|
||||
getSearchedSessions,
|
||||
sessions => {
|
||||
return groupSessions(sessions);
|
||||
}
|
||||
);
|
||||
|
||||
export const getFavorites = createSelector(
|
||||
getSearchedSessions,
|
||||
getFavoriteIds,
|
||||
(sessions, favoriteIds) =>
|
||||
sessions.filter(x => favoriteIds.indexOf(x.id) > -1)
|
||||
);
|
||||
|
||||
export const getGroupedFavorites = createSelector(
|
||||
getFavorites,
|
||||
sessions => {
|
||||
return groupSessions(sessions);
|
||||
}
|
||||
);
|
||||
|
||||
const getIdParam = (_state: AppState, props: any) => {
|
||||
const stringParam = props.match.params['id'];
|
||||
return parseInt(stringParam, 10);
|
||||
};
|
||||
|
||||
export const getSession = createSelector(
|
||||
getSessions,
|
||||
getIdParam,
|
||||
(sessions, id) => sessions.find(x => x.id === id)
|
||||
);
|
||||
|
||||
function groupSessions(sessions: Session[]) {
|
||||
return sessions
|
||||
.sort(
|
||||
(a, b) =>
|
||||
parseDate(a.dateTimeStart).valueOf() -
|
||||
parseDate(b.dateTimeStart).valueOf()
|
||||
)
|
||||
.reduce(
|
||||
(groups, session) => {
|
||||
let starterHour = parseDate(session.dateTimeStart);
|
||||
starterHour.setMinutes(0);
|
||||
starterHour.setSeconds(0);
|
||||
const starterHourStr = starterHour.toJSON();
|
||||
const foundGroup = groups.find(
|
||||
group => group.startTime === starterHourStr
|
||||
);
|
||||
if (foundGroup) {
|
||||
foundGroup.sessions.push(session);
|
||||
} else {
|
||||
groups.push({
|
||||
startTime: starterHourStr,
|
||||
sessions: [session],
|
||||
});
|
||||
}
|
||||
return groups;
|
||||
},
|
||||
[] as SessionGroup[]
|
||||
);
|
||||
}
|
||||
|
||||
export const getSpeaker = createSelector(
|
||||
getSpeakers,
|
||||
getIdParam,
|
||||
(speakers, id) => speakers.find(x => x.id === id)
|
||||
);
|
||||
|
||||
export const getSpeakerSessions = createSelector(
|
||||
getSessions,
|
||||
sessions => {
|
||||
const speakerSessions: { [key: number]: Session[] } = {};
|
||||
sessions.forEach(session => {
|
||||
session.speakerIds.forEach(speakerId => {
|
||||
if (speakerSessions[speakerId]) {
|
||||
speakerSessions[speakerId].push(session);
|
||||
} else {
|
||||
speakerSessions[speakerId] = [session];
|
||||
}
|
||||
});
|
||||
});
|
||||
return speakerSessions;
|
||||
}
|
||||
);
|
||||
|
||||
export const mapCenter = (state: AppState) => {
|
||||
const item = state.data.locations.find(l => l.id === state.data.mapCenterId);
|
||||
if (item == null) {
|
||||
return {
|
||||
id: 1,
|
||||
name: 'Map Center',
|
||||
lat: 43.071584,
|
||||
lng: -89.38012,
|
||||
};
|
||||
}
|
||||
return item;
|
||||
};
|
||||
54
examples/ionic-react/src/data/sessions/sessions.actions.ts
Normal file
54
examples/ionic-react/src/data/sessions/sessions.actions.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { getConfData } from '../dataApi';
|
||||
import { ActionType } from '../../util/types';
|
||||
import { SessionsState } from './sessions.state';
|
||||
|
||||
export const loadConfData = () => async (dispatch: React.Dispatch<any>) => {
|
||||
dispatch(setLoading(true));
|
||||
const data = await getConfData();
|
||||
dispatch(setData(data));
|
||||
dispatch(setLoading(false));
|
||||
};
|
||||
|
||||
export const setLoading = (isLoading: boolean) =>
|
||||
({
|
||||
type: 'set-conf-loading',
|
||||
isLoading,
|
||||
} as const);
|
||||
|
||||
export const setData = (data: Partial<SessionsState>) =>
|
||||
({
|
||||
type: 'set-conf-data',
|
||||
data,
|
||||
} as const);
|
||||
|
||||
export const addFavorite = (sessionId: number) =>
|
||||
({
|
||||
type: 'add-favorite',
|
||||
sessionId,
|
||||
} as const);
|
||||
|
||||
export const removeFavorite = (sessionId: number) =>
|
||||
({
|
||||
type: 'remove-favorite',
|
||||
sessionId,
|
||||
} as const);
|
||||
|
||||
export const updateFilteredTracks = (filteredTracks: string[]) =>
|
||||
({
|
||||
type: 'update-filtered-tracks',
|
||||
filteredTracks,
|
||||
} as const);
|
||||
|
||||
export const setSearchText = (searchText?: string) =>
|
||||
({
|
||||
type: 'set-search-text',
|
||||
searchText,
|
||||
} as const);
|
||||
|
||||
export type SessionsActions =
|
||||
| ActionType<typeof setLoading>
|
||||
| ActionType<typeof setData>
|
||||
| ActionType<typeof addFavorite>
|
||||
| ActionType<typeof removeFavorite>
|
||||
| ActionType<typeof updateFilteredTracks>
|
||||
| ActionType<typeof setSearchText>;
|
||||
31
examples/ionic-react/src/data/sessions/sessions.reducer.ts
Normal file
31
examples/ionic-react/src/data/sessions/sessions.reducer.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { SessionsActions } from './sessions.actions';
|
||||
import { SessionsState } from './sessions.state';
|
||||
|
||||
export const sessionsReducer = (
|
||||
state: SessionsState,
|
||||
action: SessionsActions
|
||||
): SessionsState => {
|
||||
switch (action.type) {
|
||||
case 'set-conf-loading': {
|
||||
return { ...state, loading: action.isLoading };
|
||||
}
|
||||
case 'set-conf-data': {
|
||||
return { ...state, ...action.data };
|
||||
}
|
||||
case 'add-favorite': {
|
||||
return { ...state, favorites: [...state.favorites, action.sessionId] };
|
||||
}
|
||||
case 'remove-favorite': {
|
||||
return {
|
||||
...state,
|
||||
favorites: [...state.favorites.filter(x => x !== action.sessionId)],
|
||||
};
|
||||
}
|
||||
case 'update-filtered-tracks': {
|
||||
return { ...state, filteredTracks: action.filteredTracks };
|
||||
}
|
||||
case 'set-search-text': {
|
||||
return { ...state, searchText: action.searchText };
|
||||
}
|
||||
}
|
||||
};
|
||||
14
examples/ionic-react/src/data/sessions/sessions.state.ts
Normal file
14
examples/ionic-react/src/data/sessions/sessions.state.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Location } from '../../models/Location';
|
||||
import { Speaker } from '../../models/Speaker';
|
||||
import { Session } from '../../models/Session';
|
||||
export interface SessionsState {
|
||||
sessions: Session[];
|
||||
speakers: Speaker[];
|
||||
favorites: number[];
|
||||
locations: Location[];
|
||||
filteredTracks: string[];
|
||||
searchText?: string;
|
||||
mapCenterId?: number;
|
||||
loading?: boolean;
|
||||
allTracks: string[];
|
||||
}
|
||||
29
examples/ionic-react/src/data/state.ts
Normal file
29
examples/ionic-react/src/data/state.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { combineReducers } from './combineReducers';
|
||||
import { sessionsReducer } from './sessions/sessions.reducer';
|
||||
import { userReducer } from './user/user.reducer';
|
||||
|
||||
export const initialState: AppState = {
|
||||
data: {
|
||||
sessions: [],
|
||||
speakers: [],
|
||||
favorites: [],
|
||||
locations: [],
|
||||
allTracks: [],
|
||||
filteredTracks: [],
|
||||
mapCenterId: 0,
|
||||
loading: false,
|
||||
},
|
||||
user: {
|
||||
hasSeenTutorial: false,
|
||||
darkMode: false,
|
||||
isLoggedin: false,
|
||||
loading: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const reducers = combineReducers({
|
||||
data: sessionsReducer,
|
||||
user: userReducer,
|
||||
});
|
||||
|
||||
export type AppState = ReturnType<typeof reducers>;
|
||||
76
examples/ionic-react/src/data/user/user.actions.ts
Normal file
76
examples/ionic-react/src/data/user/user.actions.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import {
|
||||
getUserData,
|
||||
setIsLoggedInData,
|
||||
setUsernameData,
|
||||
setHasSeenTutorialData,
|
||||
} from '../dataApi';
|
||||
import { ActionType } from '../../util/types';
|
||||
import { UserState } from './user.state';
|
||||
|
||||
export const loadUserData = () => async (dispatch: React.Dispatch<any>) => {
|
||||
dispatch(setLoading(true));
|
||||
const data = await getUserData();
|
||||
dispatch(setData(data));
|
||||
dispatch(setLoading(false));
|
||||
};
|
||||
|
||||
export const setLoading = (isLoading: boolean) =>
|
||||
({
|
||||
type: 'set-user-loading',
|
||||
isLoading,
|
||||
} as const);
|
||||
|
||||
export const setData = (data: Partial<UserState>) =>
|
||||
({
|
||||
type: 'set-user-data',
|
||||
data,
|
||||
} as const);
|
||||
|
||||
export const logoutUser = () => async (dispatch: React.Dispatch<any>) => {
|
||||
await setIsLoggedInData(false);
|
||||
dispatch(setUsername());
|
||||
};
|
||||
|
||||
export const setIsLoggedIn = (loggedIn: boolean) => async (
|
||||
dispatch: React.Dispatch<any>
|
||||
) => {
|
||||
await setIsLoggedInData(loggedIn);
|
||||
return {
|
||||
type: 'set-is-loggedin',
|
||||
loggedIn,
|
||||
} as const;
|
||||
};
|
||||
|
||||
export const setUsername = (username?: string) => async (
|
||||
dispatch: React.Dispatch<any>
|
||||
) => {
|
||||
await setUsernameData(username);
|
||||
return {
|
||||
type: 'set-username',
|
||||
username,
|
||||
} as const;
|
||||
};
|
||||
|
||||
export const setHasSeenTutorial = (hasSeenTutorial: boolean) => async (
|
||||
dispatch: React.Dispatch<any>
|
||||
) => {
|
||||
await setHasSeenTutorialData(hasSeenTutorial);
|
||||
return {
|
||||
type: 'set-has-seen-tutorial',
|
||||
hasSeenTutorial,
|
||||
} as const;
|
||||
};
|
||||
|
||||
export const setDarkMode = (darkMode: boolean) =>
|
||||
({
|
||||
type: 'set-dark-mode',
|
||||
darkMode,
|
||||
} as const);
|
||||
|
||||
export type UserActions =
|
||||
| ActionType<typeof setLoading>
|
||||
| ActionType<typeof setData>
|
||||
| ActionType<typeof setIsLoggedIn>
|
||||
| ActionType<typeof setUsername>
|
||||
| ActionType<typeof setHasSeenTutorial>
|
||||
| ActionType<typeof setDarkMode>;
|
||||
19
examples/ionic-react/src/data/user/user.reducer.ts
Normal file
19
examples/ionic-react/src/data/user/user.reducer.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { UserActions } from './user.actions';
|
||||
import { UserState } from './user.state';
|
||||
|
||||
export function userReducer(state: UserState, action: UserActions): UserState {
|
||||
switch (action.type) {
|
||||
case 'set-user-loading':
|
||||
return { ...state, loading: action.isLoading };
|
||||
case 'set-user-data':
|
||||
return { ...state, ...action.data };
|
||||
case 'set-username':
|
||||
return { ...state, username: action.username };
|
||||
case 'set-has-seen-tutorial':
|
||||
return { ...state, hasSeenTutorial: action.hasSeenTutorial };
|
||||
case 'set-dark-mode':
|
||||
return { ...state, darkMode: action.darkMode };
|
||||
case 'set-is-loggedin':
|
||||
return { ...state, isLoggedin: action.loggedIn };
|
||||
}
|
||||
}
|
||||
7
examples/ionic-react/src/data/user/user.state.ts
Normal file
7
examples/ionic-react/src/data/user/user.state.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export interface UserState {
|
||||
isLoggedin: boolean;
|
||||
username?: string;
|
||||
darkMode: boolean;
|
||||
hasSeenTutorial: boolean;
|
||||
loading: boolean;
|
||||
}
|
||||
5
examples/ionic-react/src/declarations.ts
Normal file
5
examples/ionic-react/src/declarations.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export interface AppPage {
|
||||
url: string;
|
||||
icon: object;
|
||||
title: string;
|
||||
}
|
||||
5
examples/ionic-react/src/index.tsx
Normal file
5
examples/ionic-react/src/index.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('root'));
|
||||
6
examples/ionic-react/src/models/Location.ts
Normal file
6
examples/ionic-react/src/models/Location.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export interface Location {
|
||||
id: number;
|
||||
name?: string;
|
||||
lat: number;
|
||||
lng: number;
|
||||
}
|
||||
10
examples/ionic-react/src/models/Session.ts
Normal file
10
examples/ionic-react/src/models/Session.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export interface Session {
|
||||
id: number;
|
||||
dateTimeStart: string;
|
||||
dateTimeEnd: string;
|
||||
name: string;
|
||||
location: string;
|
||||
description: string;
|
||||
speakerIds: number[];
|
||||
tracks: string[];
|
||||
}
|
||||
5
examples/ionic-react/src/models/SessionGroup.ts
Normal file
5
examples/ionic-react/src/models/SessionGroup.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Session } from './Session';
|
||||
export interface SessionGroup {
|
||||
startTime: string;
|
||||
sessions: Session[];
|
||||
}
|
||||
10
examples/ionic-react/src/models/Speaker.ts
Normal file
10
examples/ionic-react/src/models/Speaker.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export interface Speaker {
|
||||
id: number;
|
||||
name: string;
|
||||
profilePic: string;
|
||||
twitter: string;
|
||||
about: string;
|
||||
location: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
}
|
||||
26
examples/ionic-react/src/pages/About.scss
Normal file
26
examples/ionic-react/src/pages/About.scss
Normal file
@@ -0,0 +1,26 @@
|
||||
#about-page {
|
||||
.about-header {
|
||||
background-color: #222;
|
||||
padding: 16px;
|
||||
width: 100%;
|
||||
height: 30%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.about-header img {
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.about-info p {
|
||||
color: var(--ion-color-dark);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.about-info ion-icon {
|
||||
margin-inline-end: 32px;
|
||||
}
|
||||
|
||||
.ios .about-info {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
81
examples/ionic-react/src/pages/About.tsx
Normal file
81
examples/ionic-react/src/pages/About.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import React, { useState } from 'react';
|
||||
import { IonHeader, IonToolbar, IonTitle, IonContent, IonPage, IonButtons, IonMenuButton, IonButton, IonIcon, IonDatetime, IonSelectOption, IonList, IonItem, IonLabel, IonSelect, IonPopover } from '@ionic/react';
|
||||
import './About.scss';
|
||||
import { calendar, pin, more } from 'ionicons/icons';
|
||||
import AboutPopover from '../components/AboutPopover';
|
||||
|
||||
interface AboutProps { }
|
||||
|
||||
const About: React.FC<AboutProps> = () => {
|
||||
|
||||
const [showPopover, setShowPopover] = useState(false);
|
||||
const [popoverEvent, setPopoverEvent] = useState();
|
||||
|
||||
const presentPopover = (e: React.MouseEvent) => {
|
||||
setPopoverEvent(e.nativeEvent);
|
||||
setShowPopover(true);
|
||||
};
|
||||
const conferenceDate = '2047-05-17';
|
||||
|
||||
return (
|
||||
<IonPage id="about-page">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonMenuButton></IonMenuButton>
|
||||
</IonButtons>
|
||||
<IonTitle>About</IonTitle>
|
||||
<IonButtons slot="end">
|
||||
<IonButton icon-only onClick={presentPopover}>
|
||||
<IonIcon slot="icon-only" icon={more}></IonIcon>
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
|
||||
<div className="about-header">
|
||||
<img src="assets/img/ionic-logo-white.svg" alt="ionic logo" />
|
||||
</div>
|
||||
<div className="about-info">
|
||||
<h4 className="ion-padding-start">Ionic Conference</h4>
|
||||
|
||||
<IonList lines="none">
|
||||
<IonItem>
|
||||
<IonIcon icon={calendar} slot="start"></IonIcon>
|
||||
<IonLabel position="stacked">Date</IonLabel>
|
||||
<IonDatetime displayFormat="MMM DD, YYYY" max="2056" value={conferenceDate}></IonDatetime>
|
||||
</IonItem>
|
||||
|
||||
<IonItem>
|
||||
<IonIcon icon={pin} slot="start"></IonIcon>
|
||||
<IonLabel position="stacked">Location</IonLabel>
|
||||
<IonSelect>
|
||||
<IonSelectOption value="madison" selected>Madison, WI</IonSelectOption>
|
||||
<IonSelectOption value="austin">Austin, TX</IonSelectOption>
|
||||
<IonSelectOption value="chicago">Chicago, IL</IonSelectOption>
|
||||
<IonSelectOption value="seattle">Seattle, WA</IonSelectOption>
|
||||
</IonSelect>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
|
||||
<p className="ion-padding-start ion-padding-end">
|
||||
The Ionic Conference is a one-day conference featuring talks from the Ionic team. It is focused on Ionic applications being
|
||||
built with Ionic 2. This includes migrating apps from Ionic 1 to Ionic 2, Angular concepts, Webpack, Sass, and many
|
||||
other technologies used in Ionic 2. Tickets are completely sold out, and we’re expecting more than 1000 developers
|
||||
– making this the largest Ionic conference ever!
|
||||
</p>
|
||||
</div>
|
||||
</IonContent>
|
||||
<IonPopover
|
||||
isOpen={showPopover}
|
||||
event={popoverEvent}
|
||||
onDidDismiss={() => setShowPopover(false)}
|
||||
>
|
||||
<AboutPopover dismiss={() => setShowPopover(false)} />
|
||||
</IonPopover>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(About);
|
||||
6
examples/ionic-react/src/pages/Account.scss
Normal file
6
examples/ionic-react/src/pages/Account.scss
Normal file
@@ -0,0 +1,6 @@
|
||||
#account-page {
|
||||
img {
|
||||
max-width: 140px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
87
examples/ionic-react/src/pages/Account.tsx
Normal file
87
examples/ionic-react/src/pages/Account.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
import React, { useState } from 'react';
|
||||
import { IonHeader, IonToolbar, IonTitle, IonContent, IonPage, IonButtons, IonMenuButton, IonList, IonItem, IonAlert } from '@ionic/react';
|
||||
import './Account.scss';
|
||||
import { setUsername } from '../data/user/user.actions';
|
||||
import { connect } from '../data/connect';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
|
||||
interface OwnProps extends RouteComponentProps { }
|
||||
|
||||
interface StateProps {
|
||||
username?: string;
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
setUsername: typeof setUsername;
|
||||
}
|
||||
|
||||
interface AccountProps extends OwnProps, StateProps, DispatchProps { }
|
||||
|
||||
const Account: React.FC<AccountProps> = ({ setUsername, username }) => {
|
||||
|
||||
const [showAlert, setShowAlert] = useState(false);
|
||||
|
||||
const clicked = (text: string) => {
|
||||
console.log(`Clicked ${text}`);
|
||||
}
|
||||
|
||||
return (
|
||||
<IonPage id="account-page">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonMenuButton></IonMenuButton>
|
||||
</IonButtons>
|
||||
<IonTitle>Account</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
{username &&
|
||||
(<div className="ion-padding-top ion-text-center">
|
||||
<img src="https://www.gravatar.com/avatar?d=mm&s=140" alt="avatar" />
|
||||
<h2>{ username }</h2>
|
||||
<IonList inset>
|
||||
<IonItem onClick={() => clicked('Update Picture')}>Update Picture</IonItem>
|
||||
<IonItem onClick={() => setShowAlert(true)}>Change Username</IonItem>
|
||||
<IonItem onClick={() => clicked('Change Password')}>Change Password</IonItem>
|
||||
<IonItem routerLink="/support" routerDirection="none">Support</IonItem>
|
||||
<IonItem routerLink="/logout" routerDirection="none">Logout</IonItem>
|
||||
</IonList>
|
||||
</div>)
|
||||
}
|
||||
</IonContent>
|
||||
<IonAlert
|
||||
isOpen={showAlert}
|
||||
header="Change Username"
|
||||
buttons={[
|
||||
'Cancel',
|
||||
{
|
||||
text: 'Ok',
|
||||
handler: (data) => {
|
||||
setUsername(data.username);
|
||||
}
|
||||
}
|
||||
]}
|
||||
inputs={[
|
||||
{
|
||||
type: 'text',
|
||||
name: 'username',
|
||||
value: username,
|
||||
placeholder: 'username'
|
||||
}
|
||||
]}
|
||||
onDidDismiss={() => setShowAlert(false)}
|
||||
/>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect<OwnProps, StateProps, DispatchProps>({
|
||||
mapStateToProps: (state) => ({
|
||||
username: state.user.username
|
||||
}),
|
||||
mapDispatchToProps: {
|
||||
setUsername,
|
||||
},
|
||||
component: Account
|
||||
})
|
||||
16
examples/ionic-react/src/pages/Login.scss
Normal file
16
examples/ionic-react/src/pages/Login.scss
Normal file
@@ -0,0 +1,16 @@
|
||||
#login-page, #signup-page, #support-page {
|
||||
.login-logo {
|
||||
padding: 20px 0;
|
||||
min-height: 200px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.login-logo img {
|
||||
max-width: 150px;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
}
|
||||
108
examples/ionic-react/src/pages/Login.tsx
Normal file
108
examples/ionic-react/src/pages/Login.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
import React, { useState } from 'react';
|
||||
import { IonHeader, IonToolbar, IonTitle, IonContent, IonPage, IonButtons, IonMenuButton, IonRow, IonCol, IonButton, IonList, IonItem, IonLabel, IonInput, IonText } from '@ionic/react';
|
||||
import './Login.scss';
|
||||
import { setIsLoggedIn, setUsername } from '../data/user/user.actions';
|
||||
import { connect } from '../data/connect';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
|
||||
interface OwnProps extends RouteComponentProps {}
|
||||
|
||||
interface DispatchProps {
|
||||
setIsLoggedIn: typeof setIsLoggedIn;
|
||||
setUsername: typeof setUsername;
|
||||
}
|
||||
|
||||
interface LoginProps extends OwnProps, DispatchProps { }
|
||||
|
||||
const Login: React.FC<LoginProps> = ({setIsLoggedIn, history, setUsername: setUsernameAction}) => {
|
||||
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [formSubmitted, setFormSubmitted] = useState(false);
|
||||
const [usernameError, setUsernameError] = useState(false);
|
||||
const [passwordError, setPasswordError] = useState(false);
|
||||
|
||||
const login = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setFormSubmitted(true);
|
||||
if(!username) {
|
||||
setUsernameError(true);
|
||||
}
|
||||
if(!password) {
|
||||
setPasswordError(true);
|
||||
}
|
||||
|
||||
if(username && password) {
|
||||
await setIsLoggedIn(true);
|
||||
await setUsernameAction(username);
|
||||
history.push('/tabs/schedule', {direction: 'none'});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<IonPage id="login-page">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonMenuButton></IonMenuButton>
|
||||
</IonButtons>
|
||||
<IonTitle>Login</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
|
||||
<div className="login-logo">
|
||||
<img src="assets/img/appicon.svg" alt="Ionic logo" />
|
||||
</div>
|
||||
|
||||
<form noValidate onSubmit={login}>
|
||||
<IonList>
|
||||
<IonItem>
|
||||
<IonLabel position="stacked" color="primary">Username</IonLabel>
|
||||
<IonInput name="username" type="text" value={username} spellCheck={false} autocapitalize="off" onIonChange={e => setUsername(e.detail.value!)}
|
||||
required>
|
||||
</IonInput>
|
||||
</IonItem>
|
||||
|
||||
{formSubmitted && usernameError && <IonText color="danger">
|
||||
<p className="ion-padding-start">
|
||||
Username is required
|
||||
</p>
|
||||
</IonText>}
|
||||
|
||||
<IonItem>
|
||||
<IonLabel position="stacked" color="primary">Password</IonLabel>
|
||||
<IonInput name="password" type="password" value={password} onIonChange={e => setPassword(e.detail.value!)}>
|
||||
</IonInput>
|
||||
</IonItem>
|
||||
|
||||
{formSubmitted && passwordError && <IonText color="danger">
|
||||
<p className="ion-padding-start">
|
||||
Password is required
|
||||
</p>
|
||||
</IonText>}
|
||||
</IonList>
|
||||
|
||||
<IonRow>
|
||||
<IonCol>
|
||||
<IonButton type="submit" expand="block">Login</IonButton>
|
||||
</IonCol>
|
||||
<IonCol>
|
||||
<IonButton routerLink="/signup" color="light" expand="block">Signup</IonButton>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</form>
|
||||
|
||||
</IonContent>
|
||||
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect<OwnProps, {}, DispatchProps>({
|
||||
mapDispatchToProps: {
|
||||
setIsLoggedIn,
|
||||
setUsername
|
||||
},
|
||||
component: Login
|
||||
})
|
||||
54
examples/ionic-react/src/pages/MainTabs.tsx
Normal file
54
examples/ionic-react/src/pages/MainTabs.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import React from 'react';
|
||||
import { IonTabs, IonRouterOutlet, IonTabBar, IonTabButton, IonIcon, IonLabel } from '@ionic/react';
|
||||
import { Route, Redirect } from 'react-router';
|
||||
import { calendar, contacts, map, informationCircle } from 'ionicons/icons';
|
||||
import SchedulePage from './SchedulePage';
|
||||
import SpeakerList from './SpeakerList';
|
||||
import SpeakerDetail from './SpeakerDetail';
|
||||
import SessionDetail from './SessionDetail';
|
||||
import MapView from './MapView';
|
||||
import About from './About';
|
||||
|
||||
interface MainTabsProps { }
|
||||
|
||||
const MainTabs: React.FC<MainTabsProps> = () => {
|
||||
|
||||
return (
|
||||
<IonTabs>
|
||||
<IonRouterOutlet>
|
||||
<Redirect exact path="/tabs" to="/tabs/schedule" />
|
||||
{/*
|
||||
Using the render method prop cuts down the number of renders your components will have due to route changes.
|
||||
Use the component prop when your component depends on the RouterComponentProps passed in automatically.
|
||||
*/}
|
||||
<Route path="/tabs/schedule" render={() => <SchedulePage />} exact={true} />
|
||||
<Route path="/tabs/speakers" render={() => <SpeakerList />} exact={true} />
|
||||
<Route path="/tabs/speakers/:id" component={SpeakerDetail} exact={true} />
|
||||
<Route path="/tabs/schedule/:id" component={SessionDetail} />
|
||||
<Route path="/tabs/speakers/sessions/:id" component={SessionDetail} />
|
||||
<Route path="/tabs/map" render={() => <MapView />} exact={true} />
|
||||
<Route path="/tabs/about" render={() => <About />} exact={true} />
|
||||
</IonRouterOutlet>
|
||||
<IonTabBar slot="bottom">
|
||||
<IonTabButton tab="schedule" href="/tabs/schedule">
|
||||
<IonIcon icon={calendar} />
|
||||
<IonLabel>Schedule</IonLabel>
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="speakers" href="/tabs/speakers">
|
||||
<IonIcon icon={contacts} />
|
||||
<IonLabel>Speakers</IonLabel>
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="map" href="/tabs/map">
|
||||
<IonIcon icon={map} />
|
||||
<IonLabel>Map</IonLabel>
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="about" href="/tabs/about">
|
||||
<IonIcon icon={informationCircle} />
|
||||
<IonLabel>About</IonLabel>
|
||||
</IonTabButton>
|
||||
</IonTabBar>
|
||||
</IonTabs>
|
||||
);
|
||||
};
|
||||
|
||||
export default MainTabs;
|
||||
18
examples/ionic-react/src/pages/MapView.scss
Normal file
18
examples/ionic-react/src/pages/MapView.scss
Normal file
@@ -0,0 +1,18 @@
|
||||
#map-view {
|
||||
.map-canvas {
|
||||
position: absolute;
|
||||
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
background-color: transparent;
|
||||
|
||||
opacity: 0;
|
||||
transition: opacity 250ms ease-in;
|
||||
}
|
||||
|
||||
.show-map {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
}
|
||||
44
examples/ionic-react/src/pages/MapView.tsx
Normal file
44
examples/ionic-react/src/pages/MapView.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import Map from '../components/Map';
|
||||
import { IonHeader, IonToolbar, IonButtons, IonMenuButton, IonTitle, IonContent, IonPage } from '@ionic/react';
|
||||
import { Location } from '../models/Location';
|
||||
import { connect } from '../data/connect';
|
||||
import * as selectors from '../data/selectors';
|
||||
import './MapView.scss';
|
||||
|
||||
interface OwnProps { }
|
||||
|
||||
interface StateProps {
|
||||
locations: Location[];
|
||||
mapCenter: Location;
|
||||
}
|
||||
|
||||
interface DispatchProps { }
|
||||
|
||||
interface MapViewProps extends OwnProps, StateProps, DispatchProps { };
|
||||
|
||||
const MapView: React.FC<MapViewProps> = ({ locations, mapCenter }) => {
|
||||
return (
|
||||
<IonPage id="map-view">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonMenuButton></IonMenuButton>
|
||||
</IonButtons>
|
||||
<IonTitle>Map</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent class="map-page">
|
||||
<Map locations={locations} mapCenter={mapCenter} />
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
)};
|
||||
|
||||
export default connect<OwnProps, StateProps, DispatchProps>({
|
||||
mapStateToProps: (state) => ({
|
||||
locations: state.data.locations,
|
||||
mapCenter: selectors.mapCenter(state)
|
||||
}),
|
||||
component: MapView
|
||||
});
|
||||
42
examples/ionic-react/src/pages/SchedulePage.scss
Normal file
42
examples/ionic-react/src/pages/SchedulePage.scss
Normal file
@@ -0,0 +1,42 @@
|
||||
#schedule-page {
|
||||
ion-item-sliding.track-ionic ion-label {
|
||||
border-left: 2px solid var(--ion-color-primary);
|
||||
padding-left: 10px;
|
||||
}
|
||||
ion-item-sliding.track-angular ion-label {
|
||||
border-left: 2px solid var(--ion-color-angular);
|
||||
padding-left: 10px;
|
||||
}
|
||||
ion-item-sliding.track-communication ion-label {
|
||||
border-left: 2px solid var(--ion-color-communication);
|
||||
padding-left: 10px;
|
||||
}
|
||||
ion-item-sliding.track-tooling ion-label {
|
||||
border-left: 2px solid var(--ion-color-tooling);
|
||||
padding-left: 10px;
|
||||
}
|
||||
ion-item-sliding.track-services ion-label {
|
||||
border-left: 2px solid var(--ion-color-services);
|
||||
padding-left: 10px;
|
||||
}
|
||||
ion-item-sliding.track-design ion-label {
|
||||
border-left: 2px solid var(--ion-color-design);
|
||||
padding-left: 10px;
|
||||
}
|
||||
ion-item-sliding.track-workshop ion-label {
|
||||
border-left: 2px solid var(--ion-color-workshop);
|
||||
padding-left: 10px;
|
||||
}
|
||||
ion-item-sliding.track-food ion-label {
|
||||
border-left: 2px solid var(--ion-color-food);
|
||||
padding-left: 10px;
|
||||
}
|
||||
ion-item-sliding.track-documentation ion-label {
|
||||
border-left: 2px solid var(--ion-color-documentation);
|
||||
padding-left: 10px;
|
||||
}
|
||||
ion-item-sliding.track-navigation ion-label {
|
||||
border-left: 2px solid var(--ion-color-navigation);
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
120
examples/ionic-react/src/pages/SchedulePage.tsx
Normal file
120
examples/ionic-react/src/pages/SchedulePage.tsx
Normal file
@@ -0,0 +1,120 @@
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { IonToolbar, IonContent, IonPage, IonButtons, IonMenuButton, IonSegment, IonSegmentButton, IonButton, IonIcon, IonSearchbar, IonRefresher, IonRefresherContent, IonToast, IonModal, IonHeader, getConfig } from '@ionic/react';
|
||||
import { connect } from '../data/connect';
|
||||
import { options } from 'ionicons/icons';
|
||||
import SessionList from '../components/SessionList';
|
||||
import SessionListFilter from '../components/SessionListFilter';
|
||||
import './SchedulePage.scss'
|
||||
import * as selectors from '../data/selectors';
|
||||
import { setSearchText, addFavorite, removeFavorite } from '../data/sessions/sessions.actions';
|
||||
import ShareSocialFab from '../components/ShareSocialFab';
|
||||
import { SessionGroup } from '../models/SessionGroup';
|
||||
|
||||
interface OwnProps { }
|
||||
|
||||
interface StateProps {
|
||||
sessionGroups: SessionGroup[];
|
||||
favoriteGroups: SessionGroup[];
|
||||
mode: 'ios' | 'md'
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
setSearchText: typeof setSearchText;
|
||||
}
|
||||
|
||||
type SchedulePageProps = OwnProps & StateProps & DispatchProps;
|
||||
|
||||
const SchedulePage: React.FC<SchedulePageProps> = ({ favoriteGroups, sessionGroups, setSearchText, mode }) => {
|
||||
const [segment, setSegment] = useState<'all' | 'favorites'>('all');
|
||||
const [showFilterModal, setShowFilterModal] = useState(false);
|
||||
const ionRefresherRef = useRef<HTMLIonRefresherElement>(null);
|
||||
const [showCompleteToast, setShowCompleteToast] = useState(false);
|
||||
|
||||
const doRefresh = () => {
|
||||
setTimeout(() => {
|
||||
ionRefresherRef.current!.complete();
|
||||
setShowCompleteToast(true);
|
||||
}, 2500)
|
||||
};
|
||||
|
||||
return (
|
||||
<IonPage id="schedule-page">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonMenuButton />
|
||||
</IonButtons>
|
||||
|
||||
<IonSegment onIonChange={(e) => setSegment(e.detail.value as any)}>
|
||||
<IonSegmentButton value="all" checked={segment === 'all'}>
|
||||
All
|
||||
</IonSegmentButton>
|
||||
<IonSegmentButton value="favorites" checked={segment === 'favorites'}>
|
||||
Favorites
|
||||
</IonSegmentButton>
|
||||
</IonSegment>
|
||||
|
||||
<IonButtons slot="end">
|
||||
<IonButton onClick={() => setShowFilterModal(true)}>
|
||||
{mode === 'ios' ? 'Filter' : <IonIcon icon={options} slot="icon-only" />}
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
|
||||
<IonToolbar>
|
||||
<IonSearchbar
|
||||
placeholder="Search"
|
||||
onIonChange={(e: CustomEvent) => setSearchText(e.detail.value)}
|
||||
/>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent>
|
||||
<IonRefresher slot="fixed" ref={ionRefresherRef} onIonRefresh={doRefresh}>
|
||||
<IonRefresherContent />
|
||||
</IonRefresher>
|
||||
<IonToast
|
||||
isOpen={showCompleteToast}
|
||||
message="Refresh complete"
|
||||
duration={2000}
|
||||
onDidDismiss={() => setShowCompleteToast(false)}
|
||||
/>
|
||||
|
||||
<SessionList
|
||||
sessionGroups={sessionGroups}
|
||||
listType={segment}
|
||||
hide={segment === 'favorites'}
|
||||
/>
|
||||
<SessionList
|
||||
sessionGroups={favoriteGroups}
|
||||
listType={segment}
|
||||
hide={segment === 'all'}
|
||||
/>
|
||||
</IonContent>
|
||||
|
||||
<IonModal
|
||||
isOpen={showFilterModal}
|
||||
onDidDismiss={() => setShowFilterModal(false)}
|
||||
>
|
||||
<SessionListFilter
|
||||
onDismissModal={() => setShowFilterModal(false)}
|
||||
/>
|
||||
</IonModal>
|
||||
|
||||
<ShareSocialFab />
|
||||
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect<OwnProps, StateProps, DispatchProps>({
|
||||
mapStateToProps: (state) => ({
|
||||
sessionGroups: selectors.getGroupedSessions(state),
|
||||
favoriteGroups: selectors.getGroupedFavorites(state),
|
||||
mode: getConfig()!.get('mode')
|
||||
}),
|
||||
mapDispatchToProps: {
|
||||
setSearchText
|
||||
},
|
||||
component: React.memo(SchedulePage)
|
||||
});
|
||||
73
examples/ionic-react/src/pages/SessionDetail.scss
Normal file
73
examples/ionic-react/src/pages/SessionDetail.scss
Normal file
@@ -0,0 +1,73 @@
|
||||
#session-detail-page {
|
||||
.session-track-ionic {
|
||||
color: var(--ion-color-primary);
|
||||
}
|
||||
|
||||
.session-track-angular {
|
||||
color: var(--ion-color-angular);
|
||||
}
|
||||
|
||||
.session-track-communication {
|
||||
color: var(--ion-color-communication);
|
||||
}
|
||||
|
||||
.session-track-tooling {
|
||||
color: var(--ion-color-tooling);
|
||||
}
|
||||
|
||||
.session-track-services {
|
||||
color: var(--ion-color-services);
|
||||
}
|
||||
|
||||
.session-track-design {
|
||||
color: var(--ion-color-design);
|
||||
}
|
||||
|
||||
.session-track-workshop {
|
||||
color: var(--ion-color-workshop);
|
||||
}
|
||||
|
||||
.session-track-food {
|
||||
color: var(--ion-color-food);
|
||||
}
|
||||
|
||||
.session-track-documentation {
|
||||
color: var(--ion-color-documentation);
|
||||
}
|
||||
|
||||
.session-track-navigation {
|
||||
color: var(--ion-color-navigation);
|
||||
}
|
||||
|
||||
.show-favorite {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.icon-heart-empty {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
transform: scale(1);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.icon-heart {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
transform: scale(0);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.show-favorite .icon-heart {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.show-favorite .icon-heart-empty {
|
||||
transform: scale(0);
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
108
examples/ionic-react/src/pages/SessionDetail.tsx
Normal file
108
examples/ionic-react/src/pages/SessionDetail.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
import React from 'react';
|
||||
import { IonHeader, IonToolbar, IonContent, IonPage, IonButtons, IonBackButton, IonButton, IonIcon, IonText, IonList, IonItem, IonLabel } from '@ionic/react';
|
||||
import { connect } from '../data/connect';
|
||||
import { withRouter, RouteComponentProps } from 'react-router';
|
||||
import * as selectors from '../data/selectors';
|
||||
import { starOutline, star, share, cloudDownload } from 'ionicons/icons';
|
||||
import './SessionDetail.scss';
|
||||
import { Time } from '../components/Time';
|
||||
import { addFavorite, removeFavorite } from '../data/sessions/sessions.actions';
|
||||
import { Session } from '../models/Session';
|
||||
|
||||
interface OwnProps extends RouteComponentProps { };
|
||||
|
||||
interface StateProps {
|
||||
session?: Session;
|
||||
favoriteSessions: number[],
|
||||
};
|
||||
|
||||
interface DispatchProps {
|
||||
addFavorite: typeof addFavorite;
|
||||
removeFavorite: typeof removeFavorite;
|
||||
}
|
||||
|
||||
type SessionDetailProps = OwnProps & StateProps & DispatchProps;
|
||||
|
||||
const SessionDetail: React.FC<SessionDetailProps> = ({ session, addFavorite, removeFavorite, favoriteSessions }) => {
|
||||
|
||||
if (!session) {
|
||||
return <div>Session not found</div>
|
||||
}
|
||||
|
||||
const isFavorite = favoriteSessions.indexOf(session.id) > -1;
|
||||
|
||||
const toggleFavorite = () => {
|
||||
isFavorite ? removeFavorite(session.id) : addFavorite(session.id);
|
||||
};
|
||||
const shareSession = () => { };
|
||||
const sessionClick = (text: string) => {
|
||||
console.log(`Clicked ${text}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<IonPage id="session-detail-page">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonBackButton defaultHref="/tabs/schedule"></IonBackButton>
|
||||
</IonButtons>
|
||||
<IonButtons slot="end">
|
||||
<IonButton onClick={() => toggleFavorite()}>
|
||||
{isFavorite ?
|
||||
<IonIcon slot="icon-only" icon={star}></IonIcon> :
|
||||
<IonIcon slot="icon-only" icon={starOutline}></IonIcon>
|
||||
}
|
||||
</IonButton>
|
||||
<IonButton onClick={() => shareSession}>
|
||||
<IonIcon slot="icon-only" icon={share}></IonIcon>
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<div className="ion-padding">
|
||||
<h1>{session.name}</h1>
|
||||
{session.tracks.map(track => (
|
||||
<span key={track} className={`session-track-${track.toLowerCase()}`}>{track}</span>
|
||||
))}
|
||||
<p>{session.description}</p>
|
||||
<IonText color="medium">
|
||||
<Time date={session.dateTimeStart} /> – <Time date={session.dateTimeEnd} />
|
||||
<br />
|
||||
{session.location}
|
||||
</IonText>
|
||||
</div>
|
||||
<IonList>
|
||||
<IonItem onClick={() => sessionClick('watch')} button>
|
||||
<IonLabel color="primary">Watch</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem onClick={() => sessionClick('add to calendar')} button>
|
||||
<IonLabel color="primary">Add to Calendar</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem onClick={() => sessionClick('mark as unwatched')} button>
|
||||
<IonLabel color="primary">Mark as Unwatched</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem onClick={() => sessionClick('download video')} button>
|
||||
<IonLabel color="primary">Download Video</IonLabel>
|
||||
<IonIcon slot="end" color="primary" size="small" icon={cloudDownload}></IonIcon>
|
||||
</IonItem>
|
||||
<IonItem onClick={() => sessionClick('leave feedback')} button>
|
||||
<IonLabel color="primary">Leave Feedback</IonLabel>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect<OwnProps, StateProps, DispatchProps>({
|
||||
mapStateToProps: (state, OwnProps) => ({
|
||||
session: selectors.getSession(state, OwnProps),
|
||||
favoriteSessions: state.data.favorites
|
||||
}),
|
||||
mapDispatchToProps: {
|
||||
addFavorite,
|
||||
removeFavorite
|
||||
},
|
||||
component: withRouter(SessionDetail)
|
||||
})
|
||||
111
examples/ionic-react/src/pages/Signup.tsx
Normal file
111
examples/ionic-react/src/pages/Signup.tsx
Normal file
@@ -0,0 +1,111 @@
|
||||
import React, { useState } from 'react';
|
||||
import { IonHeader, IonToolbar, IonTitle, IonContent, IonPage, IonButtons, IonMenuButton, IonRow, IonCol, IonButton, IonList, IonItem, IonLabel, IonInput, IonText } from '@ionic/react';
|
||||
import './Login.scss';
|
||||
import { setIsLoggedIn, setUsername } from '../data/user/user.actions';
|
||||
import { connect } from '../data/connect';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
|
||||
interface OwnProps extends RouteComponentProps {}
|
||||
|
||||
interface DispatchProps {
|
||||
setIsLoggedIn: typeof setIsLoggedIn;
|
||||
setUsername: typeof setUsername;
|
||||
}
|
||||
|
||||
interface LoginProps extends OwnProps, DispatchProps { }
|
||||
|
||||
const Login: React.FC<LoginProps> = ({setIsLoggedIn, history, setUsername: setUsernameAction}) => {
|
||||
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [formSubmitted, setFormSubmitted] = useState(false);
|
||||
const [usernameError, setUsernameError] = useState(false);
|
||||
const [passwordError, setPasswordError] = useState(false);
|
||||
|
||||
const login = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setFormSubmitted(true);
|
||||
if(!username) {
|
||||
setUsernameError(true);
|
||||
}
|
||||
if(!password) {
|
||||
setPasswordError(true);
|
||||
}
|
||||
|
||||
if(username && password) {
|
||||
await setIsLoggedIn(true);
|
||||
await setUsernameAction(username);
|
||||
history.push('/tabs/schedule', {direction: 'none'});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<IonPage id="signup-page">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonMenuButton></IonMenuButton>
|
||||
</IonButtons>
|
||||
<IonTitle>Signup</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
|
||||
<div className="login-logo">
|
||||
<img src="assets/img/appicon.svg" alt="Ionic logo" />
|
||||
</div>
|
||||
|
||||
<form noValidate onSubmit={login}>
|
||||
<IonList>
|
||||
<IonItem>
|
||||
<IonLabel position="stacked" color="primary">Username</IonLabel>
|
||||
<IonInput name="username" type="text" value={username} spellCheck={false} autocapitalize="off" onIonChange={e => {
|
||||
setUsername(e.detail.value!);
|
||||
setUsernameError(false);
|
||||
}}
|
||||
required>
|
||||
</IonInput>
|
||||
</IonItem>
|
||||
|
||||
{formSubmitted && usernameError && <IonText color="danger">
|
||||
<p className="ion-padding-start">
|
||||
Username is required
|
||||
</p>
|
||||
</IonText>}
|
||||
|
||||
<IonItem>
|
||||
<IonLabel position="stacked" color="primary">Password</IonLabel>
|
||||
<IonInput name="password" type="password" value={password} onIonChange={e => {
|
||||
setPassword(e.detail.value!);
|
||||
setPasswordError(false);
|
||||
}}>
|
||||
</IonInput>
|
||||
</IonItem>
|
||||
|
||||
{formSubmitted && passwordError && <IonText color="danger">
|
||||
<p className="ion-padding-start">
|
||||
Password is required
|
||||
</p>
|
||||
</IonText>}
|
||||
</IonList>
|
||||
|
||||
<IonRow>
|
||||
<IonCol>
|
||||
<IonButton type="submit" expand="block">Create</IonButton>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</form>
|
||||
|
||||
</IonContent>
|
||||
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect<OwnProps, {}, DispatchProps>({
|
||||
mapDispatchToProps: {
|
||||
setIsLoggedIn,
|
||||
setUsername
|
||||
},
|
||||
component: Login
|
||||
})
|
||||
11
examples/ionic-react/src/pages/SpeakerDetail.scss
Normal file
11
examples/ionic-react/src/pages/SpeakerDetail.scss
Normal file
@@ -0,0 +1,11 @@
|
||||
#speaker-detail {
|
||||
img {
|
||||
max-width: 140px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
p {
|
||||
color: #60646B;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
64
examples/ionic-react/src/pages/SpeakerDetail.tsx
Normal file
64
examples/ionic-react/src/pages/SpeakerDetail.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import React from 'react';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
import { IonIcon, IonHeader, IonToolbar, IonButtons, IonTitle, IonContent, IonButton, IonBackButton, IonPage } from '@ionic/react'
|
||||
import './SpeakerDetail.scss';
|
||||
import { logoTwitter, logoGithub, logoInstagram } from 'ionicons/icons';
|
||||
import { connect } from '../data/connect';
|
||||
import * as selectors from '../data/selectors';
|
||||
import { Speaker } from '../models/Speaker';
|
||||
|
||||
interface OwnProps extends RouteComponentProps {
|
||||
speaker?: Speaker;
|
||||
};
|
||||
|
||||
interface StateProps {};
|
||||
|
||||
interface DispatchProps {};
|
||||
|
||||
interface SpeakerDetailProps extends OwnProps, StateProps, DispatchProps {};
|
||||
|
||||
const SpeakerDetail: React.FC<SpeakerDetailProps> = ({ speaker }) => {
|
||||
|
||||
if (!speaker) {
|
||||
return <div>Speaker not found</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<IonPage id="speaker-detail">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonBackButton defaultHref="/tabs/speakers" />
|
||||
</IonButtons>
|
||||
<IonTitle>{speaker.name}</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent className="ion-padding speaker-detail speaker-page-list">
|
||||
<div className="ion-text-center">
|
||||
<img src={speaker.profilePic} alt={speaker.name} />
|
||||
<br />
|
||||
<IonButton fill="clear" size="small" color="twitter">
|
||||
<IonIcon icon={logoTwitter} slot="icon-only"></IonIcon>
|
||||
</IonButton>
|
||||
<IonButton fill="clear" size="small" color="github">
|
||||
<IonIcon icon={logoGithub} slot="icon-only"></IonIcon>
|
||||
</IonButton>
|
||||
<IonButton fill="clear" size="small" color="instagram">
|
||||
<IonIcon icon={logoInstagram} slot="icon-only"></IonIcon>
|
||||
</IonButton>
|
||||
</div>
|
||||
|
||||
<p>{speaker.about}</p>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default connect({
|
||||
mapStateToProps: (state, ownProps) => ({
|
||||
speaker: selectors.getSpeaker(state, ownProps)
|
||||
}),
|
||||
component: SpeakerDetail
|
||||
});
|
||||
24
examples/ionic-react/src/pages/SpeakerList.scss
Normal file
24
examples/ionic-react/src/pages/SpeakerList.scss
Normal file
@@ -0,0 +1,24 @@
|
||||
#speaker-list {
|
||||
.scroll {
|
||||
background: #ededed;
|
||||
}
|
||||
|
||||
.speaker-card {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.speaker-card ion-card-header {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.speaker-card ion-card-header .item {
|
||||
padding: 4px 16px;
|
||||
}
|
||||
|
||||
.speaker-card ion-card-content {
|
||||
flex: 1 1 auto;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
61
examples/ionic-react/src/pages/SpeakerList.tsx
Normal file
61
examples/ionic-react/src/pages/SpeakerList.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import React from 'react';
|
||||
import { IonHeader, IonToolbar, IonTitle, IonContent, IonPage, IonButtons, IonMenuButton, IonList, IonGrid, IonRow, IonCol } from '@ionic/react';
|
||||
import SpeakerItem from '../components/SpeakerItem';
|
||||
import { Speaker } from '../models/Speaker';
|
||||
import { Session } from '../models/Session';
|
||||
import { connect } from '../data/connect';
|
||||
import * as selectors from '../data/selectors';
|
||||
import './SpeakerList.scss';
|
||||
|
||||
interface OwnProps { };
|
||||
|
||||
interface StateProps {
|
||||
speakers: Speaker[];
|
||||
speakerSessions: { [key: number]: Session[] };
|
||||
};
|
||||
|
||||
interface DispatchProps { };
|
||||
|
||||
interface SpeakerListProps extends OwnProps, StateProps, DispatchProps { };
|
||||
|
||||
const SpeakerList: React.FC<SpeakerListProps> = ({ speakers, speakerSessions }) => {
|
||||
|
||||
return (
|
||||
<IonPage id="speaker-list">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonMenuButton />
|
||||
</IonButtons>
|
||||
<IonTitle>Speakers</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent className={`outer-content`}>
|
||||
<IonList>
|
||||
<IonGrid fixed>
|
||||
<IonRow align-items-stretch>
|
||||
{speakers.map(speaker => (
|
||||
<IonCol size="12" size-md="6" key={speaker.id}>
|
||||
<SpeakerItem
|
||||
key={speaker.id}
|
||||
speaker={speaker}
|
||||
sessions={speakerSessions[speaker.id]}
|
||||
/>
|
||||
</IonCol>
|
||||
))}
|
||||
</IonRow>
|
||||
</IonGrid>
|
||||
</IonList>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect<OwnProps, StateProps, DispatchProps>({
|
||||
mapStateToProps: (state) => ({
|
||||
speakers: selectors.getSpeakers(state),
|
||||
speakerSessions: selectors.getSpeakerSessions(state)
|
||||
}),
|
||||
component: React.memo(SpeakerList)
|
||||
});
|
||||
83
examples/ionic-react/src/pages/Support.tsx
Normal file
83
examples/ionic-react/src/pages/Support.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import React, { useState } from 'react';
|
||||
import { IonHeader, IonToolbar, IonTitle, IonContent, IonPage, IonButtons, IonMenuButton, IonRow, IonCol, IonButton, IonList, IonItem, IonLabel, IonText, IonTextarea, IonToast } from '@ionic/react';
|
||||
import './Login.scss';
|
||||
import { connect } from '../data/connect';
|
||||
|
||||
interface OwnProps { }
|
||||
|
||||
interface DispatchProps { }
|
||||
|
||||
interface SupportProps extends OwnProps, DispatchProps { }
|
||||
|
||||
const Support: React.FC<SupportProps> = () => {
|
||||
|
||||
const [message, setMessage] = useState('');
|
||||
const [formSubmitted, setFormSubmitted] = useState(false);
|
||||
const [messageError, setMessageError] = useState(false);
|
||||
const [showToast, setShowToast] = useState(false);
|
||||
|
||||
const send = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setFormSubmitted(true);
|
||||
if (!message) {
|
||||
setMessageError(true);
|
||||
}
|
||||
if (message) {
|
||||
setMessage('');
|
||||
setShowToast(true);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<IonPage id="support-page">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonMenuButton></IonMenuButton>
|
||||
</IonButtons>
|
||||
<IonTitle>Support</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
|
||||
<div className="login-logo">
|
||||
<img src="assets/img/appicon.svg" alt="Ionic logo" />
|
||||
</div>
|
||||
|
||||
<form noValidate onSubmit={send}>
|
||||
<IonList>
|
||||
<IonItem>
|
||||
<IonLabel position="stacked" color="primary">Enter your support message below</IonLabel>
|
||||
<IonTextarea name="message" value={message} spellCheck={false} autocapitalize="off" rows={6} onIonChange={e => setMessage(e.detail.value!)}
|
||||
required>
|
||||
</IonTextarea>
|
||||
</IonItem>
|
||||
|
||||
{formSubmitted && messageError && <IonText color="danger">
|
||||
<p className="ion-padding-start">
|
||||
Support message is required
|
||||
</p>
|
||||
</IonText>}
|
||||
</IonList>
|
||||
|
||||
<IonRow>
|
||||
<IonCol>
|
||||
<IonButton type="submit" expand="block">Submit</IonButton>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</form>
|
||||
|
||||
</IonContent>
|
||||
|
||||
<IonToast
|
||||
isOpen={showToast}
|
||||
duration={3000}
|
||||
message="Your support request has been sent"
|
||||
onDidDismiss={() => setShowToast(false)} />
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect<OwnProps, {}, DispatchProps>({
|
||||
component: Support
|
||||
})
|
||||
38
examples/ionic-react/src/pages/Tutorial.scss
Normal file
38
examples/ionic-react/src/pages/Tutorial.scss
Normal file
@@ -0,0 +1,38 @@
|
||||
#tutorial-page {
|
||||
ion-toolbar {
|
||||
// TODO test transparent and fullscreen
|
||||
--background: transparent;
|
||||
--border-color: transparent;
|
||||
}
|
||||
|
||||
.swiper-slide {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.slide-title {
|
||||
margin-top: 2.8rem;
|
||||
}
|
||||
|
||||
.slide-image {
|
||||
max-height: 50%;
|
||||
max-width: 60%;
|
||||
margin: 36px 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
b {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
p {
|
||||
padding: 0 40px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
color: var(--ion-color-step-600, #60646b);
|
||||
|
||||
b {
|
||||
color: var(--ion-text-color, #000000);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
87
examples/ionic-react/src/pages/Tutorial.tsx
Normal file
87
examples/ionic-react/src/pages/Tutorial.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { IonContent, IonPage, IonHeader, IonToolbar, IonButtons, IonButton, IonSlides, IonSlide, IonIcon } from '@ionic/react';
|
||||
import { arrowForward } from 'ionicons/icons';
|
||||
import { setHasSeenTutorial } from '../data/user/user.actions';
|
||||
import './Tutorial.scss';
|
||||
import { connect } from '../data/connect';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
|
||||
interface OwnProps extends RouteComponentProps {};
|
||||
|
||||
interface DispatchProps {
|
||||
setHasSeenTutorial: typeof setHasSeenTutorial
|
||||
}
|
||||
|
||||
interface TutorialProps extends OwnProps, DispatchProps { };
|
||||
|
||||
const Tutorial: React.FC<TutorialProps> = ({ history, setHasSeenTutorial }) => {
|
||||
const [showSkip, setShowSkip] = useState(true);
|
||||
const slideRef = useRef<HTMLIonSlidesElement>(null);
|
||||
|
||||
const startApp = async () => {
|
||||
await setHasSeenTutorial(true);
|
||||
history.push('/tabs/schedule', { direction: 'none' });
|
||||
};
|
||||
|
||||
const handleSlideChangeStart = () => {
|
||||
slideRef.current!.isEnd().then(isEnd => setShowSkip(!isEnd));
|
||||
};
|
||||
|
||||
return (
|
||||
<IonPage id="tutorial-page">
|
||||
<IonHeader no-border>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="end">
|
||||
{showSkip && <IonButton color='primary' onClick={startApp}>Skip</IonButton>}
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent fullscreen>
|
||||
|
||||
<IonSlides ref={slideRef} onIonSlideWillChange={handleSlideChangeStart} pager={false}>
|
||||
<IonSlide>
|
||||
<img src="assets/img/ica-slidebox-img-1.png" alt="" className="slide-image" />
|
||||
<h2 className="slide-title">
|
||||
Welcome to <b>ICA</b>
|
||||
</h2>
|
||||
<p>
|
||||
The <b>ionic conference app</b> is a practical preview of the ionic framework in action, and a demonstration of proper code use.
|
||||
</p>
|
||||
</IonSlide>
|
||||
|
||||
<IonSlide>
|
||||
<img src="assets/img/ica-slidebox-img-2.png" alt="" className="slide-image" />
|
||||
<h2 className="slide-title">What is Ionic?</h2>
|
||||
<p>
|
||||
<b>Ionic Framework</b> is an open source SDK that enables developers to build high quality mobile apps with web technologies like HTML, CSS, and JavaScript.
|
||||
</p>
|
||||
</IonSlide>
|
||||
|
||||
<IonSlide>
|
||||
<img src="assets/img/ica-slidebox-img-3.png" alt="" className="slide-image" />
|
||||
<h2 className="slide-title">What is Ionic Appflow?</h2>
|
||||
<p>
|
||||
<b>Ionic Appflow</b> is a powerful set of services and features built on top of Ionic Framework that brings a totally new level of app development agility to mobile dev teams.
|
||||
</p>
|
||||
</IonSlide>
|
||||
|
||||
<IonSlide>
|
||||
<img src="assets/img/ica-slidebox-img-4.png" alt="" className="slide-image" />
|
||||
<h2 className="slide-title">Ready to Play?</h2>
|
||||
<IonButton fill="clear" onClick={startApp}>
|
||||
Continue
|
||||
<IonIcon slot="end" icon={arrowForward} />
|
||||
</IonButton>
|
||||
</IonSlide>
|
||||
</IonSlides>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect<OwnProps, {}, DispatchProps>({
|
||||
mapDispatchToProps: ({
|
||||
setHasSeenTutorial
|
||||
}),
|
||||
component: Tutorial
|
||||
});
|
||||
1
examples/ionic-react/src/react-app-env.d.ts
vendored
Normal file
1
examples/ionic-react/src/react-app-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="react-scripts" />
|
||||
179
examples/ionic-react/src/theme.css
Normal file
179
examples/ionic-react/src/theme.css
Normal file
@@ -0,0 +1,179 @@
|
||||
|
||||
/* Ionic Variables and Theming. For more information, please see
|
||||
// https://beta.ionicframework.com/docs/theming/
|
||||
// The app direction is used to include
|
||||
// rtl styles in your app. For more information, please see
|
||||
// https://beta.ionicframework.com/docs/layout/rtl
|
||||
// $app-direction: ltr;
|
||||
// Ionic Colors
|
||||
// --------------------------------------------------
|
||||
// Named colors makes it easy to reuse colors on various components.
|
||||
// It's highly recommended to change the default colors
|
||||
// to match your app's branding. Ionic provides eight layered colors
|
||||
// that can be changed to theme an app. Additional colors can be
|
||||
// added as well (see below). For more information, please see
|
||||
// https://beta.ionicframework.com/docs/theming/advanced
|
||||
// To easily create custom color palettes for your app’s UI,
|
||||
// check out our color generator:
|
||||
// https://beta.ionicframework.com/docs/theming/color-generator
|
||||
*/
|
||||
|
||||
:root {
|
||||
--ion-color-angular: #ac282b;
|
||||
--ion-color-communication: #8e8d93;
|
||||
--ion-color-tooling: #fe4c52;
|
||||
--ion-color-services: #fd8b2d;
|
||||
--ion-color-design: #fed035;
|
||||
--ion-color-workshop: #69bb7b;
|
||||
--ion-color-food: #3bc7c4;
|
||||
--ion-color-documentation: #b16be3;
|
||||
--ion-color-navigation: #6600cc;
|
||||
|
||||
--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;
|
||||
|
||||
--ion-color-secondary: #0cd1e8;
|
||||
--ion-color-secondary-rgb: 12, 209, 232;
|
||||
--ion-color-secondary-contrast: #ffffff;
|
||||
--ion-color-secondary-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-secondary-shade: #0bb8cc;
|
||||
--ion-color-secondary-tint: #24d6ea;
|
||||
|
||||
--ion-color-tertiary: #7044ff;
|
||||
--ion-color-tertiary-rgb: 112, 68, 255;
|
||||
--ion-color-tertiary-contrast: #ffffff;
|
||||
--ion-color-tertiary-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-tertiary-shade: #633ce0;
|
||||
--ion-color-tertiary-tint: #7e57ff;
|
||||
|
||||
--ion-color-success: #10dc60;
|
||||
--ion-color-success-rgb: 16, 220, 96;
|
||||
--ion-color-success-contrast: #ffffff;
|
||||
--ion-color-success-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-success-shade: #0ec254;
|
||||
--ion-color-success-tint: #28e070;
|
||||
|
||||
--ion-color-warning: #ffce00;
|
||||
--ion-color-warning-rgb: 255, 206, 0;
|
||||
--ion-color-warning-contrast: #ffffff;
|
||||
--ion-color-warning-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-warning-shade: #e0b500;
|
||||
--ion-color-warning-tint: #ffd31a;
|
||||
|
||||
--ion-color-danger: #f04141;
|
||||
--ion-color-danger-rgb: 245, 61, 61;
|
||||
--ion-color-danger-contrast: #ffffff;
|
||||
--ion-color-danger-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-danger-shade: #d33939;
|
||||
--ion-color-danger-tint: #f25454;
|
||||
|
||||
--ion-color-dark: #222428;
|
||||
--ion-color-dark-rgb: 34, 34, 34;
|
||||
--ion-color-dark-contrast: #ffffff;
|
||||
--ion-color-dark-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-dark-shade: #1e2023;
|
||||
--ion-color-dark-tint: #383a3e;
|
||||
|
||||
--ion-color-medium: #989aa2;
|
||||
--ion-color-medium-rgb: 152, 154, 162;
|
||||
--ion-color-medium-contrast: #ffffff;
|
||||
--ion-color-medium-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-medium-shade: #86888f;
|
||||
--ion-color-medium-tint: #a2a4ab;
|
||||
|
||||
--ion-color-light: #f4f5f8;
|
||||
--ion-color-light-rgb: 244, 244, 244;
|
||||
--ion-color-light-contrast: #000000;
|
||||
--ion-color-light-contrast-rgb: 0, 0, 0;
|
||||
--ion-color-light-shade: #d7d8da;
|
||||
--ion-color-light-tint: #f5f6f9;
|
||||
}
|
||||
|
||||
/* Additional Ionic Colors
|
||||
// --------------------------------------------------
|
||||
// In order to add colors to be used with Ionic components,
|
||||
// the color should be added as a class with the convention `.ion-color-{COLOR}`
|
||||
// where `{COLOR}` is the color to be used on the Ionic component
|
||||
// and each variant is defined for the color. For more information, please see
|
||||
// https://beta.ionicframework.com/docs/theming/advanced
|
||||
*/
|
||||
|
||||
.ion-color-favorite {
|
||||
--ion-color-base: #69bb7b;
|
||||
--ion-color-base-rgb: 105, 187, 123;
|
||||
--ion-color-contrast: #ffffff;
|
||||
--ion-color-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-shade: #5ca56c;
|
||||
--ion-color-tint: #78c288;
|
||||
}
|
||||
|
||||
.ion-color-twitter {
|
||||
--ion-color-base: #1da1f4;
|
||||
--ion-color-base-rgb: 29, 161, 244;
|
||||
--ion-color-contrast: #ffffff;
|
||||
--ion-color-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-shade: #1a8ed7;
|
||||
--ion-color-tint: #34aaf5;
|
||||
}
|
||||
|
||||
.ion-color-google {
|
||||
--ion-color-base: #dc4a38;
|
||||
--ion-color-base-rgb: 220, 74, 56;
|
||||
--ion-color-contrast: #ffffff;
|
||||
--ion-color-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-shade: #c24131;
|
||||
--ion-color-tint: #e05c4c;
|
||||
}
|
||||
|
||||
.ion-color-vimeo {
|
||||
--ion-color-base: #23b6ea;
|
||||
--ion-color-base-rgb: 35, 182, 234;
|
||||
--ion-color-contrast: #ffffff;
|
||||
--ion-color-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-shade: #1fa0ce;
|
||||
--ion-color-tint: #39bdec;
|
||||
}
|
||||
|
||||
.ion-color-facebook {
|
||||
--ion-color-base: #3b5998;
|
||||
--ion-color-base-rgb: 59, 89, 152;
|
||||
--ion-color-contrast: #ffffff;
|
||||
--ion-color-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-shade: #344e86;
|
||||
--ion-color-tint: #4f6aa2;
|
||||
}
|
||||
|
||||
/* Shared Variables
|
||||
// --------------------------------------------------
|
||||
// To customize the look and feel of this app, you can override
|
||||
// the CSS variables found in Ionic's source files.
|
||||
// To view all the possible Ionic variables, see:
|
||||
// https://beta.ionicframework.com/docs/theming/css-variables#ionic-variables
|
||||
*/
|
||||
|
||||
:root {
|
||||
--ion-headings-font-weight: 300;
|
||||
|
||||
--ion-color-angular: #ac282b;
|
||||
--ion-color-communication: #8e8d93;
|
||||
--ion-color-tooling: #fe4c52;
|
||||
--ion-color-services: #fd8b2d;
|
||||
--ion-color-design: #fed035;
|
||||
--ion-color-workshop: #69bb7b;
|
||||
--ion-color-food: #3bc7c4;
|
||||
--ion-color-documentation: #b16be3;
|
||||
--ion-color-navigation: #6600cc;
|
||||
}
|
||||
|
||||
.md {
|
||||
--ion-toolbar-background: var(--ion-color-primary);
|
||||
--ion-toolbar-color: #fff;
|
||||
--ion-toolbar-color-activated: #fff;
|
||||
}
|
||||
|
||||
|
||||
|
||||
320
examples/ionic-react/src/theme/variables.css
Normal file
320
examples/ionic-react/src/theme/variables.css
Normal file
@@ -0,0 +1,320 @@
|
||||
|
||||
/* Ionic Variables and Theming. For more information, please see
|
||||
// https://beta.ionicframework.com/docs/theming/
|
||||
// The app direction is used to include
|
||||
// rtl styles in your app. For more information, please see
|
||||
// https://beta.ionicframework.com/docs/layout/rtl
|
||||
// $app-direction: ltr;
|
||||
// Ionic Colors
|
||||
// --------------------------------------------------
|
||||
// Named colors makes it easy to reuse colors on various components.
|
||||
// It's highly recommended to change the default colors
|
||||
// to match your app's branding. Ionic provides eight layered colors
|
||||
// that can be changed to theme an app. Additional colors can be
|
||||
// added as well (see below). For more information, please see
|
||||
// https://beta.ionicframework.com/docs/theming/advanced
|
||||
// To easily create custom color palettes for your app’s UI,
|
||||
// check out our color generator:
|
||||
// https://beta.ionicframework.com/docs/theming/color-generator
|
||||
*/
|
||||
|
||||
:root {
|
||||
--ion-color-angular: #ac282b;
|
||||
--ion-color-communication: #8e8d93;
|
||||
--ion-color-tooling: #fe4c52;
|
||||
--ion-color-services: #fd8b2d;
|
||||
--ion-color-design: #fed035;
|
||||
--ion-color-workshop: #69bb7b;
|
||||
--ion-color-food: #3bc7c4;
|
||||
--ion-color-documentation: #b16be3;
|
||||
--ion-color-navigation: #6600cc;
|
||||
|
||||
--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;
|
||||
|
||||
--ion-color-secondary: #0cd1e8;
|
||||
--ion-color-secondary-rgb: 12, 209, 232;
|
||||
--ion-color-secondary-contrast: #ffffff;
|
||||
--ion-color-secondary-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-secondary-shade: #0bb8cc;
|
||||
--ion-color-secondary-tint: #24d6ea;
|
||||
|
||||
--ion-color-tertiary: #7044ff;
|
||||
--ion-color-tertiary-rgb: 112, 68, 255;
|
||||
--ion-color-tertiary-contrast: #ffffff;
|
||||
--ion-color-tertiary-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-tertiary-shade: #633ce0;
|
||||
--ion-color-tertiary-tint: #7e57ff;
|
||||
|
||||
--ion-color-success: #10dc60;
|
||||
--ion-color-success-rgb: 16, 220, 96;
|
||||
--ion-color-success-contrast: #ffffff;
|
||||
--ion-color-success-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-success-shade: #0ec254;
|
||||
--ion-color-success-tint: #28e070;
|
||||
|
||||
--ion-color-warning: #ffce00;
|
||||
--ion-color-warning-rgb: 255, 206, 0;
|
||||
--ion-color-warning-contrast: #ffffff;
|
||||
--ion-color-warning-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-warning-shade: #e0b500;
|
||||
--ion-color-warning-tint: #ffd31a;
|
||||
|
||||
--ion-color-danger: #f04141;
|
||||
--ion-color-danger-rgb: 245, 61, 61;
|
||||
--ion-color-danger-contrast: #ffffff;
|
||||
--ion-color-danger-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-danger-shade: #d33939;
|
||||
--ion-color-danger-tint: #f25454;
|
||||
|
||||
--ion-color-dark: #222428;
|
||||
--ion-color-dark-rgb: 34, 34, 34;
|
||||
--ion-color-dark-contrast: #ffffff;
|
||||
--ion-color-dark-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-dark-shade: #1e2023;
|
||||
--ion-color-dark-tint: #383a3e;
|
||||
|
||||
--ion-color-medium: #989aa2;
|
||||
--ion-color-medium-rgb: 152, 154, 162;
|
||||
--ion-color-medium-contrast: #ffffff;
|
||||
--ion-color-medium-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-medium-shade: #86888f;
|
||||
--ion-color-medium-tint: #a2a4ab;
|
||||
|
||||
--ion-color-light: #f4f5f8;
|
||||
--ion-color-light-rgb: 244, 244, 244;
|
||||
--ion-color-light-contrast: #000000;
|
||||
--ion-color-light-contrast-rgb: 0, 0, 0;
|
||||
--ion-color-light-shade: #d7d8da;
|
||||
--ion-color-light-tint: #f5f6f9;
|
||||
}
|
||||
|
||||
/* Additional Ionic Colors
|
||||
// --------------------------------------------------
|
||||
// In order to add colors to be used with Ionic components,
|
||||
// the color should be added as a class with the convention `.ion-color-{COLOR}`
|
||||
// where `{COLOR}` is the color to be used on the Ionic component
|
||||
// and each variant is defined for the color. For more information, please see
|
||||
// https://beta.ionicframework.com/docs/theming/advanced
|
||||
*/
|
||||
|
||||
.ion-color-favorite {
|
||||
--ion-color-base: #69bb7b;
|
||||
--ion-color-base-rgb: 105, 187, 123;
|
||||
--ion-color-contrast: #ffffff;
|
||||
--ion-color-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-shade: #5ca56c;
|
||||
--ion-color-tint: #78c288;
|
||||
}
|
||||
|
||||
.ion-color-twitter {
|
||||
--ion-color-base: #1da1f4;
|
||||
--ion-color-base-rgb: 29, 161, 244;
|
||||
--ion-color-contrast: #ffffff;
|
||||
--ion-color-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-shade: #1a8ed7;
|
||||
--ion-color-tint: #34aaf5;
|
||||
}
|
||||
|
||||
.ion-color-google {
|
||||
--ion-color-base: #dc4a38;
|
||||
--ion-color-base-rgb: 220, 74, 56;
|
||||
--ion-color-contrast: #ffffff;
|
||||
--ion-color-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-shade: #c24131;
|
||||
--ion-color-tint: #e05c4c;
|
||||
}
|
||||
|
||||
.ion-color-vimeo {
|
||||
--ion-color-base: #23b6ea;
|
||||
--ion-color-base-rgb: 35, 182, 234;
|
||||
--ion-color-contrast: #ffffff;
|
||||
--ion-color-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-shade: #1fa0ce;
|
||||
--ion-color-tint: #39bdec;
|
||||
}
|
||||
|
||||
.ion-color-facebook {
|
||||
--ion-color-base: #3b5998;
|
||||
--ion-color-base-rgb: 59, 89, 152;
|
||||
--ion-color-contrast: #ffffff;
|
||||
--ion-color-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-shade: #344e86;
|
||||
--ion-color-tint: #4f6aa2;
|
||||
}
|
||||
|
||||
/* Shared Variables
|
||||
// --------------------------------------------------
|
||||
// To customize the look and feel of this app, you can override
|
||||
// the CSS variables found in Ionic's source files.
|
||||
// To view all the possible Ionic variables, see:
|
||||
// https://beta.ionicframework.com/docs/theming/css-variables#ionic-variables
|
||||
*/
|
||||
|
||||
:root {
|
||||
--ion-headings-font-weight: 300;
|
||||
|
||||
--ion-color-angular: #ac282b;
|
||||
--ion-color-communication: #8e8d93;
|
||||
--ion-color-tooling: #fe4c52;
|
||||
--ion-color-services: #fd8b2d;
|
||||
--ion-color-design: #fed035;
|
||||
--ion-color-workshop: #69bb7b;
|
||||
--ion-color-food: #3bc7c4;
|
||||
--ion-color-documentation: #b16be3;
|
||||
--ion-color-navigation: #6600cc;
|
||||
}
|
||||
|
||||
/*
|
||||
* Dark Theme
|
||||
* ----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
.dark-theme {
|
||||
--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
|
||||
* ----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
.dark-theme.ios {
|
||||
--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-toolbar-background: #0d0d0d;
|
||||
|
||||
--ion-item-background: #1c1c1c;
|
||||
--ion-item-background-activated: #313131;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Material Design Dark Theme
|
||||
* ----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
.dark-theme.md {
|
||||
--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;
|
||||
}
|
||||
15
examples/ionic-react/src/util/types.ts
Normal file
15
examples/ionic-react/src/util/types.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export interface DispatchObject {
|
||||
[key: string]: any;
|
||||
type: string;
|
||||
}
|
||||
|
||||
type PromiseResolveValue<T> = T extends Promise<infer R> ? R : T;
|
||||
type EffectType<T extends (...args: any) => any> = ReturnType<ReturnType<T>>;
|
||||
type EffectReturnValue<T extends (...args: any) => any> = PromiseResolveValue<
|
||||
EffectType<T>
|
||||
>;
|
||||
export type ActionType<T extends (...args: any) => any> = ReturnType<
|
||||
T
|
||||
> extends DispatchObject
|
||||
? ReturnType<T>
|
||||
: EffectReturnValue<T>;
|
||||
19
examples/ionic-react/tsconfig.json
Normal file
19
examples/ionic-react/tsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "preserve"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
3
examples/ionic-react/tslint.json
Normal file
3
examples/ionic-react/tslint.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": ["tslint-react"]
|
||||
}
|
||||
Reference in New Issue
Block a user