├── .gitignore ├── .prettierrc.json ├── capacitor.config.ts ├── ionic.config.json ├── package-lock.json ├── package.json ├── public ├── assets │ ├── icon │ │ ├── favicon.png │ │ └── icon.png │ └── shapes.svg ├── index.html └── manifest.json ├── src ├── App.test.tsx ├── App.tsx ├── components │ ├── Menu.css │ └── Menu.tsx ├── index.tsx ├── pages │ ├── Plugin.css │ └── Plugin.tsx ├── react-app-env.d.ts ├── reportWebVitals.ts ├── service-worker.ts ├── serviceWorkerRegistration.ts ├── setupTests.ts └── theme │ └── variables.css └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | .vscode 21 | .idea 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # Optional eslint cache 28 | .eslintcache 29 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /capacitor.config.ts: -------------------------------------------------------------------------------- 1 | import { CapacitorConfig } from '@capacitor/cli'; 2 | 3 | const config: CapacitorConfig = { 4 | appId: 'io.ionic.starter', 5 | appName: 'capacitor-plugin-demo', 6 | webDir: 'build', 7 | bundledWebRuntime: false 8 | }; 9 | 10 | export default config; 11 | -------------------------------------------------------------------------------- /ionic.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "capacitor-plugin-demo", 3 | "integrations": { 4 | "capacitor": {} 5 | }, 6 | "type": "react" 7 | } 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "capacitor-plugin-demo", 3 | "version": "0.0.1", 4 | "private": true, 5 | "prettier": "@ionic/prettier-config", 6 | "dependencies": { 7 | "@capacitor/action-sheet": "^1.0.0", 8 | "@capacitor/app": "^1.0.0", 9 | "@capacitor/app-launcher": "^1.0.0", 10 | "@capacitor/browser": "^1.0.0", 11 | "@capacitor/camera": "^1.0.0", 12 | "@capacitor/clipboard": "^1.0.0", 13 | "@capacitor/core": "3.0.0", 14 | "@capacitor/device": "^1.0.0", 15 | "@capacitor/dialog": "^1.0.0", 16 | "@capacitor/filesystem": "^1.0.0", 17 | "@capacitor/geolocation": "^1.0.0", 18 | "@capacitor/haptics": "^1.0.0", 19 | "@capacitor/keyboard": "^1.0.0", 20 | "@capacitor/local-notifications": "^1.0.0", 21 | "@capacitor/network": "^1.0.0", 22 | "@capacitor/screen-reader": "^1.0.0", 23 | "@capacitor/share": "^1.0.0", 24 | "@capacitor/splash-screen": "^1.0.0", 25 | "@capacitor/status-bar": "^1.0.0", 26 | "@capacitor/storage": "^1.0.0", 27 | "@capacitor/text-zoom": "^1.0.0", 28 | "@capacitor/toast": "^1.0.0", 29 | "@ionic/pwa-elements": "^3.0.2", 30 | "@ionic/react": "^5.5.0", 31 | "@ionic/react-router": "^5.5.0", 32 | "@testing-library/jest-dom": "^5.11.9", 33 | "@testing-library/react": "^11.2.5", 34 | "@testing-library/user-event": "^12.6.3", 35 | "@types/jest": "^26.0.20", 36 | "@types/node": "^12.19.15", 37 | "@types/react": "^16.14.3", 38 | "@types/react-dom": "^16.9.10", 39 | "@types/react-router": "^5.1.11", 40 | "@types/react-router-dom": "^5.1.7", 41 | "ionicons": "^5.4.0", 42 | "prettier": "^2.3.0", 43 | "react": "^17.0.1", 44 | "react-dom": "^17.0.1", 45 | "react-router": "^5.2.0", 46 | "react-router-dom": "^5.2.0", 47 | "react-scripts": "4.0.2", 48 | "typescript": "^4.1.3", 49 | "web-vitals": "^0.2.4", 50 | "workbox-background-sync": "^5.1.4", 51 | "workbox-broadcast-update": "^5.1.4", 52 | "workbox-cacheable-response": "^5.1.4", 53 | "workbox-core": "^5.1.4", 54 | "workbox-expiration": "^5.1.4", 55 | "workbox-google-analytics": "^5.1.4", 56 | "workbox-navigation-preload": "^5.1.4", 57 | "workbox-precaching": "^5.1.4", 58 | "workbox-range-requests": "^5.1.4", 59 | "workbox-routing": "^5.1.4", 60 | "workbox-strategies": "^5.1.4", 61 | "workbox-streams": "^5.1.4" 62 | }, 63 | "scripts": { 64 | "start": "react-scripts start", 65 | "build": "react-scripts build", 66 | "test": "react-scripts test", 67 | "eject": "react-scripts eject" 68 | }, 69 | "eslintConfig": { 70 | "extends": [ 71 | "react-app", 72 | "react-app/jest" 73 | ] 74 | }, 75 | "browserslist": { 76 | "production": [ 77 | ">0.2%", 78 | "not dead", 79 | "not op_mini all" 80 | ], 81 | "development": [ 82 | "last 1 chrome version", 83 | "last 1 firefox version", 84 | "last 1 safari version" 85 | ] 86 | }, 87 | "devDependencies": { 88 | "@capacitor/cli": "3.0.0", 89 | "@ionic/prettier-config": "^1.0.1" 90 | }, 91 | "description": "An Ionic project" 92 | } 93 | -------------------------------------------------------------------------------- /public/assets/icon/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/capacitor-plugin-demo/47c4da635d7b4299a4cdc63cfe185b7bd19b943a/public/assets/icon/favicon.png -------------------------------------------------------------------------------- /public/assets/icon/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/capacitor-plugin-demo/47c4da635d7b4299a4cdc63cfe185b7bd19b943a/public/assets/icon/icon.png -------------------------------------------------------------------------------- /public/assets/shapes.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ionic App 6 | 7 | 8 | 9 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Ionic App", 3 | "name": "My Ionic App", 4 | "icons": [ 5 | { 6 | "src": "assets/icon/favicon.png", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "assets/icon/icon.png", 12 | "type": "image/png", 13 | "sizes": "512x512", 14 | "purpose": "maskable" 15 | } 16 | ], 17 | "start_url": ".", 18 | "display": "standalone", 19 | "theme_color": "#ffffff", 20 | "background_color": "#ffffff" 21 | } 22 | -------------------------------------------------------------------------------- /src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders without crashing', () => { 6 | const { baseElement } = render(); 7 | expect(baseElement).toBeDefined(); 8 | }); 9 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { IonApp, IonRouterOutlet, IonSplitPane } from '@ionic/react'; 2 | import { IonReactRouter } from '@ionic/react-router'; 3 | import { Redirect, Route } from 'react-router-dom'; 4 | import Menu from './components/Menu'; 5 | import PluginPage from './pages/Plugin'; 6 | 7 | /* Core CSS required for Ionic components to work properly */ 8 | import '@ionic/react/css/core.css'; 9 | 10 | /* Basic CSS for apps built with Ionic */ 11 | import '@ionic/react/css/normalize.css'; 12 | import '@ionic/react/css/structure.css'; 13 | import '@ionic/react/css/typography.css'; 14 | 15 | /* Optional CSS utils that can be commented out */ 16 | import '@ionic/react/css/padding.css'; 17 | import '@ionic/react/css/float-elements.css'; 18 | import '@ionic/react/css/text-alignment.css'; 19 | import '@ionic/react/css/text-transformation.css'; 20 | import '@ionic/react/css/flex-utils.css'; 21 | import '@ionic/react/css/display.css'; 22 | 23 | /* Theme variables */ 24 | import './theme/variables.css'; 25 | 26 | const App: React.FC = () => { 27 | return ( 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | ); 44 | }; 45 | 46 | export default App; 47 | -------------------------------------------------------------------------------- /src/components/Menu.css: -------------------------------------------------------------------------------- 1 | ion-menu ion-content { 2 | --background: var(--ion-item-background, var(--ion-background-color, #fff)); 3 | } 4 | 5 | ion-menu.md ion-content { 6 | --padding-start: 8px; 7 | --padding-end: 8px; 8 | --padding-top: 20px; 9 | --padding-bottom: 20px; 10 | } 11 | 12 | ion-menu.md ion-list { 13 | padding: 20px 0; 14 | } 15 | 16 | ion-menu.md ion-note { 17 | margin-bottom: 30px; 18 | } 19 | 20 | ion-menu.md ion-list-header, ion-menu.md ion-note { 21 | padding-left: 10px; 22 | } 23 | 24 | ion-menu.md ion-list#inbox-list { 25 | border-bottom: 1px solid var(--ion-color-step-150, #d7d8da); 26 | } 27 | 28 | ion-menu.md ion-list#inbox-list ion-list-header { 29 | font-size: 22px; 30 | font-weight: 600; 31 | min-height: 20px; 32 | } 33 | 34 | ion-menu.md ion-list#labels-list ion-list-header { 35 | font-size: 16px; 36 | margin-bottom: 18px; 37 | color: #757575; 38 | min-height: 26px; 39 | } 40 | 41 | ion-menu.md ion-item { 42 | --padding-start: 10px; 43 | --padding-end: 10px; 44 | border-radius: 4px; 45 | } 46 | 47 | ion-menu.md ion-item.selected { 48 | --background: rgba(var(--ion-color-primary-rgb), 0.14); 49 | } 50 | 51 | ion-menu.md ion-item.selected ion-icon { 52 | color: var(--ion-color-primary); 53 | } 54 | 55 | ion-menu.md ion-item ion-icon { 56 | color: #616e7e; 57 | } 58 | 59 | ion-menu.md ion-item ion-label { 60 | font-weight: 500; 61 | } 62 | 63 | ion-menu.ios ion-content { 64 | --padding-bottom: 20px; 65 | } 66 | 67 | ion-menu.ios ion-list { 68 | padding: 20px 0 0 0; 69 | } 70 | 71 | ion-menu.ios ion-note { 72 | line-height: 24px; 73 | margin-bottom: 20px; 74 | } 75 | 76 | ion-menu.ios ion-item { 77 | --padding-start: 16px; 78 | --padding-end: 16px; 79 | --min-height: 50px; 80 | } 81 | 82 | ion-menu.ios ion-item ion-icon { 83 | font-size: 24px; 84 | color: #73849a; 85 | } 86 | 87 | ion-menu.ios ion-item .selected ion-icon { 88 | color: var(--ion-color-primary); 89 | } 90 | 91 | ion-menu.ios ion-list#labels-list ion-list-header { 92 | margin-bottom: 8px; 93 | } 94 | 95 | ion-menu.ios ion-list-header, 96 | ion-menu.ios ion-note { 97 | padding-left: 16px; 98 | padding-right: 16px; 99 | } 100 | 101 | ion-menu.ios ion-note { 102 | margin-bottom: 8px; 103 | } 104 | 105 | ion-note { 106 | display: inline-block; 107 | font-size: 16px; 108 | color: var(--ion-color-medium-shade); 109 | } 110 | 111 | ion-item.selected { 112 | --color: var(--ion-color-primary); 113 | } -------------------------------------------------------------------------------- /src/components/Menu.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | IonContent, 3 | IonIcon, 4 | IonItem, 5 | IonLabel, 6 | IonList, 7 | IonListHeader, 8 | IonMenu, 9 | IonMenuToggle, 10 | IonNote, 11 | } from '@ionic/react'; 12 | 13 | import { useLocation } from 'react-router-dom'; 14 | import { 15 | albums, 16 | apps, 17 | archiveOutline, 18 | archiveSharp, 19 | body, 20 | book, 21 | bookmarkOutline, 22 | browsers, 23 | camera, 24 | chatbox, 25 | clipboard, 26 | cog, 27 | compass, 28 | document, 29 | fileTray, 30 | funnel, 31 | heartOutline, 32 | heartSharp, 33 | informationCircle, 34 | key, 35 | list, 36 | mailOutline, 37 | mailSharp, 38 | mic, 39 | move, 40 | notifications, 41 | notificationsOutline, 42 | paperPlaneOutline, 43 | paperPlaneSharp, 44 | phonePortrait, 45 | rocket, 46 | share, 47 | square, 48 | trashOutline, 49 | trashSharp, 50 | warningOutline, 51 | warningSharp, 52 | wifi, 53 | } from 'ionicons/icons'; 54 | import './Menu.css'; 55 | 56 | interface AppPage { 57 | url: string; 58 | icon: string; 59 | title: string; 60 | } 61 | 62 | const appPages: AppPage[] = [ 63 | { 64 | title: 'Action Sheet', 65 | url: '/plugin/action-sheet', 66 | icon: list, 67 | }, 68 | { 69 | title: 'App', 70 | url: '/plugin/app', 71 | icon: apps, 72 | }, 73 | { 74 | title: 'App Launcher', 75 | url: '/plugin/app-launcher', 76 | icon: rocket, 77 | }, 78 | { 79 | title: 'Browser', 80 | url: '/plugin/browser', 81 | icon: browsers, 82 | }, 83 | { 84 | title: 'Camera', 85 | url: '/plugin/camera', 86 | icon: camera, 87 | }, 88 | { 89 | title: 'Clipboard', 90 | url: '/plugin/clipboard', 91 | icon: clipboard, 92 | }, 93 | { 94 | title: 'Device', 95 | url: '/plugin/device', 96 | icon: informationCircle, 97 | }, 98 | { 99 | title: 'Dialog', 100 | url: '/plugin/dialog', 101 | icon: chatbox, 102 | }, 103 | { 104 | title: 'Filesystem', 105 | url: '/plugin/filesystem', 106 | icon: fileTray, 107 | }, 108 | { 109 | title: 'Geolocation', 110 | url: '/plugin/geolocation', 111 | icon: compass, 112 | }, 113 | { 114 | title: 'Haptics', 115 | url: '/plugin/haptics', 116 | icon: body, 117 | }, 118 | { 119 | title: 'Keyboard', 120 | url: '/plugin/keyboard', 121 | icon: key, 122 | }, 123 | { 124 | title: 'Local Notifications', 125 | url: '/plugin/local-notifications', 126 | icon: notifications, 127 | }, 128 | { 129 | title: 'Motion', 130 | url: '/plugin/motion', 131 | icon: move, 132 | }, 133 | { 134 | title: 'Network', 135 | url: '/plugin/network', 136 | icon: wifi, 137 | }, 138 | { 139 | title: 'Push Notifications', 140 | url: '/plugin/push-notifications', 141 | icon: notificationsOutline, 142 | }, 143 | { 144 | title: 'Screen Reader', 145 | url: '/plugin/screen-reader', 146 | icon: book, 147 | }, 148 | { 149 | title: 'Share', 150 | url: '/plugin/share', 151 | icon: share, 152 | }, 153 | { 154 | title: 'Splash Screen', 155 | url: '/plugin/splash-screen', 156 | icon: phonePortrait, 157 | }, 158 | { 159 | title: 'Status Bar', 160 | url: '/plugin/status-bar', 161 | icon: funnel, 162 | }, 163 | { 164 | title: 'Storage', 165 | url: '/plugin/storage', 166 | icon: document, 167 | }, 168 | { 169 | title: 'Text Zoom', 170 | url: '/plugin/text-zoom', 171 | icon: square, 172 | }, 173 | { 174 | title: 'Toast', 175 | url: '/plugin/toast', 176 | icon: square, 177 | }, 178 | ]; 179 | 180 | const Menu: React.FC = () => { 181 | const location = useLocation(); 182 | 183 | return ( 184 | 185 | 186 | 187 | Plugins 188 | Capacitor v3 Plugins 189 | {appPages.map((appPage, index) => { 190 | return ( 191 | 192 | 201 | 202 | {appPage.title} 203 | 204 | 205 | ); 206 | })} 207 | 208 | 209 | 210 | ); 211 | }; 212 | 213 | export default Menu; 214 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import * as serviceWorkerRegistration from './serviceWorkerRegistration'; 5 | import reportWebVitals from './reportWebVitals'; 6 | 7 | import { defineCustomElements } from '@ionic/pwa-elements/loader'; 8 | 9 | ReactDOM.render( 10 | 11 | 12 | , 13 | document.getElementById('root') 14 | ); 15 | 16 | // If you want your app to work offline and load faster, you can change 17 | // unregister() to register() below. Note this comes with some pitfalls. 18 | // Learn more about service workers: https://cra.link/PWA 19 | serviceWorkerRegistration.unregister(); 20 | 21 | defineCustomElements(window); 22 | 23 | // If you want to start measuring performance in your app, pass a function 24 | // to log results (for example: reportWebVitals(console.log)) 25 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 26 | reportWebVitals(); 27 | -------------------------------------------------------------------------------- /src/pages/Plugin.css: -------------------------------------------------------------------------------- 1 | .plugin { 2 | padding: 24px; 3 | } -------------------------------------------------------------------------------- /src/pages/Plugin.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | IonButton, 3 | IonButtons, 4 | IonContent, 5 | IonHeader, 6 | IonItem, 7 | IonLabel, 8 | IonList, 9 | IonMenuButton, 10 | IonPage, 11 | IonTitle, 12 | IonToolbar, 13 | } from '@ionic/react'; 14 | import { useParams } from 'react-router'; 15 | import './Plugin.css'; 16 | 17 | import { App } from '@capacitor/app'; 18 | import { AppLauncher } from '@capacitor/app-launcher'; 19 | import { ActionSheet, ActionSheetButtonStyle } from '@capacitor/action-sheet'; 20 | import { Browser } from '@capacitor/browser'; 21 | import { Camera, CameraResultType } from '@capacitor/camera'; 22 | import { Clipboard } from '@capacitor/clipboard'; 23 | import { Device } from '@capacitor/device'; 24 | import { Dialog } from '@capacitor/dialog'; 25 | import { Filesystem, Directory, Encoding } from '@capacitor/filesystem'; 26 | import { Geolocation, Position } from '@capacitor/geolocation'; 27 | import { Haptics, ImpactStyle } from '@capacitor/haptics'; 28 | import { Keyboard, KeyboardResize } from '@capacitor/keyboard'; 29 | import { LocalNotifications } from '@capacitor/local-notifications'; 30 | import { Network } from '@capacitor/network'; 31 | import { ScreenReader } from '@capacitor/screen-reader'; 32 | import { Share } from '@capacitor/share'; 33 | import { SplashScreen } from '@capacitor/splash-screen'; 34 | import { StatusBar } from '@capacitor/status-bar'; 35 | import { Storage } from '@capacitor/storage'; 36 | import { TextZoom } from '@capacitor/text-zoom'; 37 | import { Toast } from '@capacitor/toast'; 38 | 39 | import { useCallback } from 'react'; 40 | 41 | interface PluginEntry { 42 | name: string; 43 | package: string; 44 | description: string; 45 | methods: { [key: string]: any }; 46 | } 47 | 48 | const pluginData = { 49 | 'action-sheet': { 50 | name: 'Action Sheet', 51 | package: '@capacitor/action-sheet', 52 | description: 53 | 'The Action Sheet API provides access to native Action Sheets, which come up from the bottom of the screen and display actions a user can take.', 54 | methods: { 55 | showActions: async () => { 56 | return ActionSheet.showActions({ 57 | title: 'Photo Options', 58 | message: 'Select an option to perform', 59 | options: [ 60 | { 61 | title: 'Upload', 62 | }, 63 | { 64 | title: 'Share', 65 | }, 66 | { 67 | title: 'Remove', 68 | style: ActionSheetButtonStyle.Destructive, 69 | }, 70 | ], 71 | }); 72 | }, 73 | }, 74 | }, 75 | 'app': { 76 | name: 'App', 77 | description: 78 | 'The App API handles high level App state and events. For example, this API emits events when the app enters and leaves the foreground, handles deeplinks, opens other apps, and manages persisted plugin state.', 79 | package: '@capacitor/app', 80 | methods: { 81 | exitApp: () => App.exitApp(), 82 | getInfo: () => App.getInfo(), 83 | getState: () => App.getState(), 84 | getLaunchUrl: () => App.getLaunchUrl(), 85 | }, 86 | }, 87 | 'app-launcher': { 88 | name: 'App Launcher', 89 | description: 'The AppLauncher API allows your app to open other apps', 90 | package: '@capacitor/app-launcher', 91 | methods: { 92 | canOpenUrl: () => { 93 | return AppLauncher.canOpenUrl({ url: 'com.getcapacitor.myapp' }); 94 | }, 95 | openUrl: () => { 96 | return AppLauncher.openUrl({ 97 | url: 'com.getcapacitor.myapp://page?id=portfolio', 98 | }); 99 | }, 100 | }, 101 | }, 102 | 'browser': { 103 | name: 'Browser', 104 | description: `The Browser API provides the ability to open an in-app browser and subscribe to browser events.`, 105 | package: '@capacitor/browser', 106 | methods: { 107 | open: () => { 108 | Browser.open({ url: 'http://capacitorjs.com/' }); 109 | }, 110 | close: () => Browser.close(), 111 | }, 112 | }, 113 | 'camera': { 114 | name: 'Camera', 115 | package: '@capacitor/camera', 116 | description: 117 | 'The Camera API provides the ability to take a photo with the camera or choose an existing one from the photo album.', 118 | methods: { 119 | getPhoto: async () => { 120 | const image = await Camera.getPhoto({ 121 | quality: 90, 122 | allowEditing: true, 123 | resultType: CameraResultType.Uri, 124 | }); 125 | 126 | // image.webPath will contain a path that can be set as an image src. 127 | // You can access the original file using image.path, which can be 128 | // passed to the Filesystem API to read the raw data of the image, 129 | // if desired (or pass resultType: CameraResultType.Base64 to getPhoto) 130 | var imageUrl = image.webPath; 131 | 132 | // Can be set to the src of an image now 133 | // imageElement.src = imageUrl; 134 | }, 135 | }, 136 | }, 137 | 'clipboard': { 138 | name: 'Clipboard', 139 | description: 140 | 'The Clipboard API enables copy and pasting to/from the system clipboard.', 141 | package: '@capacitor/clipboard', 142 | methods: { 143 | write: () => 144 | Clipboard.write({ 145 | string: 'Hello World!', 146 | }), 147 | read: () => Clipboard.read(), 148 | }, 149 | }, 150 | 'device': { 151 | name: 'Device', 152 | description: 153 | 'The Device API exposes internal information about the device, such as the model and operating system version, along with user information such as unique ids.', 154 | package: '@capacitor/device', 155 | methods: { 156 | getId: () => Device.getId(), 157 | getInfo: () => Device.getInfo(), 158 | getBatteryInfo: () => Device.getBatteryInfo(), 159 | getLanguageCode: () => Device.getLanguageCode(), 160 | }, 161 | }, 162 | 'dialog': { 163 | name: 'Dialog', 164 | description: 165 | 'The Dialog API provides methods for triggering native dialog windows for alerts, confirmations, and input prompts', 166 | package: '@capacitor/dialog', 167 | methods: { 168 | alert: () => 169 | Dialog.alert({ 170 | title: 'Stop', 171 | message: 'this is an error', 172 | }), 173 | confirm: () => 174 | Dialog.confirm({ 175 | title: 'Confirm', 176 | message: `Are you sure you'd like to press the red button?`, 177 | }), 178 | prompt: () => 179 | Dialog.prompt({ 180 | title: 'Hello', 181 | message: `What's your name?`, 182 | }), 183 | }, 184 | }, 185 | 'filesystem': { 186 | name: 'Filesystem', 187 | description: 188 | 'The Filesystem API provides a NodeJS-like API for working with files on the device.', 189 | package: '@capacitor/filesystem', 190 | methods: { 191 | readFile: () => 192 | Filesystem.readFile({ 193 | path: 'secrets/text.txt', 194 | directory: Directory.Documents, 195 | encoding: Encoding.UTF8, 196 | }), 197 | writeFile: () => 198 | Filesystem.writeFile({ 199 | path: 'secrets/text.txt', 200 | data: 'This is a test', 201 | directory: Directory.Documents, 202 | encoding: Encoding.UTF8, 203 | }), 204 | appendFile: () => 205 | Filesystem.appendFile({ 206 | path: 'secrets/text.txt', 207 | data: 'This is a test', 208 | directory: Directory.Documents, 209 | encoding: Encoding.UTF8, 210 | }), 211 | deleteFile: () => 212 | Filesystem.deleteFile({ 213 | path: 'secrets/text.txt', 214 | directory: Directory.Documents, 215 | }), 216 | 217 | mkdir: () => 218 | Filesystem.mkdir({ 219 | path: 'secrets/testdir', 220 | }), 221 | rmdir: () => 222 | Filesystem.rmdir({ 223 | path: 'secrets/testdir', 224 | }), 225 | readdir: () => 226 | Filesystem.readdir({ 227 | path: 'secrets/testdir', 228 | }), 229 | getUri: () => 230 | Filesystem.getUri({ 231 | path: 'secrets/text.txt', 232 | directory: Directory.Documents, 233 | }), 234 | stat: () => 235 | Filesystem.stat({ 236 | path: 'secrets/text.txt', 237 | directory: Directory.Documents, 238 | }), 239 | rename: () => 240 | Filesystem.rename({ 241 | from: 'secrets/text.txt', 242 | directory: Directory.Documents, 243 | to: 'secrets/old.txt', 244 | toDirectory: Directory.Documents, 245 | }), 246 | copy: () => 247 | Filesystem.copy({ 248 | from: 'secrets/text.txt', 249 | directory: Directory.Documents, 250 | to: 'secrets/text2.txt', 251 | toDirectory: Directory.Documents, 252 | }), 253 | }, 254 | }, 255 | 'geolocation': { 256 | name: 'Geolocation', 257 | description: 258 | 'The Geolocation API provides simple methods for getting and tracking the current position of the device using GPS, along with altitude, heading, and speed information if available.', 259 | package: '@capacitor/geolocation', 260 | methods: { 261 | getCurrentPosition: () => Geolocation.getCurrentPosition(), 262 | watchPosition: () => 263 | Geolocation.watchPosition( 264 | { 265 | enableHighAccuracy: true, 266 | }, 267 | (position: Position | null) => { 268 | console.log(position); 269 | }, 270 | ), 271 | clearWatch: () => 272 | Geolocation.clearWatch({ 273 | id: '', 274 | }), 275 | }, 276 | }, 277 | 'haptics': { 278 | name: 'Haptics', 279 | description: 280 | 'The Haptics API provides physical feedback to the user through touch or vibration.', 281 | package: '@capacitor/haptics', 282 | methods: { 283 | impact: () => Haptics.impact({ style: ImpactStyle.Medium }), 284 | vibrate: () => Haptics.vibrate(), 285 | selectionStart: () => Haptics.selectionStart(), 286 | selectionChanged: () => Haptics.selectionChanged(), 287 | selectionEnd: () => Haptics.selectionEnd(), 288 | }, 289 | }, 290 | 'keyboard': { 291 | name: 'Keyboard', 292 | description: 293 | 'The Keyboard API provides keyboard display and visibility control, along with event tracking when the keyboard shows and hides.', 294 | package: '@capacitor/keyboard', 295 | methods: { 296 | show: () => Keyboard.show(), 297 | hide: () => Keyboard.hide(), 298 | setAccessoryBarVisible: () => 299 | Keyboard.setAccessoryBarVisible({ 300 | isVisible: false, 301 | }), 302 | setScroll: () => 303 | Keyboard.setScroll({ 304 | isDisabled: true, 305 | }), 306 | setResizeMode: () => 307 | Keyboard.setResizeMode({ 308 | mode: KeyboardResize.Ionic, 309 | }), 310 | }, 311 | }, 312 | 'local-notifications': { 313 | name: 'LocalNotifications', 314 | description: 315 | 'The Local Notifications API provides a way to schedule device notifications locally (i.e. without a server sending push notifications).', 316 | package: '@capacitor/local-notifications', 317 | methods: { 318 | schedule: () => { 319 | return LocalNotifications.schedule({ 320 | notifications: [ 321 | { 322 | title: 'Title', 323 | body: 'Body', 324 | id: 1, 325 | schedule: { at: new Date(Date.now() + 1000 * 5) }, 326 | sound: undefined, 327 | attachments: undefined, 328 | actionTypeId: '', 329 | extra: null, 330 | }, 331 | ], 332 | }); 333 | }, 334 | getPending: () => LocalNotifications.getPending(), 335 | registerActionTypes: () => 336 | LocalNotifications.registerActionTypes({ 337 | types: [ 338 | { 339 | id: 'thing', 340 | actions: [{ id: 'action', title: 'Go' }], 341 | }, 342 | ], 343 | }), 344 | cancel: () => 345 | LocalNotifications.cancel({ 346 | notifications: [ 347 | { 348 | id: 1, 349 | }, 350 | ], 351 | }), 352 | areEnabled: () => LocalNotifications.areEnabled(), 353 | createChannel: () => 354 | LocalNotifications.createChannel({ 355 | id: 'c1', 356 | name: 'My Channel', 357 | importance: 1, 358 | }), 359 | deleteChannel: () => 360 | LocalNotifications.deleteChannel({ 361 | id: 'c1', 362 | name: 'My Channel', 363 | importance: 1, 364 | }), 365 | listChannels: () => LocalNotifications.listChannels(), 366 | checkPermissions: () => LocalNotifications.checkPermissions(), 367 | requestPermissions: () => LocalNotifications.requestPermissions(), 368 | }, 369 | }, 370 | 'motion': { 371 | name: 'Motion', 372 | description: 373 | 'The Motion API tracks accelerometer and device orientation (compass heading, etc.)', 374 | package: '@capacitor/motion', 375 | methods: {}, 376 | }, 377 | 'network': { 378 | name: 'Network', 379 | description: 380 | 'The Network API provides network and connectivity information.', 381 | package: '@capacitor/network', 382 | methods: { 383 | getStatus: () => Network.getStatus(), 384 | }, 385 | }, 386 | 'push-notifications': { 387 | name: 'Push Notifications', 388 | description: 389 | 'The Push Notifications API provides access to native push notifications.', 390 | package: '@capacitor/push-notifications', 391 | methods: {}, 392 | }, 393 | 'screen-reader': { 394 | name: 'Screen Reader', 395 | description: 396 | 'The Screen Reader API provides access to TalkBack/VoiceOver/etc. and provides simple text-to-speech capabilities for visual accessibility.', 397 | package: '@capacitor/screen-reader', 398 | methods: { 399 | isEnabled: () => ScreenReader.isEnabled(), 400 | speak: () => 401 | ScreenReader.speak({ 402 | value: 'Hello, Capacitor!', 403 | }), 404 | }, 405 | }, 406 | 'share': { 407 | name: 'Share', 408 | description: 409 | 'The Share API provides methods for sharing content in any sharing-enabled apps the user may have installed.', 410 | package: '@capacitor/share', 411 | methods: { 412 | share: () => 413 | Share.share({ 414 | title: 'See cool stuff', 415 | text: 'Really awesome thing you need to see right meow', 416 | url: 'http://ionicframework.com/', 417 | dialogTitle: 'Share with buddies', 418 | }), 419 | }, 420 | }, 421 | 'splash-screen': { 422 | name: 'Splash Screen', 423 | description: 424 | 'The Splash Screen API provides methods for showing or hiding a Splash image.', 425 | package: '@capacitor/splash-screen', 426 | methods: {}, 427 | }, 428 | 'status-bar': { 429 | name: 'Status Bar', 430 | description: 431 | 'The StatusBar API Provides methods for configuring the style of the Status Bar, along with showing or hiding it.', 432 | package: '@capacitor/status-bar', 433 | methods: {}, 434 | }, 435 | 'storage': { 436 | name: 'Storage', 437 | description: 438 | 'The Storage API provides a simple key/value persistent store for lightweight data.', 439 | package: '@capacitor/motion', 440 | methods: { 441 | set: () => 442 | Storage.set({ 443 | key: 'name', 444 | value: 'Max', 445 | }), 446 | get: () => 447 | Storage.get({ 448 | key: 'name', 449 | }), 450 | remove: () => 451 | Storage.remove({ 452 | key: 'name', 453 | }), 454 | }, 455 | }, 456 | 'text-zoom': { 457 | name: 'Text Zoom', 458 | description: 459 | 'The Text Zoom API provides the ability to change Web View text size for visual accessibility.', 460 | package: '@capacitor/text-zoom', 461 | methods: { 462 | get: () => TextZoom.get(), 463 | getPreferred: () => TextZoom.getPreferred(), 464 | set: () => 465 | TextZoom.set({ 466 | value: 0.75, 467 | }), 468 | }, 469 | }, 470 | 'toast': { 471 | name: 'Toast', 472 | description: 473 | 'The Toast API provides a notification pop up for displaying important information to a user. Just like real toast!', 474 | package: '@capacitor/toast', 475 | methods: { 476 | show: () => 477 | Toast.show({ 478 | text: 'Hello!', 479 | }), 480 | }, 481 | }, 482 | } as { [key: string]: PluginEntry }; 483 | 484 | const PluginDemo = ({ plugin }: { plugin: PluginEntry }) => { 485 | const runMethod = useCallback( 486 | async method => { 487 | const methodDemo = plugin.methods[method]; 488 | try { 489 | const ret = await methodDemo?.(); 490 | console.log(`[${plugin.name}] ${method}() - `, ret); 491 | } catch (e) { 492 | console.error(e); 493 | } 494 | }, 495 | [plugin], 496 | ); 497 | 498 | return ( 499 |
500 |

{plugin.name}

501 |

{plugin.description}

502 |

Methods

503 | 504 | {Object.keys(plugin.methods).map(method => ( 505 | 506 | {method} 507 | runMethod(method)}> 508 | Run 509 | 510 | 511 | ))} 512 | 513 |
514 | ); 515 | }; 516 | 517 | const Page: React.FC = () => { 518 | const { name } = useParams<{ name: string }>(); 519 | 520 | const plugin = pluginData[name]; 521 | 522 | if (!plugin) { 523 | return null; 524 | } 525 | 526 | return ( 527 | 528 | 529 | 530 | 531 | 532 | 533 | {plugin.name} 534 | 535 | 536 | 537 | 538 | 539 | 540 | {name} 541 | 542 | 543 | 544 | 545 | 546 | ); 547 | }; 548 | 549 | export default Page; 550 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /src/service-worker.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /* eslint-disable no-restricted-globals */ 3 | 4 | // This service worker can be customized! 5 | // See https://developers.google.com/web/tools/workbox/modules 6 | // for the list of available Workbox modules, or add any other 7 | // code you'd like. 8 | // You can also remove this file if you'd prefer not to use a 9 | // service worker, and the Workbox build step will be skipped. 10 | 11 | import { clientsClaim } from 'workbox-core'; 12 | import { ExpirationPlugin } from 'workbox-expiration'; 13 | import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching'; 14 | import { registerRoute } from 'workbox-routing'; 15 | import { StaleWhileRevalidate } from 'workbox-strategies'; 16 | 17 | declare const self: ServiceWorkerGlobalScope; 18 | 19 | clientsClaim(); 20 | 21 | // Precache all of the assets generated by your build process. 22 | // Their URLs are injected into the manifest variable below. 23 | // This variable must be present somewhere in your service worker file, 24 | // even if you decide not to use precaching. See https://cra.link/PWA 25 | precacheAndRoute(self.__WB_MANIFEST); 26 | 27 | // Set up App Shell-style routing, so that all navigation requests 28 | // are fulfilled with your index.html shell. Learn more at 29 | // https://developers.google.com/web/fundamentals/architecture/app-shell 30 | const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$'); 31 | registerRoute( 32 | // Return false to exempt requests from being fulfilled by index.html. 33 | ({ request, url }: { request: Request; url: URL }) => { 34 | // If this isn't a navigation, skip. 35 | if (request.mode !== 'navigate') { 36 | return false; 37 | } 38 | 39 | // If this is a URL that starts with /_, skip. 40 | if (url.pathname.startsWith('/_')) { 41 | return false; 42 | } 43 | 44 | // If this looks like a URL for a resource, because it contains 45 | // a file extension, skip. 46 | if (url.pathname.match(fileExtensionRegexp)) { 47 | return false; 48 | } 49 | 50 | // Return true to signal that we want to use the handler. 51 | return true; 52 | }, 53 | createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html') 54 | ); 55 | 56 | // An example runtime caching route for requests that aren't handled by the 57 | // precache, in this case same-origin .png requests like those from in public/ 58 | registerRoute( 59 | // Add in any other file extensions or routing criteria as needed. 60 | ({ url }) => url.origin === self.location.origin && url.pathname.endsWith('.png'), 61 | // Customize this strategy as needed, e.g., by changing to CacheFirst. 62 | new StaleWhileRevalidate({ 63 | cacheName: 'images', 64 | plugins: [ 65 | // Ensure that once this runtime cache reaches a maximum size the 66 | // least-recently used images are removed. 67 | new ExpirationPlugin({ maxEntries: 50 }), 68 | ], 69 | }) 70 | ); 71 | 72 | // This allows the web app to trigger skipWaiting via 73 | // registration.waiting.postMessage({type: 'SKIP_WAITING'}) 74 | self.addEventListener('message', (event) => { 75 | if (event.data && event.data.type === 'SKIP_WAITING') { 76 | self.skipWaiting(); 77 | } 78 | }); 79 | 80 | // Any other custom service worker logic can go here. 81 | -------------------------------------------------------------------------------- /src/serviceWorkerRegistration.ts: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://cra.link/PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/) 19 | ); 20 | 21 | type Config = { 22 | onSuccess?: (registration: ServiceWorkerRegistration) => void; 23 | onUpdate?: (registration: ServiceWorkerRegistration) => void; 24 | }; 25 | 26 | export function register(config?: Config) { 27 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 28 | // The URL constructor is available in all browsers that support SW. 29 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 30 | if (publicUrl.origin !== window.location.origin) { 31 | // Our service worker won't work if PUBLIC_URL is on a different origin 32 | // from what our page is served on. This might happen if a CDN is used to 33 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 34 | return; 35 | } 36 | 37 | window.addEventListener('load', () => { 38 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 39 | 40 | if (isLocalhost) { 41 | // This is running on localhost. Let's check if a service worker still exists or not. 42 | checkValidServiceWorker(swUrl, config); 43 | 44 | // Add some additional logging to localhost, pointing developers to the 45 | // service worker/PWA documentation. 46 | navigator.serviceWorker.ready.then(() => { 47 | console.log( 48 | 'This web app is being served cache-first by a service ' + 49 | 'worker. To learn more, visit https://cra.link/PWA' 50 | ); 51 | }); 52 | } else { 53 | // Is not localhost. Just register service worker 54 | registerValidSW(swUrl, config); 55 | } 56 | }); 57 | } 58 | } 59 | 60 | function registerValidSW(swUrl: string, config?: Config) { 61 | navigator.serviceWorker 62 | .register(swUrl) 63 | .then((registration) => { 64 | registration.onupdatefound = () => { 65 | const installingWorker = registration.installing; 66 | if (installingWorker == null) { 67 | return; 68 | } 69 | installingWorker.onstatechange = () => { 70 | if (installingWorker.state === 'installed') { 71 | if (navigator.serviceWorker.controller) { 72 | // At this point, the updated precached content has been fetched, 73 | // but the previous service worker will still serve the older 74 | // content until all client tabs are closed. 75 | console.log( 76 | 'New content is available and will be used when all ' + 77 | 'tabs for this page are closed. See https://cra.link/PWA.' 78 | ); 79 | 80 | // Execute callback 81 | if (config && config.onUpdate) { 82 | config.onUpdate(registration); 83 | } 84 | } else { 85 | // At this point, everything has been precached. 86 | // It's the perfect time to display a 87 | // "Content is cached for offline use." message. 88 | console.log('Content is cached for offline use.'); 89 | 90 | // Execute callback 91 | if (config && config.onSuccess) { 92 | config.onSuccess(registration); 93 | } 94 | } 95 | } 96 | }; 97 | }; 98 | }) 99 | .catch((error) => { 100 | console.error('Error during service worker registration:', error); 101 | }); 102 | } 103 | 104 | function checkValidServiceWorker(swUrl: string, config?: Config) { 105 | // Check if the service worker can be found. If it can't reload the page. 106 | fetch(swUrl, { 107 | headers: { 'Service-Worker': 'script' }, 108 | }) 109 | .then((response) => { 110 | // Ensure service worker exists, and that we really are getting a JS file. 111 | const contentType = response.headers.get('content-type'); 112 | if ( 113 | response.status === 404 || 114 | (contentType != null && contentType.indexOf('javascript') === -1) 115 | ) { 116 | // No service worker found. Probably a different app. Reload the page. 117 | navigator.serviceWorker.ready.then((registration) => { 118 | registration.unregister().then(() => { 119 | window.location.reload(); 120 | }); 121 | }); 122 | } else { 123 | // Service worker found. Proceed as normal. 124 | registerValidSW(swUrl, config); 125 | } 126 | }) 127 | .catch(() => { 128 | console.log('No internet connection found. App is running in offline mode.'); 129 | }); 130 | } 131 | 132 | export function unregister() { 133 | if ('serviceWorker' in navigator) { 134 | navigator.serviceWorker.ready 135 | .then((registration) => { 136 | registration.unregister(); 137 | }) 138 | .catch((error) => { 139 | console.error(error.message); 140 | }); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | 7 | // Mock matchmedia 8 | window.matchMedia = window.matchMedia || function() { 9 | return { 10 | matches: false, 11 | addListener: function() {}, 12 | removeListener: function() {} 13 | }; 14 | }; 15 | -------------------------------------------------------------------------------- /src/theme/variables.css: -------------------------------------------------------------------------------- 1 | /* Ionic Variables and Theming. For more info, please see: 2 | http://ionicframework.com/docs/theming/ */ 3 | 4 | /** Ionic CSS Variables **/ 5 | :root { 6 | /** primary **/ 7 | --ion-color-primary: #3880ff; 8 | --ion-color-primary-rgb: 56, 128, 255; 9 | --ion-color-primary-contrast: #ffffff; 10 | --ion-color-primary-contrast-rgb: 255, 255, 255; 11 | --ion-color-primary-shade: #3171e0; 12 | --ion-color-primary-tint: #4c8dff; 13 | 14 | /** secondary **/ 15 | --ion-color-secondary: #3dc2ff; 16 | --ion-color-secondary-rgb: 61, 194, 255; 17 | --ion-color-secondary-contrast: #ffffff; 18 | --ion-color-secondary-contrast-rgb: 255, 255, 255; 19 | --ion-color-secondary-shade: #36abe0; 20 | --ion-color-secondary-tint: #50c8ff; 21 | 22 | /** tertiary **/ 23 | --ion-color-tertiary: #5260ff; 24 | --ion-color-tertiary-rgb: 82, 96, 255; 25 | --ion-color-tertiary-contrast: #ffffff; 26 | --ion-color-tertiary-contrast-rgb: 255, 255, 255; 27 | --ion-color-tertiary-shade: #4854e0; 28 | --ion-color-tertiary-tint: #6370ff; 29 | 30 | /** success **/ 31 | --ion-color-success: #2dd36f; 32 | --ion-color-success-rgb: 45, 211, 111; 33 | --ion-color-success-contrast: #ffffff; 34 | --ion-color-success-contrast-rgb: 255, 255, 255; 35 | --ion-color-success-shade: #28ba62; 36 | --ion-color-success-tint: #42d77d; 37 | 38 | /** warning **/ 39 | --ion-color-warning: #ffc409; 40 | --ion-color-warning-rgb: 255, 196, 9; 41 | --ion-color-warning-contrast: #000000; 42 | --ion-color-warning-contrast-rgb: 0, 0, 0; 43 | --ion-color-warning-shade: #e0ac08; 44 | --ion-color-warning-tint: #ffca22; 45 | 46 | /** danger **/ 47 | --ion-color-danger: #eb445a; 48 | --ion-color-danger-rgb: 235, 68, 90; 49 | --ion-color-danger-contrast: #ffffff; 50 | --ion-color-danger-contrast-rgb: 255, 255, 255; 51 | --ion-color-danger-shade: #cf3c4f; 52 | --ion-color-danger-tint: #ed576b; 53 | 54 | /** dark **/ 55 | --ion-color-dark: #222428; 56 | --ion-color-dark-rgb: 34, 36, 40; 57 | --ion-color-dark-contrast: #ffffff; 58 | --ion-color-dark-contrast-rgb: 255, 255, 255; 59 | --ion-color-dark-shade: #1e2023; 60 | --ion-color-dark-tint: #383a3e; 61 | 62 | /** medium **/ 63 | --ion-color-medium: #92949c; 64 | --ion-color-medium-rgb: 146, 148, 156; 65 | --ion-color-medium-contrast: #ffffff; 66 | --ion-color-medium-contrast-rgb: 255, 255, 255; 67 | --ion-color-medium-shade: #808289; 68 | --ion-color-medium-tint: #9d9fa6; 69 | 70 | /** light **/ 71 | --ion-color-light: #f4f5f8; 72 | --ion-color-light-rgb: 244, 245, 248; 73 | --ion-color-light-contrast: #000000; 74 | --ion-color-light-contrast-rgb: 0, 0, 0; 75 | --ion-color-light-shade: #d7d8da; 76 | --ion-color-light-tint: #f5f6f9; 77 | } 78 | 79 | @media (prefers-color-scheme: dark) { 80 | /* 81 | * Dark Colors 82 | * ------------------------------------------- 83 | */ 84 | 85 | body { 86 | --ion-color-primary: #428cff; 87 | --ion-color-primary-rgb: 66,140,255; 88 | --ion-color-primary-contrast: #ffffff; 89 | --ion-color-primary-contrast-rgb: 255,255,255; 90 | --ion-color-primary-shade: #3a7be0; 91 | --ion-color-primary-tint: #5598ff; 92 | 93 | --ion-color-secondary: #50c8ff; 94 | --ion-color-secondary-rgb: 80,200,255; 95 | --ion-color-secondary-contrast: #ffffff; 96 | --ion-color-secondary-contrast-rgb: 255,255,255; 97 | --ion-color-secondary-shade: #46b0e0; 98 | --ion-color-secondary-tint: #62ceff; 99 | 100 | --ion-color-tertiary: #6a64ff; 101 | --ion-color-tertiary-rgb: 106,100,255; 102 | --ion-color-tertiary-contrast: #ffffff; 103 | --ion-color-tertiary-contrast-rgb: 255,255,255; 104 | --ion-color-tertiary-shade: #5d58e0; 105 | --ion-color-tertiary-tint: #7974ff; 106 | 107 | --ion-color-success: #2fdf75; 108 | --ion-color-success-rgb: 47,223,117; 109 | --ion-color-success-contrast: #000000; 110 | --ion-color-success-contrast-rgb: 0,0,0; 111 | --ion-color-success-shade: #29c467; 112 | --ion-color-success-tint: #44e283; 113 | 114 | --ion-color-warning: #ffd534; 115 | --ion-color-warning-rgb: 255,213,52; 116 | --ion-color-warning-contrast: #000000; 117 | --ion-color-warning-contrast-rgb: 0,0,0; 118 | --ion-color-warning-shade: #e0bb2e; 119 | --ion-color-warning-tint: #ffd948; 120 | 121 | --ion-color-danger: #ff4961; 122 | --ion-color-danger-rgb: 255,73,97; 123 | --ion-color-danger-contrast: #ffffff; 124 | --ion-color-danger-contrast-rgb: 255,255,255; 125 | --ion-color-danger-shade: #e04055; 126 | --ion-color-danger-tint: #ff5b71; 127 | 128 | --ion-color-dark: #f4f5f8; 129 | --ion-color-dark-rgb: 244,245,248; 130 | --ion-color-dark-contrast: #000000; 131 | --ion-color-dark-contrast-rgb: 0,0,0; 132 | --ion-color-dark-shade: #d7d8da; 133 | --ion-color-dark-tint: #f5f6f9; 134 | 135 | --ion-color-medium: #989aa2; 136 | --ion-color-medium-rgb: 152,154,162; 137 | --ion-color-medium-contrast: #000000; 138 | --ion-color-medium-contrast-rgb: 0,0,0; 139 | --ion-color-medium-shade: #86888f; 140 | --ion-color-medium-tint: #a2a4ab; 141 | 142 | --ion-color-light: #222428; 143 | --ion-color-light-rgb: 34,36,40; 144 | --ion-color-light-contrast: #ffffff; 145 | --ion-color-light-contrast-rgb: 255,255,255; 146 | --ion-color-light-shade: #1e2023; 147 | --ion-color-light-tint: #383a3e; 148 | } 149 | 150 | /* 151 | * iOS Dark Theme 152 | * ------------------------------------------- 153 | */ 154 | 155 | .ios body { 156 | --ion-background-color: #000000; 157 | --ion-background-color-rgb: 0,0,0; 158 | 159 | --ion-text-color: #ffffff; 160 | --ion-text-color-rgb: 255,255,255; 161 | 162 | --ion-color-step-50: #0d0d0d; 163 | --ion-color-step-100: #1a1a1a; 164 | --ion-color-step-150: #262626; 165 | --ion-color-step-200: #333333; 166 | --ion-color-step-250: #404040; 167 | --ion-color-step-300: #4d4d4d; 168 | --ion-color-step-350: #595959; 169 | --ion-color-step-400: #666666; 170 | --ion-color-step-450: #737373; 171 | --ion-color-step-500: #808080; 172 | --ion-color-step-550: #8c8c8c; 173 | --ion-color-step-600: #999999; 174 | --ion-color-step-650: #a6a6a6; 175 | --ion-color-step-700: #b3b3b3; 176 | --ion-color-step-750: #bfbfbf; 177 | --ion-color-step-800: #cccccc; 178 | --ion-color-step-850: #d9d9d9; 179 | --ion-color-step-900: #e6e6e6; 180 | --ion-color-step-950: #f2f2f2; 181 | 182 | --ion-item-background: #000000; 183 | 184 | --ion-card-background: #1c1c1d; 185 | } 186 | 187 | .ios ion-modal { 188 | --ion-background-color: var(--ion-color-step-100); 189 | --ion-toolbar-background: var(--ion-color-step-150); 190 | --ion-toolbar-border-color: var(--ion-color-step-250); 191 | } 192 | 193 | 194 | /* 195 | * Material Design Dark Theme 196 | * ------------------------------------------- 197 | */ 198 | 199 | .md body { 200 | --ion-background-color: #121212; 201 | --ion-background-color-rgb: 18,18,18; 202 | 203 | --ion-text-color: #ffffff; 204 | --ion-text-color-rgb: 255,255,255; 205 | 206 | --ion-border-color: #222222; 207 | 208 | --ion-color-step-50: #1e1e1e; 209 | --ion-color-step-100: #2a2a2a; 210 | --ion-color-step-150: #363636; 211 | --ion-color-step-200: #414141; 212 | --ion-color-step-250: #4d4d4d; 213 | --ion-color-step-300: #595959; 214 | --ion-color-step-350: #656565; 215 | --ion-color-step-400: #717171; 216 | --ion-color-step-450: #7d7d7d; 217 | --ion-color-step-500: #898989; 218 | --ion-color-step-550: #949494; 219 | --ion-color-step-600: #a0a0a0; 220 | --ion-color-step-650: #acacac; 221 | --ion-color-step-700: #b8b8b8; 222 | --ion-color-step-750: #c4c4c4; 223 | --ion-color-step-800: #d0d0d0; 224 | --ion-color-step-850: #dbdbdb; 225 | --ion-color-step-900: #e7e7e7; 226 | --ion-color-step-950: #f3f3f3; 227 | 228 | --ion-item-background: #1e1e1e; 229 | 230 | --ion-toolbar-background: #1f1f1f; 231 | 232 | --ion-tab-bar-background: #1f1f1f; 233 | 234 | --ion-card-background: #1e1e1e; 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | --------------------------------------------------------------------------------