├── .eslintrc.json ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .npmrc ├── .prettierrc.json ├── LICENSE ├── README.md ├── ionic-react-hooks-gh.png ├── ionic-react-hooks.png ├── jest.setup.js ├── lerna.json ├── package-lock.json ├── package.json ├── packages ├── app │ ├── CHANGELOG.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── useApp.test.ts │ │ ├── useApp.ts │ │ └── util │ │ │ ├── feature-check.ts │ │ │ └── models.ts │ └── tsconfig.json ├── browser │ ├── CHANGELOG.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── useBrowser.test.ts │ │ ├── useBrowser.ts │ │ └── util │ │ │ ├── feature-check.ts │ │ │ └── models.ts │ └── tsconfig.json ├── camera │ ├── CHANGELOG.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── useCamera.test.ts │ │ ├── useCamera.ts │ │ └── util │ │ │ ├── feature-check.ts │ │ │ └── models.ts │ └── tsconfig.json ├── clipboard │ ├── CHANGELOG.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── useClipboard.test.ts │ │ ├── useClipboard.ts │ │ └── util │ │ │ ├── feature-check.ts │ │ │ └── models.ts │ └── tsconfig.json ├── device │ ├── CHANGELOG.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── useDevice.test.ts │ │ ├── useDevice.ts │ │ └── util │ │ │ ├── feature-check.ts │ │ │ └── models.ts │ └── tsconfig.json ├── filesystem │ ├── CHANGELOG.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── useFileSystem.ts │ │ ├── util │ │ │ ├── feature-check.ts │ │ │ └── models.ts │ │ └── utils.ts │ └── tsconfig.json ├── geolocation │ ├── CHANGELOG.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── useGeolocation.test.ts │ │ ├── useGeolocation.ts │ │ └── util │ │ │ ├── feature-check.ts │ │ │ └── models.ts │ └── tsconfig.json ├── keyboard │ ├── CHANGELOG.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── useKeyboard.test.ts │ │ ├── useKeyboard.ts │ │ └── util │ │ │ ├── feature-check.ts │ │ │ └── models.ts │ └── tsconfig.json ├── network │ ├── CHANGELOG.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── useNetwork.test.ts │ │ ├── useNetwork.ts │ │ └── util │ │ │ ├── feature-check.ts │ │ │ └── models.ts │ └── tsconfig.json ├── screen-reader │ ├── CHANGELOG.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── useScreenReader.test.ts │ │ ├── useScreenReader.ts │ │ └── util │ │ │ ├── feature-check.ts │ │ │ └── models.ts │ └── tsconfig.json ├── storage │ ├── CHANGELOG.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── useStorage.test.ts │ │ ├── useStorage.ts │ │ └── util │ │ │ ├── feature-check.ts │ │ │ └── models.ts │ └── tsconfig.json └── test-app │ ├── .gitignore │ ├── CHANGELOG.md │ ├── capacitor.config.json │ ├── ionic.config.json │ ├── ios │ ├── .gitignore │ └── App │ │ ├── App.xcodeproj │ │ ├── project.pbxproj │ │ └── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ ├── App.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ ├── App │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ ├── AppIcon-20x20@1x.png │ │ │ │ ├── AppIcon-20x20@2x-1.png │ │ │ │ ├── AppIcon-20x20@2x.png │ │ │ │ ├── AppIcon-20x20@3x.png │ │ │ │ ├── AppIcon-29x29@1x.png │ │ │ │ ├── AppIcon-29x29@2x-1.png │ │ │ │ ├── AppIcon-29x29@2x.png │ │ │ │ ├── AppIcon-29x29@3x.png │ │ │ │ ├── AppIcon-40x40@1x.png │ │ │ │ ├── AppIcon-40x40@2x-1.png │ │ │ │ ├── AppIcon-40x40@2x.png │ │ │ │ ├── AppIcon-40x40@3x.png │ │ │ │ ├── AppIcon-512@2x.png │ │ │ │ ├── AppIcon-60x60@2x.png │ │ │ │ ├── AppIcon-60x60@3x.png │ │ │ │ ├── AppIcon-76x76@1x.png │ │ │ │ ├── AppIcon-76x76@2x.png │ │ │ │ ├── AppIcon-83.5x83.5@2x.png │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ └── Splash.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── splash-2732x2732-1.png │ │ │ │ ├── splash-2732x2732-2.png │ │ │ │ └── splash-2732x2732.png │ │ ├── Base.lproj │ │ │ ├── LaunchScreen.storyboard │ │ │ └── Main.storyboard │ │ ├── Info.plist │ │ ├── capacitor.config.json │ │ └── config.xml │ │ └── Podfile │ ├── package-lock.json │ ├── package.json │ ├── public │ ├── assets │ │ ├── icon │ │ │ └── favicon.png │ │ └── shapes.svg │ ├── favicon.ico │ └── index.html │ ├── scripts │ ├── build.js │ ├── start.js │ └── test.js │ ├── src │ ├── App.test.tsx │ ├── App.tsx │ ├── components │ │ └── Menu.tsx │ ├── declarations.ts │ ├── index.tsx │ ├── pages │ │ ├── AppPage.tsx │ │ ├── BrowserPage.tsx │ │ ├── CameraPage.tsx │ │ ├── ClipboardPage.tsx │ │ ├── DevicePage.tsx │ │ ├── GeolocationPage.tsx │ │ ├── Home.css │ │ ├── Home.tsx │ │ ├── KeyboardPage.tsx │ │ ├── NetworkPage.tsx │ │ ├── ScreenReaderPage.tsx │ │ ├── StoragePage.tsx │ │ └── template.tsx │ ├── react-app-env.d.ts │ ├── theme.css │ └── theme │ │ └── variables.css │ ├── tsconfig.json │ └── tslint.json ├── tsconfig.json └── tslint.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "plugins": ["@typescript-eslint", "prettier"], 4 | "extends": [ 5 | "eslint:recommended", 6 | "plugin:@typescript-eslint/recommended", 7 | "prettier" 8 | ], 9 | "rules": { 10 | "no-fallthrough": "off", 11 | "no-constant-condition": "off", 12 | "@typescript-eslint/no-this-alias": "off", 13 | "@typescript-eslint/no-explicit-any": "off", 14 | "@typescript-eslint/explicit-module-boundary-types": [ 15 | "warn", 16 | { "allowArgumentsExplicitlyTypedAsAny": true } 17 | ], 18 | "prettier/prettier": "error" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | pull_request: 8 | branches: 9 | - '**' 10 | 11 | jobs: 12 | verify: 13 | runs-on: ubuntu-latest 14 | timeout-minutes: 30 15 | steps: 16 | - uses: actions/checkout@v2 17 | - uses: actions/setup-node@v2 18 | with: 19 | node-version: 14.x 20 | cache: 'npm' 21 | cache-dependency-path: '**/package.json' 22 | - run: npm ci 23 | - run: npx lerna run lint 24 | - run: npx lerna run build 25 | deploy: 26 | runs-on: ubuntu-latest 27 | if: github.event_name == 'push' && github.ref == 'refs/heads/main' && startsWith(github.event.head_commit.message, 'chore(release):') 28 | timeout-minutes: 30 29 | needs: 30 | - verify 31 | steps: 32 | - uses: actions/checkout@v2 33 | with: 34 | fetch-depth: 0 35 | - uses: actions/setup-node@v2 36 | with: 37 | node-version: 14.x 38 | cache: 'npm' 39 | cache-dependency-path: '**/package.json' 40 | - name: Setup npm auth 41 | run: | 42 | echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc 43 | npm whoami 44 | env: 45 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 46 | - run: npm ci 47 | - run: npx lerna run build 48 | - run: npm run lerna:publish || true 49 | -------------------------------------------------------------------------------- /.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 | dist 14 | types 15 | *.tgz 16 | 17 | # misc 18 | .DS_Store 19 | .env.local 20 | .env.development.local 21 | .env.test.local 22 | .env.production.local 23 | 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=true 2 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": true, 3 | "printWidth": 100, 4 | "semi": true, 5 | "singleQuote": true, 6 | "jsxSingleQuote": false, 7 | "tabWidth": 2, 8 | "trailingComma": "es5", 9 | 10 | "requirePragma": false 11 | } 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2015-present Drifty Co. 2 | http://drifty.com/ 3 | 4 | MIT License 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |


2 |

Capacitor React Hooks

3 | 4 |

5 | A set of hooks to help Capacitor developers use native Capacitor APIs. 7 |

8 | 9 |

10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 |

19 | 20 | ## Maintainers 21 | 22 | | Maintainer | GitHub | Social | 23 | | ------------ | --------------------------------------------- | ----------------------------------------------- | 24 | | Ely Lucas | [elylucas](https://github.com/elylucas) | [@elylucas](https://twitter.com/elylucas) | 25 | 26 | > These docs are for Capacitor 3 plugins. For docs that target v2 plugins, see the [capv2](https://github.com/capacitor-community/react-hooks/tree/capv2) branch. 27 | ## Getting Started 28 | 29 | To start using Capacitor Hooks in your app, you install the React Hook package along with the Capacitor plugin you want to use. Here is an example of using the Storage plugin along with it's React hook: 30 | 31 | ```bash 32 | # Install the Capacitor Plugin 33 | npm install @capacitor/storage 34 | # And then the React hook package: 35 | npm install @capacitor-community/storage-react 36 | ``` 37 | 38 | Import the hooks: 39 | ```jsx 40 | import { useStorage } from '@capacitor-community/storage-react' 41 | ``` 42 | 43 | Then use the hooks in your app: 44 | 45 | ```jsx 46 | const [value, setValue] = useStorage('mykey'); 47 | ``` 48 | 49 | ## Feature Detection 50 | 51 | While Capacitor allows you to write to one API across several platforms, not all features are supported on all platforms. It is encouraged to check if the feature you intend to use is available before using it to avoid any runtime errors. 52 | 53 | Each of the hook plugin paths exports an `availableFeatures` object, which contains a list features for that plugin. If the feature is supported for the current platform the app is running on, that feature will be true: 54 | 55 | ```jsx 56 | const { useStorageItem, availableFeatures } = `@capacitor-community/storage-react`; 57 | const [value, setValue] = useStorage('mykey'); 58 | ... 59 | if(availableFeatures.useStorage) { 60 | // Storage is available, feel free to use it! 61 | setValue('cake'); 62 | } 63 | ``` 64 | 65 | # Upgrading from Capacitor 2 React Hooks 66 | 67 | In Capacitor 3, all the plugins were separated into their own packages. Likewise, the new React hooks plugins were also put into their own package, so you will need to install the hook for each plugin you use. 68 | 69 | Any deprecated API'S from Capacitor 2 to 3 were also removed and updated, so you might need to make some modifications to account for API changes. See the [Capacitor Plugin API for](https://capacitorjs.com/docs/plugins) to learn more. 70 | 71 | # Hook Usage 72 | 73 | ## @capacitor/app Hooks 74 | 75 | Installation: 76 | 77 | ```bash 78 | npm install @capacitor-community/app-react 79 | ``` 80 | 81 | Usage: 82 | 83 | ```jsx 84 | import { useAppState, useAppUrlOpen, useLaunchUrl, availableFeatures } from '@capacitor-community/app-react'; 85 | ``` 86 | 87 | `useAppState` provides access to App status information, such as whether the app is active or inactive. This value will update dynamically. 88 | 89 | ```jsx 90 | const {state} = useAppState(); 91 | ``` 92 | 93 | #### `useLaunchUrl` 94 | 95 | `useLaunchUrl` provides the URL the app was initially launched with. If you'd like to track future inbound URL events, use `useAppUrlOpen` below instead. 96 | 97 | ```jsx 98 | const { launchUrl } = useLaunchUrl(); 99 | ``` 100 | 101 | #### `useAppUrlOpen` 102 | 103 | `useAppUrlOpen` provides the most recent URL used to activate the app. For example, if the user followed a link in another app that opened your app. 104 | 105 | ```jsx 106 | const { appUrlOpen } = useAppUrlOpen(); 107 | ``` 108 | 109 | See the [App](https://capacitorjs.com/docs/apis/app) Capacitor Plugin docs for more info on the plugin API. 110 | 111 | ## @capcitor/browser Hooks 112 | 113 | Installation: 114 | 115 | ```bash 116 | npm install @capacitor-community/browser-react 117 | ``` 118 | 119 | Usage: 120 | 121 | ```jsx 122 | import { useClose, useOpen, availableFeatures } from '@capacitor-community/browser-react'; 123 | ``` 124 | 125 | `useOpen`, `useClose` provides a way to launch, and close an in-app browser for external content: 126 | 127 | ```jsx 128 | // Open url in browser 129 | const { open } = useOpen(); 130 | 131 | open({ url: 'http://ionicframework.com' }); 132 | 133 | // Close url in browser 134 | const { close } = useClose(); 135 | useClose(); 136 | ``` 137 | 138 | See the [Browser](https://capacitorjs.com/docs/apis/browser) Capacitor Plugin docs for more info on the plugin API. 139 | 140 | ## @capacitor/camera Hooks 141 | 142 | Installation: 143 | 144 | ```bash 145 | npm install @capacitor-community/camera-react 146 | ``` 147 | 148 | Usage: 149 | 150 | ```jsx 151 | import { useCamera, availableFeatures } from '@capacitor-community/camera-react'; 152 | ``` 153 | 154 | `useCamera` provides a way to take a photo: 155 | 156 | ```jsx 157 | const { photo, getPhoto } = useCamera(); 158 | const triggerCamera = useCallback(async () => { 159 | getPhoto({ 160 | quality: 100, 161 | allowEditing: false, 162 | resultType: CameraResultType.DataUrl 163 | }) 164 | }, [getPhoto]); 165 | 166 |
{photo && }
167 | ``` 168 | 169 | See the [Camera](https://capacitorjs.com/docs/apis/camera) Capacitor Plugin docs for more info on the plugin API. 170 | 171 | ## Clipboard Hooks 172 | 173 | Installation: 174 | 175 | ```bash 176 | npm install @capacitor-community/clipboard-react 177 | ``` 178 | 179 | Usage: 180 | 181 | ```jsx 182 | import { useClipboard, availableFeatures } from '@capacitor-community/clipboard-react'; 183 | ``` 184 | 185 | `useClipboard` reads and writes clipboard data: 186 | 187 | ```jsx 188 | const { value, getValue, setValue } = useClipboard(); 189 | 190 | const paste = useCallback(async () => { 191 | await setValue('http://ionicframework.com/); 192 | }, [setValue]); 193 | 194 | const copy = useCallback(async () => { 195 | getValue(); 196 | }, [getValue]) 197 | ``` 198 | 199 | See the [Clipboard](https://capacitorjs.com/docs/apis/clipboard) Capacitor Plugin docs for more info on the plugin API. 200 | 201 | ## Device Hooks 202 | 203 | Installation: 204 | 205 | ```bash 206 | npm install @capacitor-community/device-react 207 | ``` 208 | 209 | Usage: 210 | 211 | ```jsx 212 | import { useGetInfo, useGetLanguageCode, availableFeatures } from '@capacitor-community/device-react'; 213 | ``` 214 | 215 | `useGetInfo`, `useGetLanguageCode` gives access to device information and device language settings: 216 | 217 | ```jsx 218 | const { info } = useGetInfo(); 219 | const { languageCode } = useGetLanguageCode(); 220 | ``` 221 | 222 | See the [Device](https://capacitorjs.com/docs/apis/device) Capacitor Plugin docs for more info on the plugin API. 223 | 224 | ## Filesystem Hooks 225 | 226 | Installation: 227 | 228 | ```bash 229 | npm install @capacitor-community/filesystem-react 230 | ``` 231 | 232 | Usage: 233 | 234 | ```jsx 235 | import { useFilesystem, base64FromPath, availableFeatures } from '@capacitor-community/filesystem-react'; 236 | ``` 237 | 238 | `useFilesystem` returns back common methods to gain access to file system apis. 239 | 240 | ```jsx 241 | const { readFile } = useFilesystem(); 242 | 243 | const file = await readFile({ 244 | path: filepath, 245 | directory: FilesystemDirectory.Data 246 | }); 247 | ``` 248 | 249 | `base64FromPath` is a helper method that will take in a path to a file and return back the base64 encoded representation of that file. 250 | 251 | See the [Filesystem](https://capacitorjs.com/docs/apis/filesystem) Capacitor Plugin docs for more info on the plugin API. 252 | 253 | ```jsx 254 | const base64String = await base64FromPath(path); 255 | ``` 256 | 257 | ## Geolocation Hooks 258 | 259 | Installation: 260 | 261 | ```bash 262 | npm install @capacitor-community/geolocation-react 263 | ``` 264 | 265 | Usage: 266 | 267 | ```jsx 268 | import { useCurrentPosition, useWatchPosition, availableFeatures } from '@capacitor-community/geolocation-react'; 269 | ``` 270 | 271 | `useCurrentPosition` returns a single geolocation position using the Geolocation API in Capacitor. The position can be manually updated by calling `getPosition`: 272 | 273 | ```jsx 274 | const { currentPosition, getPosition } = useCurrentPosition(); 275 | 276 | const handleRefreshPosition = () => { 277 | getPosition(); 278 | } 279 | ``` 280 | 281 | `useWatchPosition` tracks a geolocation position using the `watchPosition` in the Geolocation API in Capacitor. The location will automatically begin updating, and you can use the `clearWatch` and `startWatch` methods to manually stop and restart the watch. 282 | 283 | ```jsx 284 | const { currentPosition, startWatch, clearWatch } = useWatchPosition(); 285 | ``` 286 | 287 | See the [Geolocation](https://capacitorjs.com/docs/apis/geolocation) Capacitor Plugin docs for more info on the plugin API. 288 | ## Keyboard Hooks 289 | 290 | Installation: 291 | 292 | ```bash 293 | npm install @capacitor-community/keyboard-react 294 | ``` 295 | 296 | Usage: 297 | 298 | ```jsx 299 | import { useKeyboardState } from '@capacitor-community/keyboard'; 300 | ``` 301 | 302 | `useKeyboard` returns whether or not the on-screen keyboard is shown as well as an approximation of the keyboard height in pixels. 303 | 304 | ```jsx 305 | const { isOpen, keyboardHeight } = useKeyboard(); 306 | // Use keyboardHeight to translate an input that would otherwise be hidden by the keyboard 307 | ``` 308 | 309 | See the [Keyboard](https://capacitorjs.com/docs/apis/keyboard) Capacitor Plugin docs for more info on the plugin API. 310 | 311 | ## Network Hooks 312 | 313 | Installation: 314 | 315 | ```bash 316 | npm install @capacitor-community/network-react 317 | ``` 318 | 319 | Usage: 320 | 321 | ```jsx 322 | import { useStatus, availableFeatures } from '@capacitor-community/network-react'; 323 | ``` 324 | 325 | `useStatus` monitors network status and information: 326 | 327 | ```jsx 328 | const { networkStatus } = useStatus(); 329 | ``` 330 | 331 | See the [Network](https://capacitorjs.com/docs/apis/network) Capacitor Plugin docs for more info on the plugin API. 332 | ## ScreenReader Hooks 333 | 334 | Installation: 335 | 336 | ```bash 337 | npm install @capacitor-community/screen-reader-react 338 | ``` 339 | 340 | Usage: 341 | 342 | ```jsx 343 | import { useIsScreenReaderEnabled, useSpeak, availableFeatures } from '@capacitor-community/screen-reader-react'; 344 | ``` 345 | 346 | `useIsScreenReaderEnabled` provides access to detecting and responding to a screen reading device or OS setting being enabled: 347 | 348 | ```jsx 349 | const {isScreenReaderEnabled} = useIsScreenReaderEnabled(); 350 | ``` 351 | 352 | `useSpeak` activates a text-to-speech engine (if available) to read spoken text. 353 | ```jsx 354 | const { speak } = useSpeak(); 355 | speak({value: textToSpeak}) 356 | ``` 357 | 358 | See the [ScreenReader](https://capacitorjs.com/docs/apis/screenreader) Capacitor Plugin docs for more info on the plugin API. 359 | 360 | ## Storage Hooks 361 | 362 | Installation: 363 | 364 | ```bash 365 | npm install @capacitor-community/storage-react 366 | ``` 367 | 368 | Usage: 369 | 370 | ```jsx 371 | import { useStorage, useStorageItem, availableFeatures } from '@capacitor-community/storage-react'; 372 | ``` 373 | 374 | `useStorage` provides access to Capacitor's storage engine. There is also a helper called `useStorageItem` which makes managing a single item easy if you don't need to access the full Storage API (see below) 375 | 376 | ```jsx 377 | const { get, set, remove, getKeys, clear } = useStorage(); 378 | useEffect(() => { 379 | async function example() { 380 | const value = await get('name'); 381 | await set('name', 'Max'); 382 | await remove('name'); 383 | const allKeys = await getKeys(); 384 | await clear(); 385 | } 386 | }, [ get, set, remove, keys, clear ]); 387 | ``` 388 | 389 | #### `useStorageItem` 390 | 391 | `useStorageItem` tracks a single item and provides a nice way to read and write that item: 392 | 393 | ```jsx 394 | const [ name , setName ] = useStorageItem('name', 'Max'); 395 | 396 | // Example: 397 | const updateName = useCallback((n) => { 398 | setName(n); 399 | }, [ setName ]); 400 | ``` 401 | 402 | `useStorageItem` will use the initial value already in storage, or the one provided if there is no existing value. 403 | 404 | See the [Storage](https://capacitorjs.com/docs/apis/storage) Capacitor Plugin docs for more info on the plugin API. 405 | 406 | # Other Hooks 407 | 408 | This is an evolving project and not all of the Capacitor Plugins are supported yet. If there is one you need, feel free top open an issue for it, or better yet, submit a PR! 409 | -------------------------------------------------------------------------------- /ionic-react-hooks-gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capacitor-community/react-hooks/bfdf2b7b78651fd1f7c1ce53d4986b7b3148b0ee/ionic-react-hooks-gh.png -------------------------------------------------------------------------------- /ionic-react-hooks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capacitor-community/react-hooks/bfdf2b7b78651fd1f7c1ce53d4986b7b3148b0ee/ionic-react-hooks.png -------------------------------------------------------------------------------- /jest.setup.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capacitor-community/react-hooks/bfdf2b7b78651fd1f7c1ce53d4986b7b3148b0ee/jest.setup.js -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": ["packages/*"], 3 | "version": "independent", 4 | "command": { 5 | "bootstrap": { 6 | "hoist": true, 7 | "ci": true 8 | }, 9 | "version": { 10 | "allowBranch": "main", 11 | "conventionalCommits": true, 12 | "createRelease": "github", 13 | "message": "chore(release): Release", 14 | "tagVersionPrefix": "" 15 | }, 16 | "publish": { 17 | "verifyAccess": false 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "capacitor-react-hooks", 3 | "private": true, 4 | "description": "React Hooks for Capacitor apps", 5 | "keywords": [ 6 | "react", 7 | "capacitor", 8 | "hooks", 9 | "ionic framework", 10 | "ionic" 11 | ], 12 | "license": "MIT", 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/capacitor-community/react-hooks.git" 16 | }, 17 | "scripts": { 18 | "lerna:publish": "lerna publish from-git --dist-tag latest --yes", 19 | "lerna:version": "lerna version --conventional-commits", 20 | "prerelease": "lerna run build && lerna run lint", 21 | "release": "npm run lerna:version" 22 | }, 23 | "devDependencies": { 24 | "@capacitor/browser": "^1.0.4", 25 | "@capacitor/camera": "^1.1.1", 26 | "@capacitor/cli": "^3.2.4", 27 | "@capacitor/clipboard": "^1.0.4", 28 | "@capacitor/core": "^3.2.4", 29 | "@capacitor/device": "^1.0.4", 30 | "@capacitor/filesystem": "^1.0.4", 31 | "@capacitor/geolocation": "^1.1.0", 32 | "@capacitor/keyboard": "^1.1.0", 33 | "@capacitor/network": "^1.0.3", 34 | "@capacitor/screen-reader": "1.0.3", 35 | "@capacitor/storage": "^1.2.0", 36 | "@testing-library/react-hooks": "^7.0.2", 37 | "@types/jest": "^27.0.2", 38 | "@types/react": "^17.0.29", 39 | "@typescript-eslint/eslint-plugin": "^5.0.0", 40 | "@typescript-eslint/parser": "^5.0.0", 41 | "eslint": "^8.0.0", 42 | "eslint-config-prettier": "^8.3.0", 43 | "eslint-plugin-prettier": "^4.0.0", 44 | "jest": "^27.2.5", 45 | "lerna": "^4.0.0", 46 | "prettier": "^2.4.1", 47 | "react": "^17.0.2", 48 | "react-test-renderer": "^17.0.2", 49 | "rimraf": "^2.6.1", 50 | "rollup": "^2.58.0", 51 | "ts-jest": "^27.0.5", 52 | "typescript": "^4.4.4" 53 | }, 54 | "dependencies": { 55 | "@capacitor-community/app-react": "file:packages/app", 56 | "@capacitor-community/browser-react": "file:packages/browser", 57 | "@capacitor-community/camera-react": "file:packages/camera", 58 | "@capacitor-community/clipboard-react": "file:packages/clipboard", 59 | "@capacitor-community/device-react": "file:packages/device", 60 | "@capacitor-community/filesystem-react": "file:packages/filesystem", 61 | "@capacitor-community/geolocation-react": "file:packages/geolocation", 62 | "@capacitor-community/keyboard-react": "file:packages/keyboard", 63 | "@capacitor-community/network-react": "file:packages/network", 64 | "@capacitor-community/screen-reader-react": "file:packages/screen-reader", 65 | "@capacitor-community/storage-react": "file:packages/storage", 66 | "react-ionic-hooks-test-app": "file:packages/test-app" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /packages/app/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # 0.1.0 (2021-10-17) 7 | 8 | **Note:** Version bump only for package @capacitor-community/app-react 9 | 10 | 11 | 12 | 13 | 14 | ## [0.0.3](https://github.com/capacitor-community/react-hooks/compare/@capacitor-community/app-react@0.0.2...@capacitor-community/app-react@0.0.3) (2021-10-17) 15 | 16 | **Note:** Version bump only for package @capacitor-community/app-react 17 | 18 | 19 | 20 | 21 | 22 | ## [0.0.2](https://github.com/capacitor-community/react-hooks/compare/@capacitor-community/app-react@0.0.1...@capacitor-community/app-react@0.0.2) (2021-10-17) 23 | 24 | **Note:** Version bump only for package @capacitor-community/app-react 25 | 26 | 27 | 28 | 29 | 30 | ## 0.0.1 (2021-10-16) 31 | 32 | **Note:** Version bump only for package @capacitor-community/app-react 33 | -------------------------------------------------------------------------------- /packages/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@capacitor-community/app-react", 3 | "version": "0.1.0", 4 | "description": "React Hooks for Capacitor App Plugin", 5 | "keywords": [ 6 | "react", 7 | "capacitor", 8 | "hooks", 9 | "ionic framework", 10 | "ionic" 11 | ], 12 | "license": "MIT", 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/capacitor-community/react-hooks.git" 16 | }, 17 | "files": [ 18 | "dist/" 19 | ], 20 | "main": "dist/index.js", 21 | "scripts": { 22 | "build": "npm run clean && npm run transpile", 23 | "clean": "rimraf dist", 24 | "eslint": "eslint \"src/**/*.{ts,tsx,js,jsx}\"", 25 | "lerna:release": "", 26 | "lint": "npm run eslint", 27 | "lint.fix": "npm run eslint -- --fix", 28 | "test": "echo tests are in process", 29 | "transpile": "tsc" 30 | }, 31 | "peerDependencies": { 32 | "@capacitor/app": "*", 33 | "@capacitor/core": ">=3.0.0", 34 | "react": "*" 35 | }, 36 | "prettier": "../../.prettierrc.json", 37 | "eslintConfig": { 38 | "extends": "../../.eslintrc.json" 39 | }, 40 | "jest": { 41 | "preset": "ts-jest", 42 | "testPathIgnorePatterns": [ 43 | "node_modules", 44 | "dist-transpiled", 45 | "dist", 46 | "test-app" 47 | ], 48 | "modulePaths": [ 49 | "" 50 | ] 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/app/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useApp'; 2 | -------------------------------------------------------------------------------- /packages/app/src/useApp.test.ts: -------------------------------------------------------------------------------- 1 | jest.mock('@capacitor/core', () => { 2 | let appStateListener: any; 3 | let appUrlOpenListener: any; 4 | const isActive = true; 5 | return { 6 | Plugins: { 7 | App: { 8 | addListener: (eventName: string, cb: any) => { 9 | switch (eventName) { 10 | case 'appStateChange': 11 | appStateListener = cb; 12 | break; 13 | case 'appUrlOpen': 14 | appUrlOpenListener = cb; 15 | break; 16 | } 17 | return { 18 | remove: () => { 19 | return; 20 | }, 21 | }; 22 | }, 23 | __updateAppState: () => { 24 | appStateListener && appStateListener({ isActive: !isActive }); 25 | }, 26 | __updateAppUrlOpen: () => { 27 | appUrlOpenListener && appUrlOpenListener({ url: 'my-app://very-legal-very-cool' }); 28 | }, 29 | getLaunchUrl: async () => { 30 | return { url: 'my-app://awesome' }; 31 | }, 32 | }, 33 | }, 34 | Capacitor: { 35 | isPluginAvailable: () => true, 36 | platform: 'ios', 37 | }, 38 | }; 39 | }); 40 | 41 | import { useAppState, useAppUrlOpen, useLaunchUrl } from './useApp'; 42 | import { act, renderHook } from '@testing-library/react-hooks'; 43 | import { Plugins } from '@capacitor/core'; 44 | 45 | it('Gets app launch URL', async () => { 46 | const r = renderHook(() => useLaunchUrl()); 47 | await act(async () => { 48 | const result = r.result; 49 | const { isAvailable } = result.current; 50 | expect(isAvailable).toBe(true); 51 | await r.waitForNextUpdate(); 52 | expect(r.result.current.launchUrl).toBe('my-app://awesome'); 53 | }); 54 | }); 55 | 56 | it('Gets app open URL', async () => { 57 | const r = renderHook(() => useAppUrlOpen()); 58 | 59 | await act(async () => { 60 | const { appUrlOpen } = r.result.current; 61 | expect(appUrlOpen).toBeUndefined(); 62 | 63 | (Plugins.App as any).__updateAppUrlOpen(); 64 | }); 65 | 66 | await act(async () => { 67 | const { appUrlOpen } = r.result.current; 68 | expect(appUrlOpen).toBe('my-app://very-legal-very-cool'); 69 | }); 70 | }); 71 | 72 | it('Gets app state', async () => { 73 | const r = renderHook(() => useAppState()); 74 | await act(async () => { 75 | const result = r.result; 76 | 77 | expect(result.current.state).toBe(true); 78 | 79 | (Plugins.App as any).__updateAppState(); 80 | }); 81 | 82 | await act(async () => { 83 | const state = r.result.current; 84 | expect(state.state).toBe(false); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /packages/app/src/useApp.ts: -------------------------------------------------------------------------------- 1 | import { App, AppState, URLOpenListenerEvent } from '@capacitor/app'; 2 | import { Capacitor } from '@capacitor/core'; 3 | import { AvailableResult, notAvailable } from './util/models'; 4 | 5 | import { isFeatureAvailable } from './util/feature-check'; 6 | import { useEffect, useState } from 'react'; 7 | 8 | interface AppUrlOpenResult extends AvailableResult { 9 | appUrlOpen?: string; 10 | } 11 | interface AppStateResult extends AvailableResult { 12 | state?: boolean; 13 | } 14 | interface LaunchUrlResult extends AvailableResult { 15 | launchUrl?: string; 16 | } 17 | 18 | export const availableFeatures = { 19 | appState: isFeatureAvailable('App', 'state'), 20 | getLaunchUrl: isFeatureAvailable('App', 'getLaunchUrl'), 21 | appUrlOpen: isFeatureAvailable('App', 'appUrlOpen'), 22 | }; 23 | 24 | if (!Capacitor.isPluginAvailable('App')) { 25 | console.warn('The @capacitor/app plugin was not found, did you forget to install it?'); 26 | } 27 | 28 | export function useAppState(): AppStateResult { 29 | if (!availableFeatures.appState) { 30 | return notAvailable; 31 | } 32 | 33 | const [state, setAppState] = useState(true); 34 | 35 | useEffect(() => { 36 | const listener = App.addListener('appStateChange', async (state: AppState) => { 37 | setAppState(state.isActive); 38 | }); 39 | 40 | return () => { 41 | listener.remove(); 42 | }; 43 | }, [App, setAppState]); 44 | 45 | return { 46 | state, 47 | isAvailable: true, 48 | }; 49 | } 50 | 51 | /** 52 | * Get the URL the app was originally launched with. Note: if 53 | * you want to detect future app opens, use `useAppUrlOpen` instead, 54 | * which will stay updated. 55 | */ 56 | export function useLaunchUrl(): LaunchUrlResult { 57 | if (!availableFeatures.getLaunchUrl) { 58 | return notAvailable; 59 | } 60 | 61 | const [launchUrl, setUrl] = useState(); 62 | 63 | useEffect(() => { 64 | async function getAppLaunchUrl() { 65 | const ret = await App.getLaunchUrl(); 66 | setUrl(ret?.url); 67 | } 68 | getAppLaunchUrl(); 69 | }, [App, setUrl]); 70 | 71 | return { 72 | launchUrl, 73 | isAvailable: true, 74 | }; 75 | } 76 | 77 | export function useAppUrlOpen(): AppUrlOpenResult { 78 | if (!isFeatureAvailable('App', 'appUrlOpen')) { 79 | return notAvailable; 80 | } 81 | 82 | const [appUrlOpen, setAppUrl] = useState(); 83 | 84 | useEffect(() => { 85 | const listener = App.addListener('appUrlOpen', async (state: URLOpenListenerEvent) => { 86 | setAppUrl(state.url); 87 | }); 88 | return () => { 89 | listener.remove(); 90 | }; 91 | }, [App, setAppUrl]); 92 | 93 | return { 94 | appUrlOpen, 95 | isAvailable: true, 96 | }; 97 | } 98 | -------------------------------------------------------------------------------- /packages/app/src/util/feature-check.ts: -------------------------------------------------------------------------------- 1 | import { Capacitor } from '@capacitor/core'; 2 | import { FeatureNotAvailableError } from './models'; 3 | 4 | const allTrue = { 5 | web: true, 6 | ios: true, 7 | android: true, 8 | electron: true, 9 | }; 10 | 11 | const featureMap = { 12 | App: { 13 | state: allTrue, 14 | getLaunchUrl: { ...allTrue, web: false }, 15 | appUrlOpen: { ...allTrue, web: false }, 16 | }, 17 | }; 18 | 19 | export function isFeatureAvailable< 20 | T extends typeof featureMap, 21 | PluginKeys extends keyof NonNullable, 22 | FeatureKeys extends keyof NonNullable[PluginKeys]> 23 | >(plugin: PluginKeys, method: FeatureKeys): boolean { 24 | const isPluginAvailable = Capacitor.isPluginAvailable(plugin as string); 25 | const isFeatureSupported = (featureMap as any)[plugin][method][Capacitor.getPlatform()]; 26 | if (isPluginAvailable && !!isFeatureSupported) { 27 | return true; 28 | } 29 | return false; 30 | } 31 | 32 | export function featureNotAvailableError(): any { 33 | throw new FeatureNotAvailableError(); 34 | } 35 | -------------------------------------------------------------------------------- /packages/app/src/util/models.ts: -------------------------------------------------------------------------------- 1 | export interface AvailableResult { 2 | isAvailable: boolean; 3 | } 4 | 5 | export const notAvailable = { 6 | isAvailable: false, 7 | }; 8 | 9 | export class FeatureNotAvailableError extends Error { 10 | constructor() { 11 | super(); 12 | this.message = 13 | 'Feature not available on this platform/device. Check availability before attempting to call this method'; 14 | this.name = 'FeatureNotAvailableError'; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "outDir": "dist" 5 | }, 6 | "files": ["src/index.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/browser/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # 0.1.0 (2021-10-17) 7 | 8 | **Note:** Version bump only for package @capacitor-community/browser-react 9 | 10 | 11 | 12 | 13 | 14 | ## [0.0.3](https://github.com/capacitor-community/react-hooks/compare/@capacitor-community/browser-react@0.0.2...@capacitor-community/browser-react@0.0.3) (2021-10-17) 15 | 16 | **Note:** Version bump only for package @capacitor-community/browser-react 17 | 18 | 19 | 20 | 21 | 22 | ## [0.0.2](https://github.com/capacitor-community/react-hooks/compare/@capacitor-community/browser-react@0.0.1...@capacitor-community/browser-react@0.0.2) (2021-10-17) 23 | 24 | **Note:** Version bump only for package @capacitor-community/browser-react 25 | 26 | 27 | 28 | 29 | 30 | ## 0.0.1 (2021-10-16) 31 | 32 | **Note:** Version bump only for package @capacitor-community/browser-react 33 | -------------------------------------------------------------------------------- /packages/browser/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@capacitor-community/browser-react", 3 | "version": "0.1.0", 4 | "description": "React Hooks for Capacitor Browser plugin", 5 | "keywords": [ 6 | "react", 7 | "capacitor", 8 | "hooks", 9 | "ionic framework", 10 | "ionic" 11 | ], 12 | "license": "MIT", 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/capacitor-community/react-hooks.git" 16 | }, 17 | "files": [ 18 | "dist/" 19 | ], 20 | "main": "dist/index.js", 21 | "scripts": { 22 | "build": "npm run clean && npm run transpile", 23 | "clean": "rimraf dist", 24 | "eslint": "eslint \"src/**/*.{ts,tsx,js,jsx}\"", 25 | "lint": "npm run eslint", 26 | "lint.fix": "npm run eslint -- --fix", 27 | "test": "echo tests are in process", 28 | "transpile": "tsc" 29 | }, 30 | "peerDependencies": { 31 | "@capacitor/browser": "*", 32 | "@capacitor/core": ">=3.0.0", 33 | "react": "*" 34 | }, 35 | "prettier": "../../.prettierrc.json", 36 | "eslintConfig": { 37 | "extends": "../../.eslintrc.json" 38 | }, 39 | "jest": { 40 | "preset": "ts-jest", 41 | "testPathIgnorePatterns": [ 42 | "node_modules", 43 | "dist-transpiled", 44 | "dist", 45 | "test-app" 46 | ], 47 | "modulePaths": [ 48 | "" 49 | ] 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/browser/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useBrowser'; 2 | -------------------------------------------------------------------------------- /packages/browser/src/useBrowser.test.ts: -------------------------------------------------------------------------------- 1 | const mock = { 2 | Plugins: { 3 | Browser: { 4 | text: 'fake', 5 | open: jest.fn(), 6 | prefetch: jest.fn(), 7 | close: jest.fn(), 8 | }, 9 | }, 10 | Capacitor: { 11 | isPluginAvailable: () => true, 12 | platform: 'ios', 13 | }, 14 | }; 15 | 16 | jest.mock('@capacitor/core', () => mock); 17 | 18 | import { useOpen, useClose } from './useBrowser'; 19 | import { renderHook, act } from '@testing-library/react-hooks'; 20 | 21 | it('Opens url', async () => { 22 | const { result } = renderHook(() => useOpen()); 23 | 24 | await act(async () => { 25 | const { open, isAvailable } = result.current; 26 | expect(isAvailable).toBe(true); 27 | await open({ url: 'http://ionicframework.com' }); 28 | expect(mock.Plugins.Browser.open).toHaveBeenCalledWith({ url: 'http://ionicframework.com' }); 29 | }); 30 | jest.resetAllMocks(); 31 | }); 32 | 33 | it('Closes url', async () => { 34 | const { result } = renderHook(() => useClose()); 35 | 36 | await act(async () => { 37 | const { close, isAvailable } = result.current; 38 | expect(isAvailable).toBe(true); 39 | await close(); 40 | expect(mock.Plugins.Browser.close).toHaveBeenCalledTimes(1); 41 | }); 42 | jest.resetAllMocks(); 43 | }); 44 | -------------------------------------------------------------------------------- /packages/browser/src/useBrowser.ts: -------------------------------------------------------------------------------- 1 | import { Browser } from '@capacitor/browser'; 2 | import { Capacitor } from '@capacitor/core'; 3 | import { AvailableResult, notAvailable } from './util/models'; 4 | import { isFeatureAvailable, featureNotAvailableError } from './util/feature-check'; 5 | 6 | interface CloseResult extends AvailableResult { 7 | close: typeof Browser.close; 8 | } 9 | interface OpenResult extends AvailableResult { 10 | open: typeof Browser.open; 11 | } 12 | 13 | if (!Capacitor.isPluginAvailable('Browser')) { 14 | console.warn('The @capacitor/browser plugin was not found, did you forget to install it?'); 15 | } 16 | 17 | export const availableFeatures = { 18 | close: isFeatureAvailable('Browser', 'close'), 19 | open: isFeatureAvailable('Browser', 'open'), 20 | }; 21 | 22 | export function useClose(): CloseResult { 23 | if (!availableFeatures.close) { 24 | return { 25 | close: featureNotAvailableError, 26 | ...notAvailable, 27 | }; 28 | } 29 | 30 | return { 31 | close: Browser.close, 32 | isAvailable: true, 33 | }; 34 | } 35 | 36 | export function useOpen(): OpenResult { 37 | if (!availableFeatures.open) { 38 | return { 39 | open: featureNotAvailableError, 40 | ...notAvailable, 41 | }; 42 | } 43 | 44 | return { 45 | open: Browser.open, 46 | isAvailable: true, 47 | }; 48 | } 49 | -------------------------------------------------------------------------------- /packages/browser/src/util/feature-check.ts: -------------------------------------------------------------------------------- 1 | import { Capacitor } from '@capacitor/core'; 2 | import { FeatureNotAvailableError } from './models'; 3 | 4 | const allTrue = { 5 | web: true, 6 | ios: true, 7 | android: true, 8 | electron: true, 9 | }; 10 | 11 | const featureMap = { 12 | Browser: { 13 | open: allTrue, 14 | close: { ...allTrue, web: false }, 15 | }, 16 | }; 17 | 18 | export function isFeatureAvailable< 19 | T extends typeof featureMap, 20 | PluginKeys extends keyof NonNullable, 21 | FeatureKeys extends keyof NonNullable[PluginKeys]> 22 | >(plugin: PluginKeys, method: FeatureKeys): boolean { 23 | const isPluginAvailable = Capacitor.isPluginAvailable(plugin as string); 24 | const isFeatureSupported = (featureMap as any)[plugin][method][Capacitor.getPlatform()]; 25 | if (isPluginAvailable && !!isFeatureSupported) { 26 | return true; 27 | } 28 | return false; 29 | } 30 | 31 | export function featureNotAvailableError(): any { 32 | throw new FeatureNotAvailableError(); 33 | } 34 | -------------------------------------------------------------------------------- /packages/browser/src/util/models.ts: -------------------------------------------------------------------------------- 1 | export interface AvailableResult { 2 | isAvailable: boolean; 3 | } 4 | 5 | export const notAvailable = { 6 | isAvailable: false, 7 | }; 8 | 9 | export class FeatureNotAvailableError extends Error { 10 | constructor() { 11 | super(); 12 | this.message = 13 | 'Feature not available on this platform/device. Check availability before attempting to call this method'; 14 | this.name = 'FeatureNotAvailableError'; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/browser/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "outDir": "dist" 5 | }, 6 | "files": ["src/index.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/camera/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # 0.1.0 (2021-10-17) 7 | 8 | **Note:** Version bump only for package @capacitor-community/camera-react 9 | 10 | 11 | 12 | 13 | 14 | ## [0.0.3](https://github.com/capacitor-community/react-hooks/compare/@capacitor-community/camera-react@0.0.2...@capacitor-community/camera-react@0.0.3) (2021-10-17) 15 | 16 | **Note:** Version bump only for package @capacitor-community/camera-react 17 | 18 | 19 | 20 | 21 | 22 | ## [0.0.2](https://github.com/capacitor-community/react-hooks/compare/@capacitor-community/camera-react@0.0.1...@capacitor-community/camera-react@0.0.2) (2021-10-17) 23 | 24 | **Note:** Version bump only for package @capacitor-community/camera-react 25 | 26 | 27 | 28 | 29 | 30 | ## 0.0.1 (2021-10-16) 31 | 32 | **Note:** Version bump only for package @capacitor-community/camera-react 33 | -------------------------------------------------------------------------------- /packages/camera/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@capacitor-community/camera-react", 3 | "version": "0.1.0", 4 | "description": "React Hooks for Capacitor Camera plugin", 5 | "keywords": [ 6 | "react", 7 | "capacitor", 8 | "hooks", 9 | "ionic framework", 10 | "ionic" 11 | ], 12 | "license": "MIT", 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/capacitor-community/react-hooks.git" 16 | }, 17 | "files": [ 18 | "dist/" 19 | ], 20 | "main": "dist/index.js", 21 | "scripts": { 22 | "build": "npm run clean && npm run transpile", 23 | "clean": "rimraf dist", 24 | "eslint": "eslint \"src/**/*.{ts,tsx,js,jsx}\"", 25 | "lint": "npm run eslint", 26 | "lint.fix": "npm run eslint -- --fix", 27 | "test": "echo tests are in process", 28 | "transpile": "tsc" 29 | }, 30 | "peerDependencies": { 31 | "@capacitor/camera": "*", 32 | "@capacitor/core": ">=3.0.0", 33 | "react": "*" 34 | }, 35 | "prettier": "../../.prettierrc.json", 36 | "eslintConfig": { 37 | "extends": "../../.eslintrc.json" 38 | }, 39 | "jest": { 40 | "preset": "ts-jest", 41 | "testPathIgnorePatterns": [ 42 | "node_modules", 43 | "dist-transpiled", 44 | "dist", 45 | "test-app" 46 | ], 47 | "modulePaths": [ 48 | "" 49 | ] 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/camera/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useCamera'; 2 | -------------------------------------------------------------------------------- /packages/camera/src/useCamera.test.ts: -------------------------------------------------------------------------------- 1 | import { ImageOptions } from '@capacitor/camera'; 2 | import { useCamera } from './useCamera'; 3 | 4 | import { renderHook, act } from '@testing-library/react-hooks'; 5 | 6 | jest.mock('@capacitor/core', () => { 7 | return { 8 | Plugins: { 9 | Camera: { 10 | getPhoto: async (options: ImageOptions) => { 11 | return { 12 | base64String: 'fake', 13 | dataUrl: 'fake', 14 | exif: {}, 15 | format: 'jpeg', 16 | path: 'fake', 17 | webPath: 'fake', 18 | }; 19 | }, 20 | }, 21 | }, 22 | Capacitor: { 23 | isPluginAvailable: () => true, 24 | platform: 'ios', 25 | }, 26 | }; 27 | }); 28 | 29 | it('Gets photo', async () => { 30 | const { result } = renderHook(() => useCamera()); 31 | 32 | await act(async () => { 33 | const { getPhoto, photo, isAvailable } = result.current; 34 | 35 | expect(isAvailable).toBe(true); 36 | 37 | getPhoto({ 38 | quality: 90, 39 | allowEditing: true, 40 | resultType: 'base64' as any, 41 | }); 42 | }); 43 | 44 | await act(async () => { 45 | const { photo } = result.current; 46 | 47 | expect(photo).toMatchObject({ 48 | base64String: 'fake', 49 | dataUrl: 'fake', 50 | exif: {}, 51 | format: 'jpeg', 52 | path: 'fake', 53 | webPath: 'fake', 54 | }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /packages/camera/src/useCamera.ts: -------------------------------------------------------------------------------- 1 | import { Camera, ImageOptions, Photo } from '@capacitor/camera'; 2 | import { AvailableResult, notAvailable } from './util/models'; 3 | import { isFeatureAvailable, featureNotAvailableError } from './util/feature-check'; 4 | import { useState } from 'react'; 5 | import { Capacitor } from '@capacitor/core'; 6 | 7 | interface CameraResult extends AvailableResult { 8 | photo?: Photo; 9 | getPhoto: (options: ImageOptions) => Promise; 10 | } 11 | 12 | if (!Capacitor.isPluginAvailable('Camera')) { 13 | console.warn('The @capacitor/camera plugin was not found, did you forget to install it?'); 14 | } 15 | 16 | export const availableFeatures = { 17 | getPhoto: isFeatureAvailable('Camera', 'getPhoto'), 18 | }; 19 | 20 | export function useCamera(): CameraResult { 21 | const [photo, setPhoto] = useState(); 22 | 23 | if (!availableFeatures.getPhoto) { 24 | return { 25 | getPhoto: featureNotAvailableError, 26 | ...notAvailable, 27 | }; 28 | } 29 | 30 | async function getPhoto(options: ImageOptions) { 31 | const cameraPhoto = await Camera.getPhoto(options); 32 | setPhoto(cameraPhoto); 33 | return cameraPhoto; 34 | } 35 | 36 | return { 37 | photo, 38 | getPhoto, 39 | isAvailable: true, 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /packages/camera/src/util/feature-check.ts: -------------------------------------------------------------------------------- 1 | import { Capacitor } from '@capacitor/core'; 2 | import { FeatureNotAvailableError } from './models'; 3 | 4 | const allTrue = { 5 | web: true, 6 | ios: true, 7 | android: true, 8 | electron: true, 9 | }; 10 | 11 | const featureMap = { 12 | Camera: { 13 | getPhoto: allTrue, 14 | }, 15 | }; 16 | 17 | export function isFeatureAvailable< 18 | T extends typeof featureMap, 19 | PluginKeys extends keyof NonNullable, 20 | FeatureKeys extends keyof NonNullable[PluginKeys]> 21 | >(plugin: PluginKeys, method: FeatureKeys): boolean { 22 | const isPluginAvailable = Capacitor.isPluginAvailable(plugin as string); 23 | const isFeatureSupported = (featureMap as any)[plugin][method][Capacitor.getPlatform()]; 24 | if (isPluginAvailable && !!isFeatureSupported) { 25 | return true; 26 | } 27 | return false; 28 | } 29 | 30 | export function featureNotAvailableError(): any { 31 | throw new FeatureNotAvailableError(); 32 | } 33 | -------------------------------------------------------------------------------- /packages/camera/src/util/models.ts: -------------------------------------------------------------------------------- 1 | export interface AvailableResult { 2 | isAvailable: boolean; 3 | } 4 | 5 | export const notAvailable = { 6 | isAvailable: false, 7 | }; 8 | 9 | export class FeatureNotAvailableError extends Error { 10 | constructor() { 11 | super(); 12 | this.message = 13 | 'Feature not available on this platform/device. Check availability before attempting to call this method'; 14 | this.name = 'FeatureNotAvailableError'; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/camera/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "outDir": "dist" 5 | }, 6 | "files": ["src/index.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/clipboard/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # 0.1.0 (2021-10-17) 7 | 8 | **Note:** Version bump only for package @capacitor-community/clipboard-react 9 | 10 | 11 | 12 | 13 | 14 | ## [0.0.3](https://github.com/capacitor-community/react-hooks/compare/@capacitor-community/clipboard-react@0.0.2...@capacitor-community/clipboard-react@0.0.3) (2021-10-17) 15 | 16 | **Note:** Version bump only for package @capacitor-community/clipboard-react 17 | 18 | 19 | 20 | 21 | 22 | ## [0.0.2](https://github.com/capacitor-community/react-hooks/compare/@capacitor-community/clipboard-react@0.0.1...@capacitor-community/clipboard-react@0.0.2) (2021-10-17) 23 | 24 | **Note:** Version bump only for package @capacitor-community/clipboard-react 25 | 26 | 27 | 28 | 29 | 30 | ## 0.0.1 (2021-10-16) 31 | 32 | **Note:** Version bump only for package @capacitor-community/clipboard-react 33 | -------------------------------------------------------------------------------- /packages/clipboard/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@capacitor-community/clipboard-react", 3 | "version": "0.1.0", 4 | "description": "React Hooks for Capacitor Clipboard plugin", 5 | "keywords": [ 6 | "react", 7 | "capacitor", 8 | "hooks", 9 | "ionic framework", 10 | "ionic" 11 | ], 12 | "license": "MIT", 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/capacitor-community/react-hooks.git" 16 | }, 17 | "files": [ 18 | "dist/" 19 | ], 20 | "main": "dist/index.js", 21 | "scripts": { 22 | "build": "npm run clean && npm run transpile", 23 | "clean": "rimraf dist", 24 | "eslint": "eslint \"src/**/*.{ts,tsx,js,jsx}\"", 25 | "lint": "npm run eslint", 26 | "lint.fix": "npm run eslint -- --fix", 27 | "test": "echo tests are in process", 28 | "transpile": "tsc" 29 | }, 30 | "peerDependencies": { 31 | "@capacitor/clipboard": "*", 32 | "@capacitor/core": ">=3.0.0", 33 | "react": "*" 34 | }, 35 | "prettier": "../../.prettierrc.json", 36 | "eslintConfig": { 37 | "extends": "../../.eslintrc.json" 38 | }, 39 | "jest": { 40 | "preset": "ts-jest", 41 | "testPathIgnorePatterns": [ 42 | "node_modules", 43 | "dist-transpiled", 44 | "dist", 45 | "test-app" 46 | ], 47 | "modulePaths": [ 48 | "" 49 | ] 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/clipboard/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useClipboard'; 2 | -------------------------------------------------------------------------------- /packages/clipboard/src/useClipboard.test.ts: -------------------------------------------------------------------------------- 1 | import { useClipboard } from './useClipboard'; 2 | 3 | import { renderHook, act } from '@testing-library/react-hooks'; 4 | 5 | jest.mock('@capacitor/core', () => { 6 | let text = 'fake'; 7 | 8 | return { 9 | Plugins: { 10 | Clipboard: { 11 | text: 'fake', 12 | read: async () => { 13 | return { value: text }; 14 | }, 15 | write: async ({ string }: { string: string }) => { 16 | text = string; 17 | return {}; 18 | }, 19 | }, 20 | }, 21 | Capacitor: { 22 | isPluginAvailable: () => true, 23 | platform: 'ios', 24 | }, 25 | }; 26 | }); 27 | 28 | it('Reads clipboard data', async () => { 29 | const r = renderHook(() => useClipboard()); 30 | 31 | await act(async () => { 32 | const result = r.result; 33 | const { getValue, isAvailable } = result.current; 34 | expect(isAvailable).toBe(true); 35 | await getValue(); 36 | }); 37 | 38 | await act(async () => { 39 | const result = r.result; 40 | expect(result.current.value).toBe('fake'); 41 | }); 42 | }); 43 | 44 | it('Writes clipboard data', async () => { 45 | const { result } = renderHook(() => useClipboard()); 46 | 47 | await act(async () => { 48 | const { setValue, isAvailable } = result.current; 49 | expect(isAvailable).toBe(true); 50 | await setValue('testing'); 51 | }); 52 | 53 | await act(async () => { 54 | const { getValue } = result.current; 55 | await getValue(); 56 | }); 57 | 58 | await act(async () => { 59 | const { value: data } = result.current; 60 | expect(data).toBe('testing'); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /packages/clipboard/src/useClipboard.ts: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { Clipboard } from '@capacitor/clipboard'; 3 | import { AvailableResult, notAvailable } from './util/models'; 4 | import { isFeatureAvailable, featureNotAvailableError } from './util/feature-check'; 5 | import { Capacitor } from '@capacitor/core'; 6 | 7 | interface ClipboardResult extends AvailableResult { 8 | value?: string; 9 | getValue: () => void; 10 | setValue: (value: string) => void; 11 | } 12 | 13 | if (!Capacitor.isPluginAvailable('Clipboard')) { 14 | console.warn('The @capacitor/clipboard plugin was not found, did you forget to install it?'); 15 | } 16 | export const availableFeatures = { 17 | useClipboard: isFeatureAvailable('Clipboard', 'useClipboard'), 18 | }; 19 | 20 | export function useClipboard(): ClipboardResult { 21 | if (!availableFeatures.useClipboard) { 22 | return { 23 | getValue: featureNotAvailableError, 24 | setValue: featureNotAvailableError, 25 | ...notAvailable, 26 | }; 27 | } 28 | 29 | const [data, setData] = useState(); 30 | 31 | async function getValue() { 32 | const ret = await Clipboard.read(); 33 | 34 | setData(ret.value); 35 | } 36 | 37 | async function setValue(value: string) { 38 | await Clipboard.write({ 39 | string: value, 40 | }); 41 | } 42 | 43 | return { 44 | value: data, 45 | getValue, 46 | setValue, 47 | isAvailable: true, 48 | }; 49 | } 50 | -------------------------------------------------------------------------------- /packages/clipboard/src/util/feature-check.ts: -------------------------------------------------------------------------------- 1 | import { Capacitor } from '@capacitor/core'; 2 | import { FeatureNotAvailableError } from './models'; 3 | 4 | const allTrue = { 5 | web: true, 6 | ios: true, 7 | android: true, 8 | electron: true, 9 | }; 10 | 11 | const featureMap = { 12 | Clipboard: { 13 | useClipboard: { ...allTrue, web: 'clipboard' in navigator }, 14 | }, 15 | }; 16 | 17 | export function isFeatureAvailable< 18 | T extends typeof featureMap, 19 | PluginKeys extends keyof NonNullable, 20 | FeatureKeys extends keyof NonNullable[PluginKeys]> 21 | >(plugin: PluginKeys, method: FeatureKeys): boolean { 22 | const isPluginAvailable = Capacitor.isPluginAvailable(plugin as string); 23 | const isFeatureSupported = (featureMap as any)[plugin][method][Capacitor.getPlatform()]; 24 | if (isPluginAvailable && !!isFeatureSupported) { 25 | return true; 26 | } 27 | return false; 28 | } 29 | 30 | export function featureNotAvailableError(): any { 31 | throw new FeatureNotAvailableError(); 32 | } 33 | -------------------------------------------------------------------------------- /packages/clipboard/src/util/models.ts: -------------------------------------------------------------------------------- 1 | export interface AvailableResult { 2 | isAvailable: boolean; 3 | } 4 | 5 | export const notAvailable = { 6 | isAvailable: false, 7 | }; 8 | 9 | export class FeatureNotAvailableError extends Error { 10 | constructor() { 11 | super(); 12 | this.message = 13 | 'Feature not available on this platform/device. Check availability before attempting to call this method'; 14 | this.name = 'FeatureNotAvailableError'; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/clipboard/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "outDir": "dist" 5 | }, 6 | "files": ["src/index.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/device/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # 0.1.0 (2021-10-17) 7 | 8 | **Note:** Version bump only for package @capacitor-community/device-react 9 | 10 | 11 | 12 | 13 | 14 | ## [0.0.3](https://github.com/capacitor-community/react-hooks/compare/@capacitor-community/device-react@0.0.2...@capacitor-community/device-react@0.0.3) (2021-10-17) 15 | 16 | **Note:** Version bump only for package @capacitor-community/device-react 17 | 18 | 19 | 20 | 21 | 22 | ## [0.0.2](https://github.com/capacitor-community/react-hooks/compare/@capacitor-community/device-react@0.0.1...@capacitor-community/device-react@0.0.2) (2021-10-17) 23 | 24 | **Note:** Version bump only for package @capacitor-community/device-react 25 | 26 | 27 | 28 | 29 | 30 | ## 0.0.1 (2021-10-16) 31 | 32 | **Note:** Version bump only for package @capacitor-community/device-react 33 | -------------------------------------------------------------------------------- /packages/device/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@capacitor-community/device-react", 3 | "version": "0.1.0", 4 | "description": "React Hooks for Capacitor Device plugin", 5 | "keywords": [ 6 | "react", 7 | "capacitor", 8 | "hooks", 9 | "ionic framework", 10 | "ionic" 11 | ], 12 | "license": "MIT", 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/capacitor-community/react-hooks.git" 16 | }, 17 | "files": [ 18 | "dist/" 19 | ], 20 | "main": "dist/index.js", 21 | "scripts": { 22 | "build": "npm run clean && npm run transpile", 23 | "clean": "rimraf dist", 24 | "eslint": "eslint \"src/**/*.{ts,tsx,js,jsx}\"", 25 | "lint": "npm run eslint", 26 | "lint.fix": "npm run eslint -- --fix", 27 | "test": "echo tests are in process", 28 | "transpile": "tsc" 29 | }, 30 | "peerDependencies": { 31 | "@capacitor/core": ">=3.0.0", 32 | "@capacitor/device": "*", 33 | "react": "*" 34 | }, 35 | "prettier": "../../.prettierrc.json", 36 | "eslintConfig": { 37 | "extends": "../../.eslintrc.json" 38 | }, 39 | "jest": { 40 | "preset": "ts-jest", 41 | "testPathIgnorePatterns": [ 42 | "node_modules", 43 | "dist-transpiled", 44 | "dist", 45 | "test-app" 46 | ], 47 | "modulePaths": [ 48 | "" 49 | ] 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/device/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useDevice'; 2 | -------------------------------------------------------------------------------- /packages/device/src/useDevice.test.ts: -------------------------------------------------------------------------------- 1 | import { DeviceInfo, DeviceLanguageCodeResult } from '@capacitor/device'; 2 | 3 | jest.mock('@capacitor/core', () => { 4 | return { 5 | Plugins: { 6 | Device: { 7 | getInfo: async (): Promise => { 8 | return { 9 | operatingSystem: 'ios', 10 | diskFree: 12228108288, 11 | osVersion: '11.2', 12 | platform: 'ios', 13 | memUsed: 93851648, 14 | diskTotal: 499054952448, 15 | model: 'iPhone', 16 | manufacturer: 'Apple', 17 | isVirtual: true, 18 | } as any; 19 | }, 20 | getLanguageCode: async (): Promise => { 21 | return { 22 | value: 'en', 23 | }; 24 | }, 25 | }, 26 | }, 27 | Capacitor: { 28 | isPluginAvailable: () => true, 29 | platform: 'ios', 30 | }, 31 | }; 32 | }); 33 | 34 | import { renderHook, act } from '@testing-library/react-hooks'; 35 | import { useGetInfo, useGetLanguageCode } from './useDevice'; 36 | 37 | it('Gets device info and language code', async () => { 38 | const r = renderHook(() => useGetInfo()); 39 | 40 | await act(async () => { 41 | const result = r.result; 42 | const { isAvailable } = result.current; 43 | expect(isAvailable).toBe(true); 44 | }); 45 | 46 | await act(async () => { 47 | const result = r.result; 48 | const { info } = result.current; 49 | 50 | expect(info).toMatchObject({ 51 | diskFree: 12228108288, 52 | appVersion: '1.0.2', 53 | appBuild: '123', 54 | osVersion: '11.2', 55 | platform: 'ios', 56 | memUsed: 93851648, 57 | diskTotal: 499054952448, 58 | model: 'iPhone', 59 | manufacturer: 'Apple', 60 | uuid: '84AE7AA1-7000-4696-8A74-4FD588A4A5C7', 61 | isVirtual: true, 62 | }); 63 | }); 64 | }); 65 | 66 | it('Gets device language code', async () => { 67 | const r = renderHook(() => useGetLanguageCode()); 68 | 69 | await act(async () => { 70 | const result = r.result; 71 | const { isAvailable } = result.current; 72 | expect(isAvailable).toBe(true); 73 | }); 74 | 75 | await act(async () => { 76 | const result = r.result; 77 | const { languageCode } = result.current; 78 | expect(languageCode).toBe('en'); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /packages/device/src/useDevice.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import { Device, DeviceInfo } from '@capacitor/device'; 3 | import { AvailableResult, notAvailable } from './util/models'; 4 | import { isFeatureAvailable } from './util/feature-check'; 5 | 6 | interface GetInfoResult extends AvailableResult { 7 | info?: DeviceInfo; 8 | } 9 | interface GetLanguageCodeResult extends AvailableResult { 10 | languageCode?: string; 11 | } 12 | 13 | export const availableFeatures = { 14 | getInfo: isFeatureAvailable('Device', 'getInfo'), 15 | getLanguageCode: isFeatureAvailable('Device', 'getLanguageCode'), 16 | }; 17 | 18 | export function useGetInfo(): GetInfoResult { 19 | if (!availableFeatures.getInfo) { 20 | return notAvailable; 21 | } 22 | const [info, setInfo] = useState(); 23 | 24 | useEffect(() => { 25 | async function getInfo() { 26 | const data = await Device.getInfo(); 27 | setInfo(data); 28 | } 29 | getInfo(); 30 | }, [Device, setInfo]); 31 | 32 | return { 33 | info, 34 | isAvailable: true, 35 | }; 36 | } 37 | 38 | export function useGetLanguageCode(): GetLanguageCodeResult { 39 | if (!availableFeatures.getLanguageCode) { 40 | return notAvailable; 41 | } 42 | 43 | const [languageCode, setLanguageCode] = useState(); 44 | 45 | useEffect(() => { 46 | async function getLanguageCode() { 47 | const data = await Device.getLanguageCode(); 48 | setLanguageCode(data.value); 49 | } 50 | getLanguageCode(); 51 | }, [Device, setLanguageCode]); 52 | 53 | return { 54 | languageCode, 55 | isAvailable: true, 56 | }; 57 | } 58 | -------------------------------------------------------------------------------- /packages/device/src/util/feature-check.ts: -------------------------------------------------------------------------------- 1 | import { Capacitor } from '@capacitor/core'; 2 | import { FeatureNotAvailableError } from './models'; 3 | 4 | const allTrue = { 5 | web: true, 6 | ios: true, 7 | android: true, 8 | electron: true, 9 | }; 10 | 11 | const featureMap = { 12 | Device: { 13 | getInfo: allTrue, 14 | getLanguageCode: allTrue, 15 | }, 16 | }; 17 | 18 | export function isFeatureAvailable< 19 | T extends typeof featureMap, 20 | PluginKeys extends keyof NonNullable, 21 | FeatureKeys extends keyof NonNullable[PluginKeys]> 22 | >(plugin: PluginKeys, method: FeatureKeys): boolean { 23 | const isPluginAvailable = Capacitor.isPluginAvailable(plugin as string); 24 | const isFeatureSupported = (featureMap as any)[plugin][method][Capacitor.getPlatform()]; 25 | if (isPluginAvailable && !!isFeatureSupported) { 26 | return true; 27 | } 28 | return false; 29 | } 30 | 31 | export function featureNotAvailableError(): any { 32 | throw new FeatureNotAvailableError(); 33 | } 34 | -------------------------------------------------------------------------------- /packages/device/src/util/models.ts: -------------------------------------------------------------------------------- 1 | export interface AvailableResult { 2 | isAvailable: boolean; 3 | } 4 | 5 | export const notAvailable = { 6 | isAvailable: false, 7 | }; 8 | 9 | export class FeatureNotAvailableError extends Error { 10 | constructor() { 11 | super(); 12 | this.message = 13 | 'Feature not available on this platform/device. Check availability before attempting to call this method'; 14 | this.name = 'FeatureNotAvailableError'; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/device/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "outDir": "dist" 5 | }, 6 | "files": ["src/index.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/filesystem/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # 0.1.0 (2021-10-17) 7 | 8 | **Note:** Version bump only for package @capacitor-community/filesystem-react 9 | 10 | 11 | 12 | 13 | 14 | ## [0.0.3](https://github.com/capacitor-community/react-hooks/compare/@capacitor-community/filesystem-react@0.0.2...@capacitor-community/filesystem-react@0.0.3) (2021-10-17) 15 | 16 | **Note:** Version bump only for package @capacitor-community/filesystem-react 17 | 18 | 19 | 20 | 21 | 22 | ## [0.0.2](https://github.com/capacitor-community/react-hooks/compare/@capacitor-community/filesystem-react@0.0.1...@capacitor-community/filesystem-react@0.0.2) (2021-10-17) 23 | 24 | **Note:** Version bump only for package @capacitor-community/filesystem-react 25 | 26 | 27 | 28 | 29 | 30 | ## 0.0.1 (2021-10-16) 31 | 32 | **Note:** Version bump only for package @capacitor-community/filesystem-react 33 | -------------------------------------------------------------------------------- /packages/filesystem/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@capacitor-community/filesystem-react", 3 | "version": "0.1.0", 4 | "description": "React Hooks for Capacitor Filesystem plugin", 5 | "keywords": [ 6 | "react", 7 | "capacitor", 8 | "hooks", 9 | "ionic framework", 10 | "ionic" 11 | ], 12 | "license": "MIT", 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/capacitor-community/react-hooks.git" 16 | }, 17 | "files": [ 18 | "dist/" 19 | ], 20 | "main": "dist/index.js", 21 | "scripts": { 22 | "build": "npm run clean && npm run transpile", 23 | "clean": "rimraf dist", 24 | "eslint": "eslint \"src/**/*.{ts,tsx,js,jsx}\"", 25 | "lint": "npm run eslint", 26 | "lint.fix": "npm run eslint -- --fix", 27 | "test": "echo tests are in process", 28 | "transpile": "tsc" 29 | }, 30 | "peerDependencies": { 31 | "@capacitor/core": ">=3.0.0", 32 | "@capacitor/filesystem": "*", 33 | "react": "*" 34 | }, 35 | "prettier": "../../.prettierrc.json", 36 | "eslintConfig": { 37 | "extends": "../../.eslintrc.json" 38 | }, 39 | "jest": { 40 | "preset": "ts-jest", 41 | "testPathIgnorePatterns": [ 42 | "node_modules", 43 | "dist-transpiled", 44 | "dist", 45 | "test-app" 46 | ], 47 | "modulePaths": [ 48 | "" 49 | ] 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/filesystem/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useFileSystem'; 2 | export * from './utils'; 3 | -------------------------------------------------------------------------------- /packages/filesystem/src/useFileSystem.ts: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react'; 2 | import { 3 | Filesystem, 4 | WriteFileOptions, 5 | WriteFileResult, 6 | ReadFileOptions, 7 | ReadFileResult, 8 | GetUriOptions, 9 | GetUriResult, 10 | DeleteFileOptions, 11 | } from '@capacitor/filesystem'; 12 | 13 | import { AvailableResult } from './util/models'; 14 | import { isFeatureAvailable } from './util/feature-check'; 15 | 16 | interface FileSystemResult extends AvailableResult { 17 | getUri: (options: GetUriOptions) => Promise; 18 | deleteFile: (options: DeleteFileOptions) => Promise; 19 | readFile: (options: ReadFileOptions) => Promise; 20 | writeFile: (options: WriteFileOptions) => Promise; 21 | } 22 | 23 | export const availableFeatures = { 24 | useFileSystem: isFeatureAvailable('FileSystem', 'useFileSystem'), 25 | }; 26 | 27 | export function useFilesystem(): FileSystemResult { 28 | const getUri = useCallback(async (options: GetUriOptions) => { 29 | const result = await Filesystem.getUri(options); 30 | return result; 31 | }, []); 32 | 33 | const deleteFile = useCallback(async (options: DeleteFileOptions) => { 34 | const result = await Filesystem.deleteFile(options); 35 | return result; 36 | }, []); 37 | 38 | const readFile = useCallback(async (options: ReadFileOptions) => { 39 | const result = await Filesystem.readFile(options); 40 | return result; 41 | }, []); 42 | 43 | const writeFile = useCallback(async (options: WriteFileOptions) => { 44 | const result = await Filesystem.writeFile(options); 45 | return result; 46 | }, []); 47 | 48 | return { 49 | getUri, 50 | deleteFile, 51 | readFile, 52 | writeFile, 53 | isAvailable: true, 54 | }; 55 | } 56 | -------------------------------------------------------------------------------- /packages/filesystem/src/util/feature-check.ts: -------------------------------------------------------------------------------- 1 | import { Capacitor } from '@capacitor/core'; 2 | import { FeatureNotAvailableError } from './models'; 3 | 4 | const allTrue = { 5 | web: true, 6 | ios: true, 7 | android: true, 8 | electron: true, 9 | }; 10 | 11 | const featureMap = { 12 | FileSystem: { 13 | useFileSystem: allTrue, 14 | }, 15 | }; 16 | 17 | export function isFeatureAvailable< 18 | T extends typeof featureMap, 19 | PluginKeys extends keyof NonNullable, 20 | FeatureKeys extends keyof NonNullable[PluginKeys]> 21 | >(plugin: PluginKeys, method: FeatureKeys): boolean { 22 | const isPluginAvailable = Capacitor.isPluginAvailable(plugin as string); 23 | const isFeatureSupported = (featureMap as any)[plugin][method][Capacitor.getPlatform()]; 24 | if (isPluginAvailable && !!isFeatureSupported) { 25 | return true; 26 | } 27 | return false; 28 | } 29 | 30 | export function featureNotAvailableError(): any { 31 | throw new FeatureNotAvailableError(); 32 | } 33 | -------------------------------------------------------------------------------- /packages/filesystem/src/util/models.ts: -------------------------------------------------------------------------------- 1 | export interface AvailableResult { 2 | isAvailable: boolean; 3 | } 4 | 5 | export const notAvailable = { 6 | isAvailable: false, 7 | }; 8 | 9 | export class FeatureNotAvailableError extends Error { 10 | constructor() { 11 | super(); 12 | this.message = 13 | 'Feature not available on this platform/device. Check availability before attempting to call this method'; 14 | this.name = 'FeatureNotAvailableError'; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/filesystem/src/utils.ts: -------------------------------------------------------------------------------- 1 | export async function base64FromPath(path: string): Promise { 2 | const response = await fetch(path); 3 | const blob = await response.blob(); 4 | return new Promise((resolve, reject) => { 5 | const reader = new FileReader(); 6 | reader.onerror = reject; 7 | reader.onload = () => { 8 | if (typeof reader.result === 'string') { 9 | resolve(reader.result); 10 | } else { 11 | reject('method did not return a string'); 12 | } 13 | }; 14 | reader.readAsDataURL(blob); 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /packages/filesystem/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "outDir": "dist" 5 | }, 6 | "files": ["src/index.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/geolocation/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # 0.1.0 (2021-10-17) 7 | 8 | **Note:** Version bump only for package @capacitor-community/geolocation-react 9 | 10 | 11 | 12 | 13 | 14 | ## [0.0.3](https://github.com/capacitor-community/react-hooks/compare/@capacitor-community/geolocation-react@0.0.2...@capacitor-community/geolocation-react@0.0.3) (2021-10-17) 15 | 16 | **Note:** Version bump only for package @capacitor-community/geolocation-react 17 | 18 | 19 | 20 | 21 | 22 | ## [0.0.2](https://github.com/capacitor-community/react-hooks/compare/@capacitor-community/geolocation-react@0.0.1...@capacitor-community/geolocation-react@0.0.2) (2021-10-17) 23 | 24 | **Note:** Version bump only for package @capacitor-community/geolocation-react 25 | 26 | 27 | 28 | 29 | 30 | ## 0.0.1 (2021-10-16) 31 | 32 | **Note:** Version bump only for package @capacitor-community/geolocation-react 33 | -------------------------------------------------------------------------------- /packages/geolocation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@capacitor-community/geolocation-react", 3 | "version": "0.1.0", 4 | "description": "React Hooks for Capacitor GeoLocation plugin", 5 | "keywords": [ 6 | "react", 7 | "capacitor", 8 | "hooks", 9 | "ionic framework", 10 | "ionic" 11 | ], 12 | "license": "MIT", 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/capacitor-community/react-hooks.git" 16 | }, 17 | "files": [ 18 | "dist/" 19 | ], 20 | "main": "dist/index.js", 21 | "scripts": { 22 | "build": "npm run clean && npm run transpile", 23 | "clean": "rimraf dist", 24 | "eslint": "eslint \"src/**/*.{ts,tsx,js,jsx}\"", 25 | "lint": "npm run eslint", 26 | "lint.fix": "npm run eslint -- --fix", 27 | "test": "echo tests are in process", 28 | "transpile": "tsc" 29 | }, 30 | "peerDependencies": { 31 | "@capacitor/core": ">=3.0.0", 32 | "@capacitor/geolocation": "*", 33 | "react": "*" 34 | }, 35 | "prettier": "../../.prettierrc.json", 36 | "eslintConfig": { 37 | "extends": "../../.eslintrc.json" 38 | }, 39 | "jest": { 40 | "preset": "ts-jest", 41 | "testPathIgnorePatterns": [ 42 | "node_modules", 43 | "dist-transpiled", 44 | "dist", 45 | "test-app" 46 | ], 47 | "modulePaths": [ 48 | "" 49 | ] 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/geolocation/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useGeolocation'; 2 | -------------------------------------------------------------------------------- /packages/geolocation/src/useGeolocation.test.ts: -------------------------------------------------------------------------------- 1 | jest.mock('@capacitor/core', () => { 2 | let watchListener: any; 3 | 4 | const positions = [ 5 | [43.0664229, -89.3978106], 6 | [43.0726269, -89.3807787], 7 | [43.0639252, -89.3879085], 8 | [43.0542669, -89.3788027], 9 | ]; 10 | let position = positions[0]; 11 | let pos = 0; 12 | 13 | return { 14 | Plugins: { 15 | Geolocation: { 16 | positions: positions, 17 | __updatePosition: () => { 18 | position = positions[++pos % positions.length]; 19 | watchListener({ 20 | timestamp: 1573676975, 21 | coords: { 22 | latitude: position[0], 23 | longitude: position[1], 24 | accuracy: 1, 25 | }, 26 | }); 27 | }, 28 | clearWatch: ({ id }: { id: string }) => { 29 | return; 30 | }, 31 | getCurrentPosition: async (options: GeolocationOptions) => 32 | Promise.resolve({ 33 | timestamp: 1573676975, 34 | coords: { 35 | latitude: position[0], 36 | longitude: position[1], 37 | }, 38 | }), 39 | watchPosition: async ( 40 | options: GeolocationOptions, 41 | cb: (pos: GeolocationPosition, err: any) => void 42 | ) => { 43 | watchListener = cb; 44 | watchListener({ 45 | timestamp: 1573676975, 46 | coords: { 47 | latitude: position[0], 48 | longitude: position[1], 49 | accuracy: 1, 50 | }, 51 | }); 52 | 53 | return 'testid'; 54 | }, 55 | }, 56 | }, 57 | Capacitor: { 58 | isPluginAvailable: () => true, 59 | platform: 'ios', 60 | }, 61 | }; 62 | }); 63 | 64 | import { Geolocation, GeolocationOptions, GeolocationPosition } from '@capacitor/geolocation'; 65 | import { useWatchPosition, useCurrentPosition } from './useGeolocation'; 66 | import { renderHook, act } from '@testing-library/react-hooks'; 67 | 68 | it('Gets current geolocation watch position', async () => { 69 | const r = renderHook(() => useWatchPosition()); 70 | const geoMock = Geolocation as any; 71 | 72 | function match(pos: GeolocationPosition, coords: [number, number]) { 73 | expect(pos).toMatchObject({ 74 | coords: { 75 | latitude: coords[0], 76 | longitude: coords[1], 77 | }, 78 | }); 79 | } 80 | 81 | await act(async function () { 82 | r.result.current.startWatch(); 83 | }); 84 | 85 | await act(async function () { 86 | const { currentPosition } = r.result.current; 87 | match(currentPosition!, geoMock.positions[0]); 88 | (Geolocation as any).__updatePosition(); 89 | }); 90 | 91 | await act(async function () { 92 | const { currentPosition } = r.result.current; 93 | match(currentPosition!, geoMock.positions[1]); 94 | (Geolocation as any).__updatePosition(); 95 | }); 96 | 97 | await act(async function () { 98 | const { currentPosition } = r.result.current; 99 | match(currentPosition!, geoMock.positions[2]); 100 | (Geolocation as any).__updatePosition(); 101 | }); 102 | 103 | await act(async function () { 104 | const { currentPosition } = r.result.current; 105 | match(currentPosition!, geoMock.positions[3]); 106 | (Geolocation as any).__updatePosition(); 107 | }); 108 | }); 109 | 110 | it('Gets current geolocation position', async () => { 111 | const r = renderHook(() => useCurrentPosition()); 112 | const geoMock = Geolocation as any; 113 | 114 | function match(pos: GeolocationPosition, coords: [number, number]) { 115 | expect(pos).toMatchObject({ 116 | coords: { 117 | latitude: coords[0], 118 | longitude: coords[1], 119 | }, 120 | }); 121 | } 122 | 123 | await act(async function () { 124 | const { isAvailable } = r.result.current; 125 | expect(isAvailable).toBe(true); 126 | }); 127 | 128 | await act(async function () { 129 | const { currentPosition } = r.result.current; 130 | match(currentPosition!, geoMock.positions[0]); 131 | }); 132 | }); 133 | -------------------------------------------------------------------------------- /packages/geolocation/src/useGeolocation.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import { Geolocation, Position, PositionOptions } from '@capacitor/geolocation'; 3 | import { AvailableResult, notAvailable } from './util/models'; 4 | import { isFeatureAvailable, featureNotAvailableError } from './util/feature-check'; 5 | 6 | interface GetCurrentPositionResult extends AvailableResult { 7 | error?: any; 8 | currentPosition?: Position; 9 | getPosition: (options?: PositionOptions) => void; 10 | } 11 | 12 | interface GeoWatchPositionResult extends AvailableResult { 13 | error?: any; 14 | currentPosition?: Position; 15 | startWatch: (options?: PositionOptions) => void; 16 | clearWatch: () => void; 17 | } 18 | 19 | export const availableFeatures = { 20 | getCurrentPosition: isFeatureAvailable('Geolocation', 'getCurrentPosition'), 21 | watchPosition: isFeatureAvailable('Geolocation', 'watchPosition'), 22 | }; 23 | 24 | export function useCurrentPosition( 25 | options?: PositionOptions, 26 | manual = false 27 | ): GetCurrentPositionResult { 28 | if (!availableFeatures.getCurrentPosition) { 29 | return { 30 | getPosition: featureNotAvailableError, 31 | ...notAvailable, 32 | }; 33 | } 34 | 35 | const [currentPosition, setCurrentPosition] = useState(); 36 | const [error, setError] = useState(); 37 | 38 | const getPosition = async (newOptions?: PositionOptions) => { 39 | let called = false; 40 | 41 | // we use watchPosition here and grab the first result because getCurrentPosition currently has some issues 42 | const id = await Geolocation.watchPosition( 43 | newOptions || options || {}, 44 | (pos: Position | null, err) => { 45 | // watchPosition will sometimes fire updates quickly, 46 | // so we check here to make sure its only called one per getPosition invocation 47 | if (!called) { 48 | if (err) { 49 | setError(err); 50 | } 51 | 52 | if (pos) { 53 | setCurrentPosition(pos); 54 | called = true; 55 | // run on next tick so id will be defined 56 | setTimeout(() => Geolocation.clearWatch({ id }), 0); 57 | } 58 | } 59 | } 60 | ); 61 | }; 62 | 63 | useEffect(() => { 64 | if (!manual) getPosition(options); 65 | }, [options, manual]); 66 | 67 | return { 68 | error, 69 | currentPosition, 70 | getPosition, 71 | isAvailable: true, 72 | }; 73 | } 74 | 75 | export function useWatchPosition(): GeoWatchPositionResult { 76 | if (!availableFeatures.watchPosition) { 77 | return { 78 | clearWatch: featureNotAvailableError, 79 | startWatch: featureNotAvailableError, 80 | ...notAvailable, 81 | }; 82 | } 83 | 84 | const [currentPosition, setCurrentPosition] = useState(); 85 | const [watchId, setWatchId] = useState(''); 86 | const [error, setError] = useState(); 87 | 88 | const clearWatch = () => { 89 | if (watchId) { 90 | Geolocation.clearWatch({ id: watchId }); 91 | setWatchId(''); 92 | } 93 | }; 94 | 95 | const startWatch = async (options?: PositionOptions) => { 96 | if (!watchId) { 97 | const id = await Geolocation.watchPosition(options || {}, (pos: Position | null, err) => { 98 | if (err) { 99 | setError(err); 100 | } 101 | if (pos) { 102 | setCurrentPosition(pos); 103 | } 104 | }); 105 | setWatchId(id); 106 | } 107 | }; 108 | 109 | return { 110 | error, 111 | currentPosition, 112 | clearWatch, 113 | startWatch, 114 | isAvailable: true, 115 | }; 116 | } 117 | -------------------------------------------------------------------------------- /packages/geolocation/src/util/feature-check.ts: -------------------------------------------------------------------------------- 1 | import { Capacitor } from '@capacitor/core'; 2 | import { FeatureNotAvailableError } from './models'; 3 | 4 | const allTrue = { 5 | web: true, 6 | ios: true, 7 | android: true, 8 | electron: true, 9 | }; 10 | 11 | const featureMap = { 12 | Geolocation: { 13 | getCurrentPosition: { ...allTrue, web: 'geolocation' in navigator }, 14 | watchPosition: { ...allTrue, web: 'geolocation' in navigator }, 15 | }, 16 | }; 17 | 18 | export function isFeatureAvailable< 19 | T extends typeof featureMap, 20 | PluginKeys extends keyof NonNullable, 21 | FeatureKeys extends keyof NonNullable[PluginKeys]> 22 | >(plugin: PluginKeys, method: FeatureKeys): boolean { 23 | const isPluginAvailable = Capacitor.isPluginAvailable(plugin as string); 24 | const isFeatureSupported = (featureMap as any)[plugin][method][Capacitor.getPlatform()]; 25 | if (isPluginAvailable && !!isFeatureSupported) { 26 | return true; 27 | } 28 | return false; 29 | } 30 | 31 | export function featureNotAvailableError(): any { 32 | throw new FeatureNotAvailableError(); 33 | } 34 | -------------------------------------------------------------------------------- /packages/geolocation/src/util/models.ts: -------------------------------------------------------------------------------- 1 | export interface AvailableResult { 2 | isAvailable: boolean; 3 | } 4 | 5 | export const notAvailable = { 6 | isAvailable: false, 7 | }; 8 | 9 | export class FeatureNotAvailableError extends Error { 10 | constructor() { 11 | super(); 12 | this.message = 13 | 'Feature not available on this platform/device. Check availability before attempting to call this method'; 14 | this.name = 'FeatureNotAvailableError'; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/geolocation/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "outDir": "dist" 5 | }, 6 | "files": ["src/index.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/keyboard/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # 0.1.0 (2021-10-17) 7 | 8 | **Note:** Version bump only for package @capacitor-community/keyboard-react 9 | 10 | 11 | 12 | 13 | 14 | ## [0.0.3](https://github.com/capacitor-community/react-hooks/compare/@capacitor-community/keyboard-react@0.0.2...@capacitor-community/keyboard-react@0.0.3) (2021-10-17) 15 | 16 | **Note:** Version bump only for package @capacitor-community/keyboard-react 17 | 18 | 19 | 20 | 21 | 22 | ## [0.0.2](https://github.com/capacitor-community/react-hooks/compare/@capacitor-community/keyboard-react@0.0.1...@capacitor-community/keyboard-react@0.0.2) (2021-10-17) 23 | 24 | **Note:** Version bump only for package @capacitor-community/keyboard-react 25 | 26 | 27 | 28 | 29 | 30 | ## 0.0.1 (2021-10-16) 31 | 32 | **Note:** Version bump only for package @capacitor-community/keyboard-react 33 | -------------------------------------------------------------------------------- /packages/keyboard/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@capacitor-community/keyboard-react", 3 | "version": "0.1.0", 4 | "description": "React Hooks for Capacitor Keyboard plugin", 5 | "keywords": [ 6 | "react", 7 | "capacitor", 8 | "hooks", 9 | "ionic framework", 10 | "ionic" 11 | ], 12 | "license": "MIT", 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/capacitor-community/react-hooks.git" 16 | }, 17 | "files": [ 18 | "dist/" 19 | ], 20 | "main": "dist/index.js", 21 | "scripts": { 22 | "build": "npm run clean && npm run transpile", 23 | "clean": "rimraf dist", 24 | "eslint": "eslint \"src/**/*.{ts,tsx,js,jsx}\"", 25 | "lint": "npm run eslint", 26 | "lint.fix": "npm run eslint -- --fix", 27 | "test": "echo tests are in process", 28 | "transpile": "tsc" 29 | }, 30 | "peerDependencies": { 31 | "@capacitor/core": ">=3.0.0", 32 | "@capacitor/keyboard": "*", 33 | "react": "*" 34 | }, 35 | "prettier": "../../.prettierrc.json", 36 | "eslintConfig": { 37 | "extends": "../../.eslintrc.json" 38 | }, 39 | "jest": { 40 | "preset": "ts-jest", 41 | "testPathIgnorePatterns": [ 42 | "node_modules", 43 | "dist-transpiled", 44 | "dist", 45 | "test-app" 46 | ], 47 | "modulePaths": [ 48 | "" 49 | ] 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/keyboard/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useKeyboard'; 2 | -------------------------------------------------------------------------------- /packages/keyboard/src/useKeyboard.test.ts: -------------------------------------------------------------------------------- 1 | import { useKeyboard } from './useKeyboard'; 2 | import { renderHook, act } from '@testing-library/react-hooks'; 3 | 4 | it('Gets keyboard state', async () => { 5 | const r = renderHook(() => useKeyboard()); 6 | await act(async () => { 7 | const state = r.result; 8 | expect(state.current.isOpen).toBe(false); 9 | expect(state.current.keyboardHeight).toBe(0); 10 | 11 | const ev = new CustomEvent('ionKeyboardDidShow', { detail: { keyboardHeight: 150 } }); 12 | window.dispatchEvent(ev); 13 | }); 14 | 15 | await act(async () => { 16 | const state = r.result.current; 17 | expect(state.isOpen).toBe(true); 18 | expect(state.keyboardHeight).toBe(150); 19 | 20 | const ev = new CustomEvent('ionKeyboardDidHide'); 21 | window.dispatchEvent(ev); 22 | }); 23 | 24 | await act(async () => { 25 | const state = r.result.current; 26 | expect(state.isOpen).toBe(false); 27 | expect(state.keyboardHeight).toBe(0); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/keyboard/src/useKeyboard.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import { Keyboard, KeyboardInfo, KeyboardPlugin } from '@capacitor/keyboard'; 3 | import { AvailableResult } from './util/models'; 4 | import { Capacitor } from '@capacitor/core'; 5 | interface KeyboardResult extends AvailableResult { 6 | isOpen: boolean; 7 | keyboardHeight: number; 8 | keyboard: KeyboardPlugin; 9 | } 10 | 11 | if (!Capacitor.isPluginAvailable('Keyboard')) { 12 | console.warn('The @capacitor/keyboard plugin was not found, did you forget to install it?'); 13 | } 14 | 15 | export function useKeyboard(): KeyboardResult { 16 | const [isOpen, setIsOpen] = useState(false); 17 | const [keyboardHeight, setKeyboardHeight] = useState(0); 18 | 19 | useEffect(() => { 20 | const showCallback = (ki: KeyboardInfo) => { 21 | const keyboardHeight = ki.keyboardHeight; 22 | setIsOpen(true); 23 | setKeyboardHeight(keyboardHeight); 24 | }; 25 | const hideCallback = () => { 26 | setIsOpen(false); 27 | setKeyboardHeight(0); 28 | }; 29 | Keyboard.addListener('keyboardDidShow', showCallback); 30 | Keyboard.addListener('keyboardDidHide', hideCallback); 31 | 32 | return () => { 33 | Keyboard.removeAllListeners(); 34 | }; 35 | }, [setIsOpen, setKeyboardHeight]); 36 | 37 | return { 38 | isOpen, 39 | keyboardHeight, 40 | isAvailable: true, 41 | keyboard: Keyboard, 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /packages/keyboard/src/util/feature-check.ts: -------------------------------------------------------------------------------- 1 | import { Capacitor } from '@capacitor/core'; 2 | import { FeatureNotAvailableError } from './models'; 3 | 4 | const allTrue = { 5 | web: true, 6 | ios: true, 7 | android: true, 8 | electron: true, 9 | }; 10 | 11 | const featureMap = { 12 | Keyboard: {}, 13 | }; 14 | 15 | export function isFeatureAvailable< 16 | T extends typeof featureMap, 17 | PluginKeys extends keyof NonNullable, 18 | FeatureKeys extends keyof NonNullable[PluginKeys]> 19 | >(plugin: PluginKeys, method: FeatureKeys): boolean { 20 | const isPluginAvailable = Capacitor.isPluginAvailable(plugin as string); 21 | const isFeatureSupported = (featureMap as any)[plugin][method][Capacitor.getPlatform()]; 22 | if (isPluginAvailable && !!isFeatureSupported) { 23 | return true; 24 | } 25 | return false; 26 | } 27 | 28 | export function featureNotAvailableError(): any { 29 | throw new FeatureNotAvailableError(); 30 | } 31 | -------------------------------------------------------------------------------- /packages/keyboard/src/util/models.ts: -------------------------------------------------------------------------------- 1 | export interface AvailableResult { 2 | isAvailable: boolean; 3 | } 4 | 5 | export const notAvailable = { 6 | isAvailable: false, 7 | }; 8 | 9 | export class FeatureNotAvailableError extends Error { 10 | constructor() { 11 | super(); 12 | this.message = 13 | 'Feature not available on this platform/device. Check availability before attempting to call this method'; 14 | this.name = 'FeatureNotAvailableError'; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/keyboard/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "outDir": "dist" 5 | }, 6 | "files": ["src/index.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/network/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # 0.1.0 (2021-10-17) 7 | 8 | **Note:** Version bump only for package @capacitor-community/network-react 9 | 10 | 11 | 12 | 13 | 14 | ## [0.0.3](https://github.com/capacitor-community/react-hooks/compare/@capacitor-community/network-react@0.0.2...@capacitor-community/network-react@0.0.3) (2021-10-17) 15 | 16 | **Note:** Version bump only for package @capacitor-community/network-react 17 | 18 | 19 | 20 | 21 | 22 | ## [0.0.2](https://github.com/capacitor-community/react-hooks/compare/@capacitor-community/network-react@0.0.1...@capacitor-community/network-react@0.0.2) (2021-10-17) 23 | 24 | **Note:** Version bump only for package @capacitor-community/network-react 25 | 26 | 27 | 28 | 29 | 30 | ## 0.0.1 (2021-10-16) 31 | 32 | **Note:** Version bump only for package @capacitor-community/network-react 33 | -------------------------------------------------------------------------------- /packages/network/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@capacitor-community/network-react", 3 | "version": "0.1.0", 4 | "description": "React Hooks for Capacitor Network plugin", 5 | "keywords": [ 6 | "react", 7 | "capacitor", 8 | "hooks", 9 | "ionic framework", 10 | "ionic" 11 | ], 12 | "license": "MIT", 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/capacitor-community/react-hooks.git" 16 | }, 17 | "files": [ 18 | "dist/" 19 | ], 20 | "main": "dist/index.js", 21 | "scripts": { 22 | "build": "npm run clean && npm run transpile", 23 | "clean": "rimraf dist", 24 | "eslint": "eslint \"src/**/*.{ts,tsx,js,jsx}\"", 25 | "lint": "npm run eslint", 26 | "lint.fix": "npm run eslint -- --fix", 27 | "test": "echo tests are in process", 28 | "transpile": "tsc" 29 | }, 30 | "peerDependencies": { 31 | "@capacitor/core": ">=3.0.0", 32 | "@capacitor/network": "*", 33 | "react": "*" 34 | }, 35 | "prettier": "../../.prettierrc.json", 36 | "eslintConfig": { 37 | "extends": "../../.eslintrc.json" 38 | }, 39 | "jest": { 40 | "preset": "ts-jest", 41 | "testPathIgnorePatterns": [ 42 | "node_modules", 43 | "dist-transpiled", 44 | "dist", 45 | "test-app" 46 | ], 47 | "modulePaths": [ 48 | "" 49 | ] 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/network/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useNetwork'; 2 | -------------------------------------------------------------------------------- /packages/network/src/useNetwork.test.ts: -------------------------------------------------------------------------------- 1 | jest.mock('@capacitor/core', () => { 2 | let listener: any; 3 | const status = { 4 | connected: true, 5 | connectionType: 'wifi', 6 | }; 7 | return { 8 | Plugins: { 9 | Network: { 10 | __updateStatus: () => { 11 | status.connected = false; 12 | listener(status); 13 | }, 14 | addListener: (eventName: string, cb: (status: ConnectionStatus) => void) => { 15 | listener = cb; 16 | return { 17 | remove: () => { 18 | return; 19 | }, 20 | }; 21 | }, 22 | getStatus: async () => { 23 | return status; 24 | }, 25 | }, 26 | }, 27 | Capacitor: { 28 | isPluginAvailable: () => true, 29 | platform: 'ios', 30 | }, 31 | }; 32 | }); 33 | 34 | import { Network, ConnectionStatus } from '@capacitor/network'; 35 | 36 | import { useStatus } from './useNetwork'; 37 | import { renderHook, act } from '@testing-library/react-hooks'; 38 | 39 | it('Gets current network status', async () => { 40 | const r = renderHook(() => useStatus()); 41 | 42 | const networkMock = Network as any; 43 | 44 | await act(async function () { 45 | const { isAvailable } = r.result.current; 46 | expect(isAvailable).toBe(true); 47 | }); 48 | 49 | await act(async function () { 50 | const { networkStatus } = r.result.current; 51 | expect(networkStatus).toMatchObject({ 52 | connected: true, 53 | connectionType: 'wifi', 54 | }); 55 | 56 | networkMock.__updateStatus(); 57 | }); 58 | 59 | await act(async function () { 60 | const { networkStatus } = r.result.current; 61 | 62 | expect(networkStatus).toMatchObject({ 63 | connected: false, 64 | connectionType: 'wifi', 65 | }); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /packages/network/src/useNetwork.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import { Network, ConnectionStatus } from '@capacitor/network'; 3 | import { AvailableResult, notAvailable } from './util/models'; 4 | import { isFeatureAvailable } from './util/feature-check'; 5 | import { Capacitor } from '@capacitor/core'; 6 | 7 | interface NetworkStatusResult extends AvailableResult { 8 | networkStatus?: ConnectionStatus; 9 | } 10 | 11 | export const availableFeatures = { 12 | getStatus: isFeatureAvailable('Network', 'getStatus'), 13 | }; 14 | 15 | if (!Capacitor.isPluginAvailable('Network')) { 16 | console.warn('The @capacitor/network plugin was not found, did you forget to install it?'); 17 | } 18 | 19 | export function useStatus(): NetworkStatusResult { 20 | if (!availableFeatures.getStatus) { 21 | return notAvailable; 22 | } 23 | 24 | const [networkStatus, setStatus] = useState(); 25 | 26 | useEffect(() => { 27 | async function getStatus() { 28 | const status = await Network.getStatus(); 29 | setStatus(status); 30 | } 31 | getStatus(); 32 | }, [Network, setStatus]); 33 | 34 | useEffect(() => { 35 | const listener = Network.addListener('networkStatusChange', (status: ConnectionStatus) => { 36 | setStatus(status); 37 | }); 38 | 39 | return () => { 40 | listener.remove(); 41 | }; 42 | }, [Network, setStatus]); 43 | 44 | return { 45 | networkStatus, 46 | isAvailable: true, 47 | }; 48 | } 49 | -------------------------------------------------------------------------------- /packages/network/src/util/feature-check.ts: -------------------------------------------------------------------------------- 1 | import { Capacitor } from '@capacitor/core'; 2 | import { FeatureNotAvailableError } from './models'; 3 | 4 | const allTrue = { 5 | web: true, 6 | ios: true, 7 | android: true, 8 | electron: true, 9 | }; 10 | 11 | const featureMap = { 12 | Network: { 13 | getStatus: allTrue, 14 | }, 15 | }; 16 | 17 | export function isFeatureAvailable< 18 | T extends typeof featureMap, 19 | PluginKeys extends keyof NonNullable, 20 | FeatureKeys extends keyof NonNullable[PluginKeys]> 21 | >(plugin: PluginKeys, method: FeatureKeys): boolean { 22 | const isPluginAvailable = Capacitor.isPluginAvailable(plugin as string); 23 | const isFeatureSupported = (featureMap as any)[plugin][method][Capacitor.getPlatform()]; 24 | if (isPluginAvailable && !!isFeatureSupported) { 25 | return true; 26 | } 27 | return false; 28 | } 29 | 30 | export function featureNotAvailableError(): any { 31 | throw new FeatureNotAvailableError(); 32 | } 33 | -------------------------------------------------------------------------------- /packages/network/src/util/models.ts: -------------------------------------------------------------------------------- 1 | export interface AvailableResult { 2 | isAvailable: boolean; 3 | } 4 | 5 | export const notAvailable = { 6 | isAvailable: false, 7 | }; 8 | 9 | export class FeatureNotAvailableError extends Error { 10 | constructor() { 11 | super(); 12 | this.message = 13 | 'Feature not available on this platform/device. Check availability before attempting to call this method'; 14 | this.name = 'FeatureNotAvailableError'; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/network/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "outDir": "dist" 5 | }, 6 | "files": ["src/index.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/screen-reader/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # 0.1.0 (2021-10-17) 7 | 8 | **Note:** Version bump only for package @capacitor-community/screen-reader-react 9 | 10 | 11 | 12 | 13 | 14 | ## [0.0.3](https://github.com/capacitor-community/react-hooks/compare/@capacitor-community/screen-reader-react@0.0.2...@capacitor-community/screen-reader-react@0.0.3) (2021-10-17) 15 | 16 | **Note:** Version bump only for package @capacitor-community/screen-reader-react 17 | 18 | 19 | 20 | 21 | 22 | ## [0.0.2](https://github.com/capacitor-community/react-hooks/compare/@capacitor-community/screen-reader-react@0.0.1...@capacitor-community/screen-reader-react@0.0.2) (2021-10-17) 23 | 24 | **Note:** Version bump only for package @capacitor-community/screen-reader-react 25 | 26 | 27 | 28 | 29 | 30 | ## 0.0.1 (2021-10-16) 31 | 32 | **Note:** Version bump only for package @capacitor-community/screen-reader-react 33 | -------------------------------------------------------------------------------- /packages/screen-reader/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@capacitor-community/screen-reader-react", 3 | "version": "0.1.0", 4 | "description": "React Hooks for Capacitor ScreenReader plugin", 5 | "keywords": [ 6 | "react", 7 | "capacitor", 8 | "hooks", 9 | "ionic framework", 10 | "ionic" 11 | ], 12 | "license": "MIT", 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/capacitor-community/react-hooks.git" 16 | }, 17 | "files": [ 18 | "dist/" 19 | ], 20 | "main": "dist/index.js", 21 | "scripts": { 22 | "build": "npm run clean && npm run transpile", 23 | "clean": "rimraf dist", 24 | "eslint": "eslint \"src/**/*.{ts,tsx,js,jsx}\"", 25 | "lint": "npm run eslint", 26 | "lint.fix": "npm run eslint -- --fix", 27 | "test": "echo tests are in process", 28 | "transpile": "tsc" 29 | }, 30 | "peerDependencies": { 31 | "@capacitor/core": ">=3.0.0", 32 | "@capacitor/screen-reader": "*", 33 | "react": "*" 34 | }, 35 | "prettier": "../../.prettierrc.json", 36 | "eslintConfig": { 37 | "extends": "../../.eslintrc.json" 38 | }, 39 | "jest": { 40 | "preset": "ts-jest", 41 | "testPathIgnorePatterns": [ 42 | "node_modules", 43 | "dist-transpiled", 44 | "dist", 45 | "test-app" 46 | ], 47 | "modulePaths": [ 48 | "" 49 | ] 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/screen-reader/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useScreenReader'; 2 | export * from './util/feature-check'; 3 | export * from './util/models'; 4 | -------------------------------------------------------------------------------- /packages/screen-reader/src/useScreenReader.test.ts: -------------------------------------------------------------------------------- 1 | jest.mock('@capacitor/screen-reader', () => { 2 | let listener: any; 3 | let value = true; 4 | return { 5 | ScreenReader: { 6 | __updateStatus: () => { 7 | value = !value; 8 | 9 | listener && listener({ value }); 10 | }, 11 | isEnabled: async () => { 12 | return { value }; 13 | }, 14 | addListener(_eventName: string, cb: ({ value }: { value: boolean }) => void) { 15 | listener = cb; 16 | return { 17 | remove: () => { 18 | return; 19 | }, 20 | }; 21 | }, 22 | }, 23 | }; 24 | }); 25 | 26 | import { useIsScreenReaderEnabled } from './useScreenReader'; 27 | 28 | import { renderHook, act } from '@testing-library/react-hooks'; 29 | 30 | it('Gets screen reader status', async () => { 31 | const r = renderHook(() => useIsScreenReaderEnabled()); 32 | 33 | await act(async function () { 34 | const result = r.result; 35 | 36 | const { isScreenReaderEnabled, isAvailable } = result.current; 37 | 38 | expect(isScreenReaderEnabled).toBeUndefined(); 39 | expect(isAvailable).toBe(true); 40 | await r.waitForNextUpdate(); 41 | 42 | expect(r.result.current.isScreenReaderEnabled).toBe(true); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /packages/screen-reader/src/useScreenReader.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { ScreenReader, ScreenReaderState } from '@capacitor/screen-reader'; 3 | import { Capacitor } from '@capacitor/core'; 4 | import { featureNotAvailableError, isFeatureAvailable } from './util/feature-check'; 5 | import { AvailableResult, notAvailable } from './util/models'; 6 | 7 | interface IsScreenReaderEnabledResult extends AvailableResult { 8 | isScreenReaderEnabled?: boolean; 9 | } 10 | interface SpeakResult extends AvailableResult { 11 | speak: typeof ScreenReader.speak; 12 | } 13 | 14 | if (!Capacitor.isPluginAvailable('ScreenReader')) { 15 | console.warn('The @capacitor/screen-reader plugin was not found, did you forget to install it?'); 16 | } 17 | 18 | export const availableFeatures = { 19 | isScreenReaderAvailable: isFeatureAvailable('ScreenReader', 'isEnabled'), 20 | speak: isFeatureAvailable('ScreenReader', 'speak'), 21 | }; 22 | 23 | export function useIsScreenReaderEnabled(): IsScreenReaderEnabledResult { 24 | if (!availableFeatures.isScreenReaderAvailable) { 25 | return notAvailable; 26 | } 27 | 28 | const [isEnabled, setIsEnabled] = useState(); 29 | 30 | //set initial isEnabled state 31 | useEffect(() => { 32 | async function checkScreenReader() { 33 | const isEnabled = await ScreenReader.isEnabled(); 34 | setIsEnabled(isEnabled.value); 35 | } 36 | if (availableFeatures.isScreenReaderAvailable) { 37 | checkScreenReader(); 38 | } 39 | }, [ScreenReader, setIsEnabled]); 40 | 41 | //listen for state changes 42 | useEffect(() => { 43 | function checkScreenReader(state: ScreenReaderState) { 44 | setIsEnabled(state.value); 45 | } 46 | ScreenReader.addListener('stateChange', checkScreenReader); 47 | return () => { 48 | ScreenReader.removeAllListeners(); 49 | }; 50 | }, [ScreenReader, setIsEnabled]); 51 | 52 | return { 53 | isScreenReaderEnabled: isEnabled, 54 | isAvailable: true, 55 | }; 56 | } 57 | 58 | export function useSpeak(): SpeakResult { 59 | if (!availableFeatures.speak) { 60 | return { 61 | speak: featureNotAvailableError, 62 | ...notAvailable, 63 | }; 64 | } 65 | 66 | return { 67 | speak: ScreenReader.speak, 68 | isAvailable: true, 69 | }; 70 | } 71 | -------------------------------------------------------------------------------- /packages/screen-reader/src/util/feature-check.ts: -------------------------------------------------------------------------------- 1 | import { Capacitor } from '@capacitor/core'; 2 | import { FeatureNotAvailableError } from './models'; 3 | 4 | const allTrue = { 5 | web: true, 6 | ios: true, 7 | android: true, 8 | electron: true, 9 | }; 10 | 11 | const featureMap = { 12 | ScreenReader: { 13 | isEnabled: { ...allTrue, web: false }, 14 | speak: { 15 | web: 'speechSynthesis' in (typeof window != 'undefined' ? window : {}), 16 | ios: true, 17 | android: true, 18 | electron: true, 19 | }, 20 | }, 21 | }; 22 | 23 | export function isFeatureAvailable< 24 | T extends typeof featureMap, 25 | PluginKeys extends keyof NonNullable, 26 | FeatureKeys extends keyof NonNullable[PluginKeys]> 27 | >(plugin: PluginKeys, method: FeatureKeys): boolean { 28 | const isPluginAvailable = Capacitor.isPluginAvailable(plugin as string); 29 | const isFeatureSupported = (featureMap as any)[plugin][method][Capacitor.getPlatform()]; 30 | if (isPluginAvailable && !!isFeatureSupported) { 31 | return true; 32 | } 33 | return false; 34 | } 35 | 36 | export function featureNotAvailableError(): any { 37 | throw new FeatureNotAvailableError(); 38 | } 39 | -------------------------------------------------------------------------------- /packages/screen-reader/src/util/models.ts: -------------------------------------------------------------------------------- 1 | export interface AvailableResult { 2 | isAvailable: boolean; 3 | } 4 | 5 | export const notAvailable = { 6 | isAvailable: false, 7 | }; 8 | 9 | export class FeatureNotAvailableError extends Error { 10 | constructor() { 11 | super(); 12 | this.message = 13 | 'Feature not available on this platform/device. Check availability before attempting to call this method'; 14 | this.name = 'FeatreNotAvailableError'; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/screen-reader/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "outDir": "dist" 5 | }, 6 | "files": ["src/index.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/storage/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # 0.1.0 (2021-10-17) 7 | 8 | **Note:** Version bump only for package @capacitor-community/storage-react 9 | 10 | 11 | 12 | 13 | 14 | ## [0.0.3](https://github.com/capacitor-community/react-hooks/compare/@capacitor-community/storage-react@0.0.2...@capacitor-community/storage-react@0.0.3) (2021-10-17) 15 | 16 | **Note:** Version bump only for package @capacitor-community/storage-react 17 | 18 | 19 | 20 | 21 | 22 | ## [0.0.2](https://github.com/capacitor-community/react-hooks/compare/@capacitor-community/storage-react@0.0.1...@capacitor-community/storage-react@0.0.2) (2021-10-17) 23 | 24 | **Note:** Version bump only for package @capacitor-community/storage-react 25 | 26 | 27 | 28 | 29 | 30 | ## 0.0.1 (2021-10-16) 31 | 32 | **Note:** Version bump only for package @capacitor-community/storage-react 33 | -------------------------------------------------------------------------------- /packages/storage/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@capacitor-community/storage-react", 3 | "version": "0.1.0", 4 | "description": "React Hooks for Capacitor Storage plugin", 5 | "keywords": [ 6 | "react", 7 | "capacitor", 8 | "hooks", 9 | "ionic framework", 10 | "ionic" 11 | ], 12 | "license": "MIT", 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/capacitor-community/react-hooks.git" 16 | }, 17 | "files": [ 18 | "dist/" 19 | ], 20 | "main": "dist/index.js", 21 | "scripts": { 22 | "build": "npm run clean && npm run transpile", 23 | "clean": "rimraf dist", 24 | "eslint": "eslint \"src/**/*.{ts,tsx,js,jsx}\"", 25 | "lint": "npm run eslint", 26 | "lint.fix": "npm run eslint -- --fix", 27 | "test": "echo tests are in process", 28 | "transpile": "tsc" 29 | }, 30 | "peerDependencies": { 31 | "@capacitor/core": ">=3.0.0", 32 | "@capacitor/storage": "*", 33 | "react": "*" 34 | }, 35 | "prettier": "../../.prettierrc.json", 36 | "eslintConfig": { 37 | "extends": "../../.eslintrc.json" 38 | }, 39 | "jest": { 40 | "preset": "ts-jest", 41 | "testPathIgnorePatterns": [ 42 | "node_modules", 43 | "dist-transpiled", 44 | "dist", 45 | "test-app" 46 | ], 47 | "modulePaths": [ 48 | "" 49 | ] 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/storage/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useStorage'; 2 | -------------------------------------------------------------------------------- /packages/storage/src/useStorage.test.ts: -------------------------------------------------------------------------------- 1 | jest.mock('@capacitor/core', () => { 2 | let data: any = {}; 3 | return { 4 | Plugins: { 5 | Storage: { 6 | __init: (d: any) => { 7 | data = d; 8 | }, 9 | get: async ({ key }: { key: string }) => { 10 | return { value: data[key] }; 11 | }, 12 | set: async ({ key, value }: { key: string; value: string }): Promise => { 13 | data[key] = value; 14 | }, 15 | remove: async ({ key }: { key: string }) => { 16 | delete data[key]; 17 | }, 18 | keys: async () => { 19 | return Object.keys(data); 20 | }, 21 | clear: async () => { 22 | data = {}; 23 | }, 24 | }, 25 | }, 26 | Capacitor: { 27 | isPluginAvailable: () => true, 28 | platform: 'ios', 29 | }, 30 | }; 31 | }); 32 | 33 | import { Plugins } from '@capacitor/core'; 34 | import { renderHook, act } from '@testing-library/react-hooks'; 35 | import { useStorage, useStorageItem } from './useStorage'; 36 | 37 | it('Gets and sets storage values', async () => { 38 | const r = renderHook(() => useStorage()); 39 | 40 | await act(async () => { 41 | const result = r.result.current; 42 | const { isAvailable } = result; 43 | expect(isAvailable).toBe(true); 44 | }); 45 | 46 | await act(async () => { 47 | const result = r.result.current; 48 | 49 | const { get, set, remove, getKeys, clear } = result; 50 | 51 | await set('name', 'Max'); 52 | 53 | let name = await get('name'); 54 | expect(name).toEqual('Max'); 55 | 56 | await remove('name'); 57 | name = await get('name'); 58 | expect(name).toEqual(undefined); 59 | 60 | await set('name', 'Max'); 61 | const knownKeys = await getKeys(); 62 | expect(knownKeys).toStrictEqual(['name']); 63 | 64 | await clear(); 65 | name = await get('name'); 66 | expect(name).toEqual(undefined); 67 | }); 68 | }); 69 | 70 | it('Manages individual item', async () => { 71 | let r: any; 72 | await act(async () => { 73 | r = renderHook(() => useStorageItem('name', 'Max')); 74 | }); 75 | 76 | await act(async () => { 77 | return; 78 | }); 79 | 80 | await act(async () => { 81 | const result = r.result.current; 82 | 83 | const [value, setValue] = result; 84 | expect(value).toBe('Max'); 85 | 86 | setValue('Frank'); 87 | }); 88 | 89 | await act(async () => { 90 | const result = r.result.current; 91 | 92 | const [value] = result; 93 | expect(value).toBe('Frank'); 94 | }); 95 | }); 96 | 97 | it('Manages individual item with stored value', async () => { 98 | let r: any; 99 | 100 | const storageMock = Plugins.Storage as any; 101 | await act(async () => { 102 | storageMock.__init({ name: 'Matilda' }); 103 | }); 104 | 105 | await act(async () => { 106 | r = renderHook(() => useStorageItem('name', 'Max')); 107 | }); 108 | 109 | await act(async () => { 110 | return; 111 | }); 112 | 113 | await act(async () => { 114 | const result = r.result.current; 115 | 116 | const [value, setValue] = result; 117 | expect(value).toBe('Matilda'); 118 | 119 | setValue('Frank'); 120 | }); 121 | }); 122 | -------------------------------------------------------------------------------- /packages/storage/src/useStorage.ts: -------------------------------------------------------------------------------- 1 | // Inspired by useLocalStorage from https://usehooks.com/useLocalStorage/ 2 | import { useState, useEffect, useCallback } from 'react'; 3 | import { Storage } from '@capacitor/storage'; 4 | import { AvailableResult, notAvailable } from './util/models'; 5 | import { isFeatureAvailable, featureNotAvailableError } from './util/feature-check'; 6 | import { Capacitor } from '@capacitor/core'; 7 | 8 | interface StorageResult extends AvailableResult { 9 | get: (key: string) => Promise; 10 | set: (key: string, value: string) => Promise; 11 | remove: (key: string) => void; 12 | getKeys: () => Promise<{ keys: string[] }>; 13 | clear: () => Promise; 14 | } 15 | 16 | type StorageItemResult = [T | undefined, (value: T) => Promise, boolean]; 17 | 18 | if (!Capacitor.isPluginAvailable('Storage')) { 19 | console.warn('The @capacitor/storage plugin was not found, did you forget to install it?'); 20 | } 21 | 22 | export const availableFeatures = { 23 | useStorage: isFeatureAvailable('Storage', 'useStorage'), 24 | }; 25 | 26 | export function useStorage(): StorageResult { 27 | if (!availableFeatures.useStorage) { 28 | return { 29 | get: featureNotAvailableError, 30 | set: featureNotAvailableError, 31 | remove: featureNotAvailableError, 32 | getKeys: featureNotAvailableError, 33 | clear: featureNotAvailableError, 34 | ...notAvailable, 35 | }; 36 | } 37 | 38 | const get = useCallback(async (key: string) => { 39 | const v = await Storage.get({ key }); 40 | if (v) { 41 | return v.value; 42 | } 43 | return null; 44 | }, []); 45 | 46 | const set = useCallback((key: string, value: string) => { 47 | return Storage.set({ key, value: value }); 48 | }, []); 49 | 50 | const remove = useCallback((key: string) => { 51 | return Storage.remove({ key }); 52 | }, []); 53 | 54 | const getKeys = useCallback(() => { 55 | return Storage.keys(); 56 | }, []); 57 | 58 | const clear = useCallback(() => { 59 | return Storage.clear(); 60 | }, []); 61 | 62 | return { get, set, remove, getKeys, clear, isAvailable: true }; 63 | } 64 | 65 | export function useStorageItem(key: string, initialValue?: T): StorageItemResult { 66 | if (!availableFeatures.useStorage) { 67 | return [undefined, featureNotAvailableError, false]; 68 | } 69 | 70 | const [storedValue, setStoredValue] = useState(); 71 | 72 | useEffect(() => { 73 | async function loadValue() { 74 | try { 75 | const result = await Storage.get({ key }); 76 | if (result.value == undefined && initialValue != undefined) { 77 | result.value = 78 | typeof initialValue === 'string' ? initialValue : JSON.stringify(initialValue); 79 | setValue(result.value as any); 80 | } else { 81 | if (result.value) { 82 | setStoredValue( 83 | typeof result.value === 'string' ? result.value : JSON.parse(result.value) 84 | ); 85 | } else { 86 | setStoredValue(undefined); 87 | } 88 | } 89 | } catch (e) { 90 | return initialValue; 91 | } 92 | } 93 | loadValue(); 94 | }, [Storage, setStoredValue, initialValue, key]); 95 | 96 | const setValue = async (value: T) => { 97 | try { 98 | setStoredValue(value); 99 | await Storage.set({ key, value: typeof value === 'string' ? value : JSON.stringify(value) }); 100 | } catch (e) { 101 | console.error(e); 102 | } 103 | }; 104 | 105 | return [storedValue, setValue, true]; 106 | } 107 | -------------------------------------------------------------------------------- /packages/storage/src/util/feature-check.ts: -------------------------------------------------------------------------------- 1 | import { Capacitor } from '@capacitor/core'; 2 | import { FeatureNotAvailableError } from './models'; 3 | 4 | const allTrue = { 5 | web: true, 6 | ios: true, 7 | android: true, 8 | electron: true, 9 | }; 10 | 11 | const featureMap = { 12 | Storage: { 13 | useStorage: allTrue, 14 | }, 15 | }; 16 | 17 | export function isFeatureAvailable< 18 | T extends typeof featureMap, 19 | PluginKeys extends keyof NonNullable, 20 | FeatureKeys extends keyof NonNullable[PluginKeys]> 21 | >(plugin: PluginKeys, method: FeatureKeys): boolean { 22 | const isPluginAvailable = Capacitor.isPluginAvailable(plugin as string); 23 | const isFeatureSupported = (featureMap as any)[plugin][method][Capacitor.getPlatform()]; 24 | if (isPluginAvailable && !!isFeatureSupported) { 25 | return true; 26 | } 27 | return false; 28 | } 29 | 30 | export function featureNotAvailableError(): any { 31 | throw new FeatureNotAvailableError(); 32 | } 33 | -------------------------------------------------------------------------------- /packages/storage/src/util/models.ts: -------------------------------------------------------------------------------- 1 | export interface AvailableResult { 2 | isAvailable: boolean; 3 | } 4 | 5 | export const notAvailable = { 6 | isAvailable: false, 7 | }; 8 | 9 | export class FeatureNotAvailableError extends Error { 10 | constructor() { 11 | super(); 12 | this.message = 13 | 'Feature not available on this platform/device. Check availability before attempting to call this method'; 14 | this.name = 'FeatureNotAvailableError'; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/storage/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "outDir": "dist" 5 | }, 6 | "files": ["src/index.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/test-app/.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 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /packages/test-app/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # 0.1.0 (2021-10-17) 7 | 8 | **Note:** Version bump only for package react-ionic-hooks-test-app 9 | 10 | 11 | 12 | 13 | 14 | ## [0.0.3](https://github.com/capacitor-community/react-hooks/compare/react-ionic-hooks-test-app@0.0.2...react-ionic-hooks-test-app@0.0.3) (2021-10-17) 15 | 16 | **Note:** Version bump only for package react-ionic-hooks-test-app 17 | 18 | 19 | 20 | 21 | 22 | ## [0.0.2](https://github.com/capacitor-community/react-hooks/compare/react-ionic-hooks-test-app@0.0.1...react-ionic-hooks-test-app@0.0.2) (2021-10-17) 23 | 24 | **Note:** Version bump only for package react-ionic-hooks-test-app 25 | 26 | 27 | 28 | 29 | 30 | ## 0.0.1 (2021-10-16) 31 | 32 | **Note:** Version bump only for package react-ionic-hooks-test-app 33 | -------------------------------------------------------------------------------- /packages/test-app/capacitor.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "appId": "io.ionic.starter", 3 | "appName": "react-ionic-hooks", 4 | "bundledWebRuntime": false, 5 | "npmClient": "npm", 6 | "webDir": "build", 7 | "cordova": {} 8 | } 9 | -------------------------------------------------------------------------------- /packages/test-app/ionic.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-ionic-hooks", 3 | "integrations": { 4 | "capacitor": {} 5 | }, 6 | "type": "react" 7 | } 8 | -------------------------------------------------------------------------------- /packages/test-app/ios/.gitignore: -------------------------------------------------------------------------------- 1 | App/build 2 | App/Pods 3 | App/Podfile.lock 4 | App/App/public 5 | DerivedData 6 | xcuserdata 7 | 8 | # Cordova plugins for Capacitor 9 | capacitor-cordova-ios-plugins 10 | -------------------------------------------------------------------------------- /packages/test-app/ios/App/App.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/test-app/ios/App/App.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/test-app/ios/App/App.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /packages/test-app/ios/App/App/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Capacitor 3 | 4 | @UIApplicationMain 5 | class AppDelegate: UIResponder, UIApplicationDelegate { 6 | 7 | var window: UIWindow? 8 | 9 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 10 | // Override point for customization after application launch. 11 | return true 12 | } 13 | 14 | func applicationWillResignActive(_ application: UIApplication) { 15 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 16 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 17 | } 18 | 19 | func applicationDidEnterBackground(_ application: UIApplication) { 20 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 21 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 22 | } 23 | 24 | func applicationWillEnterForeground(_ application: UIApplication) { 25 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 26 | } 27 | 28 | func applicationDidBecomeActive(_ application: UIApplication) { 29 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 30 | } 31 | 32 | func applicationWillTerminate(_ application: UIApplication) { 33 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 34 | } 35 | 36 | func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { 37 | // Called when the app was launched with a url. Feel free to add additional processing here, 38 | // but if you want the App API to support tracking app url opens, make sure to keep this call 39 | return ApplicationDelegateProxy.shared.application(app, open: url, options: options) 40 | } 41 | 42 | func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { 43 | // Called when the app was launched with an activity, including Universal Links. 44 | // Feel free to add additional processing here, but if you want the App API to support 45 | // tracking app url opens, make sure to keep this call 46 | return ApplicationDelegateProxy.shared.application(application, continue: userActivity, restorationHandler: restorationHandler) 47 | } 48 | 49 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 50 | super.touchesBegan(touches, with: event) 51 | 52 | let statusBarRect = UIApplication.shared.statusBarFrame 53 | guard let touchPoint = event?.allTouches?.first?.location(in: self.window) else { return } 54 | 55 | if statusBarRect.contains(touchPoint) { 56 | NotificationCenter.default.post(name: .capacitorStatusBarTapped, object: nil) 57 | } 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /packages/test-app/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capacitor-community/react-hooks/bfdf2b7b78651fd1f7c1ce53d4986b7b3148b0ee/packages/test-app/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@1x.png -------------------------------------------------------------------------------- /packages/test-app/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capacitor-community/react-hooks/bfdf2b7b78651fd1f7c1ce53d4986b7b3148b0ee/packages/test-app/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x-1.png -------------------------------------------------------------------------------- /packages/test-app/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capacitor-community/react-hooks/bfdf2b7b78651fd1f7c1ce53d4986b7b3148b0ee/packages/test-app/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x.png -------------------------------------------------------------------------------- /packages/test-app/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capacitor-community/react-hooks/bfdf2b7b78651fd1f7c1ce53d4986b7b3148b0ee/packages/test-app/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@3x.png -------------------------------------------------------------------------------- /packages/test-app/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capacitor-community/react-hooks/bfdf2b7b78651fd1f7c1ce53d4986b7b3148b0ee/packages/test-app/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@1x.png -------------------------------------------------------------------------------- /packages/test-app/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capacitor-community/react-hooks/bfdf2b7b78651fd1f7c1ce53d4986b7b3148b0ee/packages/test-app/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x-1.png -------------------------------------------------------------------------------- /packages/test-app/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capacitor-community/react-hooks/bfdf2b7b78651fd1f7c1ce53d4986b7b3148b0ee/packages/test-app/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x.png -------------------------------------------------------------------------------- /packages/test-app/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capacitor-community/react-hooks/bfdf2b7b78651fd1f7c1ce53d4986b7b3148b0ee/packages/test-app/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@3x.png -------------------------------------------------------------------------------- /packages/test-app/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capacitor-community/react-hooks/bfdf2b7b78651fd1f7c1ce53d4986b7b3148b0ee/packages/test-app/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@1x.png -------------------------------------------------------------------------------- /packages/test-app/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capacitor-community/react-hooks/bfdf2b7b78651fd1f7c1ce53d4986b7b3148b0ee/packages/test-app/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x-1.png -------------------------------------------------------------------------------- /packages/test-app/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capacitor-community/react-hooks/bfdf2b7b78651fd1f7c1ce53d4986b7b3148b0ee/packages/test-app/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x.png -------------------------------------------------------------------------------- /packages/test-app/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capacitor-community/react-hooks/bfdf2b7b78651fd1f7c1ce53d4986b7b3148b0ee/packages/test-app/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@3x.png -------------------------------------------------------------------------------- /packages/test-app/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capacitor-community/react-hooks/bfdf2b7b78651fd1f7c1ce53d4986b7b3148b0ee/packages/test-app/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png -------------------------------------------------------------------------------- /packages/test-app/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capacitor-community/react-hooks/bfdf2b7b78651fd1f7c1ce53d4986b7b3148b0ee/packages/test-app/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@2x.png -------------------------------------------------------------------------------- /packages/test-app/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capacitor-community/react-hooks/bfdf2b7b78651fd1f7c1ce53d4986b7b3148b0ee/packages/test-app/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@3x.png -------------------------------------------------------------------------------- /packages/test-app/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capacitor-community/react-hooks/bfdf2b7b78651fd1f7c1ce53d4986b7b3148b0ee/packages/test-app/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@1x.png -------------------------------------------------------------------------------- /packages/test-app/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capacitor-community/react-hooks/bfdf2b7b78651fd1f7c1ce53d4986b7b3148b0ee/packages/test-app/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@2x.png -------------------------------------------------------------------------------- /packages/test-app/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capacitor-community/react-hooks/bfdf2b7b78651fd1f7c1ce53d4986b7b3148b0ee/packages/test-app/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5x83.5@2x.png -------------------------------------------------------------------------------- /packages/test-app/ios/App/App/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "AppIcon-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "AppIcon-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "AppIcon-29x29@2x-1.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "AppIcon-29x29@3x.png", 25 | "scale" : "3x" 26 | }, 27 | { 28 | "size" : "40x40", 29 | "idiom" : "iphone", 30 | "filename" : "AppIcon-40x40@2x.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "AppIcon-40x40@3x.png", 37 | "scale" : "3x" 38 | }, 39 | { 40 | "size" : "60x60", 41 | "idiom" : "iphone", 42 | "filename" : "AppIcon-60x60@2x.png", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "AppIcon-60x60@3x.png", 49 | "scale" : "3x" 50 | }, 51 | { 52 | "size" : "20x20", 53 | "idiom" : "ipad", 54 | "filename" : "AppIcon-20x20@1x.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "AppIcon-20x20@2x-1.png", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "size" : "29x29", 65 | "idiom" : "ipad", 66 | "filename" : "AppIcon-29x29@1x.png", 67 | "scale" : "1x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "AppIcon-29x29@2x.png", 73 | "scale" : "2x" 74 | }, 75 | { 76 | "size" : "40x40", 77 | "idiom" : "ipad", 78 | "filename" : "AppIcon-40x40@1x.png", 79 | "scale" : "1x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "AppIcon-40x40@2x-1.png", 85 | "scale" : "2x" 86 | }, 87 | { 88 | "size" : "76x76", 89 | "idiom" : "ipad", 90 | "filename" : "AppIcon-76x76@1x.png", 91 | "scale" : "1x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "AppIcon-76x76@2x.png", 97 | "scale" : "2x" 98 | }, 99 | { 100 | "size" : "83.5x83.5", 101 | "idiom" : "ipad", 102 | "filename" : "AppIcon-83.5x83.5@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "1024x1024", 107 | "idiom" : "ios-marketing", 108 | "filename" : "AppIcon-512@2x.png", 109 | "scale" : "1x" 110 | } 111 | ], 112 | "info" : { 113 | "version" : 1, 114 | "author" : "xcode" 115 | } 116 | } -------------------------------------------------------------------------------- /packages/test-app/ios/App/App/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /packages/test-app/ios/App/App/Assets.xcassets/Splash.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "splash-2732x2732-2.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "splash-2732x2732-1.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "splash-2732x2732.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /packages/test-app/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capacitor-community/react-hooks/bfdf2b7b78651fd1f7c1ce53d4986b7b3148b0ee/packages/test-app/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-1.png -------------------------------------------------------------------------------- /packages/test-app/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capacitor-community/react-hooks/bfdf2b7b78651fd1f7c1ce53d4986b7b3148b0ee/packages/test-app/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-2.png -------------------------------------------------------------------------------- /packages/test-app/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capacitor-community/react-hooks/bfdf2b7b78651fd1f7c1ce53d4986b7b3148b0ee/packages/test-app/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732.png -------------------------------------------------------------------------------- /packages/test-app/ios/App/App/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /packages/test-app/ios/App/App/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /packages/test-app/ios/App/App/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | react-ionic-hooks 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | NSAllowsArbitraryLoads 28 | 29 | 30 | UILaunchStoryboardName 31 | LaunchScreen 32 | UIMainStoryboardFile 33 | Main 34 | UIRequiredDeviceCapabilities 35 | 36 | armv7 37 | 38 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UISupportedInterfaceOrientations~ipad 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationPortraitUpsideDown 48 | UIInterfaceOrientationLandscapeLeft 49 | UIInterfaceOrientationLandscapeRight 50 | 51 | NSCameraUsageDescription 52 | to take a pic 53 | NSPhotoLibraryAddUsageDescription 54 | to look at your pics 55 | UIViewControllerBasedStatusBarAppearance 56 | 57 | NSPhotoLibraryUsageDescription 58 | moar pics 59 | NSLocationAlwaysAndWhenInUseUsageDescription 60 | know where you are at always and now 61 | NSLocationWhenInUseUsageDescription 62 | know where you are at now and forever 63 | NSLocationAlwaysUsageDescription 64 | know where you are at always 65 | 66 | 67 | -------------------------------------------------------------------------------- /packages/test-app/ios/App/App/capacitor.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "appId": "io.ionic.starter", 3 | "appName": "react-ionic-hooks", 4 | "bundledWebRuntime": false, 5 | "npmClient": "npm", 6 | "webDir": "build", 7 | "cordova": {} 8 | } 9 | -------------------------------------------------------------------------------- /packages/test-app/ios/App/App/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/test-app/ios/App/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '12.0' 2 | use_frameworks! 3 | 4 | # workaround to avoid Xcode caching of Pods that requires 5 | # Product -> Clean Build Folder after new Cordova plugins installed 6 | # Requires CocoaPods 1.6 or newer 7 | install! 'cocoapods', :disable_input_output_paths => true 8 | 9 | def capacitor_pods 10 | pod 'Capacitor', :path => '../../../../node_modules/@capacitor/ios' 11 | pod 'CapacitorCordova', :path => '../../../../node_modules/@capacitor/ios' 12 | pod 'CapacitorApp', :path => '../../../../node_modules/@capacitor/app' 13 | pod 'CapacitorBrowser', :path => '../../../../node_modules/@capacitor/browser' 14 | pod 'CapacitorCamera', :path => '../../../../node_modules/@capacitor/camera' 15 | pod 'CapacitorClipboard', :path => '../../../../node_modules/@capacitor/clipboard' 16 | pod 'CapacitorDevice', :path => '../../../../node_modules/@capacitor/device' 17 | pod 'CapacitorFilesystem', :path => '../../../../node_modules/@capacitor/filesystem' 18 | pod 'CapacitorGeolocation', :path => '../../../../node_modules/@capacitor/geolocation' 19 | pod 'CapacitorKeyboard', :path => '../../../../node_modules/@capacitor/keyboard' 20 | pod 'CapacitorNetwork', :path => '../../../../node_modules/@capacitor/network' 21 | pod 'CapacitorScreenReader', :path => '../../../../node_modules/@capacitor/screen-reader' 22 | pod 'CapacitorStorage', :path => '../../../../node_modules/@capacitor/storage' 23 | end 24 | 25 | target 'App' do 26 | capacitor_pods 27 | # Add your Pods here 28 | end 29 | -------------------------------------------------------------------------------- /packages/test-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-ionic-hooks-test-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@capacitor-community/app-react": "file:../app", 7 | "@capacitor-community/browser-react": "file:../browser", 8 | "@capacitor-community/camera-react": "file:../camera", 9 | "@capacitor-community/clipboard-react": "file:../clipboard", 10 | "@capacitor-community/device-react": "file:../device", 11 | "@capacitor-community/filesystem-react": "file:../filesystem", 12 | "@capacitor-community/geolocation-react": "file:../geolocation", 13 | "@capacitor-community/keyboard-react": "file:../keyboard", 14 | "@capacitor-community/network-react": "file:../network", 15 | "@capacitor-community/screen-reader-react": "file:../screen-reader", 16 | "@capacitor-community/storage-react": "file:../storage", 17 | "@capacitor/app": "^1.0.4", 18 | "@capacitor/browser": "^1.0.4", 19 | "@capacitor/camera": "^1.1.1", 20 | "@capacitor/clipboard": "^1.0.4", 21 | "@capacitor/core": "^3.2.4", 22 | "@capacitor/device": "^1.0.4", 23 | "@capacitor/filesystem": "^1.0.4", 24 | "@capacitor/geolocation": "^1.1.0", 25 | "@capacitor/ios": "^3.2.4", 26 | "@capacitor/keyboard": "^1.1.0", 27 | "@capacitor/network": "^1.0.3", 28 | "@capacitor/screen-reader": "^1.0.3", 29 | "@capacitor/storage": "^1.2.0", 30 | "@ionic/pwa-elements": "^3.0.2", 31 | "@ionic/react": "^5.8.4", 32 | "@ionic/react-router": "^5.8.4", 33 | "@types/jest": "^24.0.18", 34 | "@types/node": "^12.7.12", 35 | "@types/react": "^17.0.29", 36 | "@types/react-dom": "^17.0.9", 37 | "@types/react-router": "^5.1.1", 38 | "@types/react-router-dom": "^5.1.0", 39 | "ionicons": "^5.5.3", 40 | "react": "17.0.2", 41 | "react-dom": "17.0.2", 42 | "react-router": "^5.1.0", 43 | "react-router-dom": "^5.1.0", 44 | "react-scripts": "^4.0.3", 45 | "typescript": "^4.4.4" 46 | }, 47 | "scripts": { 48 | "start": "SKIP_PREFLIGHT_CHECK=true react-scripts start", 49 | "build": "CI=false SKIP_PREFLIGHT_CHECK=true react-scripts build", 50 | "test": "SKIP_PREFLIGHT_CHECK=true react-scripts test", 51 | "eject": "react-scripts eject" 52 | }, 53 | "browserslist": { 54 | "production": [ 55 | ">0.2%", 56 | "not dead", 57 | "not op_mini all" 58 | ], 59 | "development": [ 60 | "last 1 chrome version", 61 | "last 1 firefox version", 62 | "last 1 safari version" 63 | ] 64 | }, 65 | "description": "An Ionic project" 66 | } 67 | -------------------------------------------------------------------------------- /packages/test-app/public/assets/icon/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capacitor-community/react-hooks/bfdf2b7b78651fd1f7c1ce53d4986b7b3148b0ee/packages/test-app/public/assets/icon/favicon.png -------------------------------------------------------------------------------- /packages/test-app/public/assets/shapes.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /packages/test-app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capacitor-community/react-hooks/bfdf2b7b78651fd1f7c1ce53d4986b7b3148b0ee/packages/test-app/public/favicon.ico -------------------------------------------------------------------------------- /packages/test-app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ionic App 6 | 7 | 8 | 9 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /packages/test-app/scripts/build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'production'; 5 | process.env.NODE_ENV = 'production'; 6 | 7 | // Makes the script crash on unhandled rejections instead of silently 8 | // ignoring them. In the future, promise rejections that are not handled will 9 | // terminate the Node.js process with a non-zero exit code. 10 | process.on('unhandledRejection', err => { 11 | throw err; 12 | }); 13 | 14 | // Ensure environment variables are read. 15 | require('../config/env'); 16 | 17 | 18 | const path = require('path'); 19 | const chalk = require('react-dev-utils/chalk'); 20 | const fs = require('fs-extra'); 21 | const webpack = require('webpack'); 22 | const configFactory = require('../config/webpack.config'); 23 | const paths = require('../config/paths'); 24 | const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); 25 | const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'); 26 | const printHostingInstructions = require('react-dev-utils/printHostingInstructions'); 27 | const FileSizeReporter = require('react-dev-utils/FileSizeReporter'); 28 | const printBuildError = require('react-dev-utils/printBuildError'); 29 | 30 | const measureFileSizesBeforeBuild = 31 | FileSizeReporter.measureFileSizesBeforeBuild; 32 | const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild; 33 | const useYarn = fs.existsSync(paths.yarnLockFile); 34 | 35 | // These sizes are pretty large. We'll warn for bundles exceeding them. 36 | const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024; 37 | const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024; 38 | 39 | const isInteractive = process.stdout.isTTY; 40 | 41 | // Warn and crash if required files are missing 42 | if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { 43 | process.exit(1); 44 | } 45 | 46 | // Generate configuration 47 | const config = configFactory('production'); 48 | 49 | // We require that you explicitly set browsers and do not fall back to 50 | // browserslist defaults. 51 | const { checkBrowsers } = require('react-dev-utils/browsersHelper'); 52 | checkBrowsers(paths.appPath, isInteractive) 53 | .then(() => { 54 | // First, read the current file sizes in build directory. 55 | // This lets us display how much they changed later. 56 | return measureFileSizesBeforeBuild(paths.appBuild); 57 | }) 58 | .then(previousFileSizes => { 59 | // Remove all content but keep the directory so that 60 | // if you're in it, you don't end up in Trash 61 | fs.emptyDirSync(paths.appBuild); 62 | // Merge with the public folder 63 | copyPublicFolder(); 64 | // Start the webpack build 65 | return build(previousFileSizes); 66 | }) 67 | .then( 68 | ({ stats, previousFileSizes, warnings }) => { 69 | if (warnings.length) { 70 | console.log(chalk.yellow('Compiled with warnings.\n')); 71 | console.log(warnings.join('\n\n')); 72 | console.log( 73 | '\nSearch for the ' + 74 | chalk.underline(chalk.yellow('keywords')) + 75 | ' to learn more about each warning.' 76 | ); 77 | console.log( 78 | 'To ignore, add ' + 79 | chalk.cyan('// eslint-disable-next-line') + 80 | ' to the line before.\n' 81 | ); 82 | } else { 83 | console.log(chalk.green('Compiled successfully.\n')); 84 | } 85 | 86 | console.log('File sizes after gzip:\n'); 87 | printFileSizesAfterBuild( 88 | stats, 89 | previousFileSizes, 90 | paths.appBuild, 91 | WARN_AFTER_BUNDLE_GZIP_SIZE, 92 | WARN_AFTER_CHUNK_GZIP_SIZE 93 | ); 94 | console.log(); 95 | 96 | const appPackage = require(paths.appPackageJson); 97 | const publicUrl = paths.publicUrl; 98 | const publicPath = config.output.publicPath; 99 | const buildFolder = path.relative(process.cwd(), paths.appBuild); 100 | printHostingInstructions( 101 | appPackage, 102 | publicUrl, 103 | publicPath, 104 | buildFolder, 105 | useYarn 106 | ); 107 | }, 108 | err => { 109 | const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true'; 110 | if (tscCompileOnError) { 111 | console.log(chalk.yellow( 112 | 'Compiled with the following type errors (you may want to check these before deploying your app):\n' 113 | )); 114 | printBuildError(err); 115 | } else { 116 | console.log(chalk.red('Failed to compile.\n')); 117 | printBuildError(err); 118 | process.exit(1); 119 | } 120 | } 121 | ) 122 | .catch(err => { 123 | if (err && err.message) { 124 | console.log(err.message); 125 | } 126 | process.exit(1); 127 | }); 128 | 129 | // Create the production build and print the deployment instructions. 130 | function build(previousFileSizes) { 131 | // We used to support resolving modules according to `NODE_PATH`. 132 | // This now has been deprecated in favor of jsconfig/tsconfig.json 133 | // This lets you use absolute paths in imports inside large monorepos: 134 | if (process.env.NODE_PATH) { 135 | console.log( 136 | chalk.yellow( 137 | 'Setting NODE_PATH to resolve modules absolutely has been deprecated in favor of setting baseUrl in jsconfig.json (or tsconfig.json if you are using TypeScript) and will be removed in a future major release of create-react-app.' 138 | ) 139 | ); 140 | console.log(); 141 | } 142 | 143 | console.log('Creating an optimized production build...'); 144 | 145 | const compiler = webpack(config); 146 | return new Promise((resolve, reject) => { 147 | compiler.run((err, stats) => { 148 | let messages; 149 | if (err) { 150 | if (!err.message) { 151 | return reject(err); 152 | } 153 | messages = formatWebpackMessages({ 154 | errors: [err.message], 155 | warnings: [], 156 | }); 157 | } else { 158 | messages = formatWebpackMessages( 159 | stats.toJson({ all: false, warnings: true, errors: true }) 160 | ); 161 | } 162 | if (messages.errors.length) { 163 | // Only keep the first error. Others are often indicative 164 | // of the same problem, but confuse the reader with noise. 165 | if (messages.errors.length > 1) { 166 | messages.errors.length = 1; 167 | } 168 | return reject(new Error(messages.errors.join('\n\n'))); 169 | } 170 | if ( 171 | process.env.CI && 172 | (typeof process.env.CI !== 'string' || 173 | process.env.CI.toLowerCase() !== 'false') && 174 | messages.warnings.length 175 | ) { 176 | console.log( 177 | chalk.yellow( 178 | '\nTreating warnings as errors because process.env.CI = true.\n' + 179 | 'Most CI servers set it automatically.\n' 180 | ) 181 | ); 182 | return reject(new Error(messages.warnings.join('\n\n'))); 183 | } 184 | 185 | return resolve({ 186 | stats, 187 | previousFileSizes, 188 | warnings: messages.warnings, 189 | }); 190 | }); 191 | }); 192 | } 193 | 194 | function copyPublicFolder() { 195 | fs.copySync(paths.appPublic, paths.appBuild, { 196 | dereference: true, 197 | filter: file => file !== paths.appHtml, 198 | }); 199 | } 200 | -------------------------------------------------------------------------------- /packages/test-app/scripts/start.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'development'; 5 | process.env.NODE_ENV = 'development'; 6 | 7 | // Makes the script crash on unhandled rejections instead of silently 8 | // ignoring them. In the future, promise rejections that are not handled will 9 | // terminate the Node.js process with a non-zero exit code. 10 | process.on('unhandledRejection', err => { 11 | throw err; 12 | }); 13 | 14 | // Ensure environment variables are read. 15 | require('../config/env'); 16 | 17 | 18 | const fs = require('fs'); 19 | const chalk = require('react-dev-utils/chalk'); 20 | const webpack = require('webpack'); 21 | const WebpackDevServer = require('webpack-dev-server'); 22 | const clearConsole = require('react-dev-utils/clearConsole'); 23 | const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); 24 | const { 25 | choosePort, 26 | createCompiler, 27 | prepareProxy, 28 | prepareUrls, 29 | } = require('react-dev-utils/WebpackDevServerUtils'); 30 | const openBrowser = require('react-dev-utils/openBrowser'); 31 | const paths = require('../config/paths'); 32 | const configFactory = require('../config/webpack.config'); 33 | const createDevServerConfig = require('../config/webpackDevServer.config'); 34 | 35 | const useYarn = fs.existsSync(paths.yarnLockFile); 36 | const isInteractive = process.stdout.isTTY; 37 | 38 | // Warn and crash if required files are missing 39 | if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { 40 | process.exit(1); 41 | } 42 | 43 | // Tools like Cloud9 rely on this. 44 | const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000; 45 | const HOST = process.env.HOST || '0.0.0.0'; 46 | 47 | if (process.env.HOST) { 48 | console.log( 49 | chalk.cyan( 50 | `Attempting to bind to HOST environment variable: ${chalk.yellow( 51 | chalk.bold(process.env.HOST) 52 | )}` 53 | ) 54 | ); 55 | console.log( 56 | `If this was unintentional, check that you haven't mistakenly set it in your shell.` 57 | ); 58 | console.log( 59 | `Learn more here: ${chalk.yellow('https://bit.ly/CRA-advanced-config')}` 60 | ); 61 | console.log(); 62 | } 63 | 64 | // We require that you explicitly set browsers and do not fall back to 65 | // browserslist defaults. 66 | const { checkBrowsers } = require('react-dev-utils/browsersHelper'); 67 | checkBrowsers(paths.appPath, isInteractive) 68 | .then(() => { 69 | // We attempt to use the default port but if it is busy, we offer the user to 70 | // run on a different port. `choosePort()` Promise resolves to the next free port. 71 | return choosePort(HOST, DEFAULT_PORT); 72 | }) 73 | .then(port => { 74 | if (port == null) { 75 | // We have not found a port. 76 | return; 77 | } 78 | const config = configFactory('development'); 79 | const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; 80 | const appName = require(paths.appPackageJson).name; 81 | const useTypeScript = fs.existsSync(paths.appTsConfig); 82 | const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true'; 83 | const urls = prepareUrls(protocol, HOST, port); 84 | const devSocket = { 85 | warnings: warnings => 86 | devServer.sockWrite(devServer.sockets, 'warnings', warnings), 87 | errors: errors => 88 | devServer.sockWrite(devServer.sockets, 'errors', errors), 89 | }; 90 | // Create a webpack compiler that is configured with custom messages. 91 | const compiler = createCompiler({ 92 | appName, 93 | config, 94 | devSocket, 95 | urls, 96 | useYarn, 97 | useTypeScript, 98 | tscCompileOnError, 99 | webpack, 100 | }); 101 | // Load proxy config 102 | const proxySetting = require(paths.appPackageJson).proxy; 103 | const proxyConfig = prepareProxy(proxySetting, paths.appPublic); 104 | // Serve webpack assets generated by the compiler over a web server. 105 | const serverConfig = createDevServerConfig( 106 | proxyConfig, 107 | urls.lanUrlForConfig 108 | ); 109 | const devServer = new WebpackDevServer(compiler, serverConfig); 110 | // Launch WebpackDevServer. 111 | devServer.listen(port, HOST, err => { 112 | if (err) { 113 | return console.log(err); 114 | } 115 | if (isInteractive) { 116 | clearConsole(); 117 | } 118 | 119 | // We used to support resolving modules according to `NODE_PATH`. 120 | // This now has been deprecated in favor of jsconfig/tsconfig.json 121 | // This lets you use absolute paths in imports inside large monorepos: 122 | if (process.env.NODE_PATH) { 123 | console.log( 124 | chalk.yellow( 125 | 'Setting NODE_PATH to resolve modules absolutely has been deprecated in favor of setting baseUrl in jsconfig.json (or tsconfig.json if you are using TypeScript) and will be removed in a future major release of create-react-app.' 126 | ) 127 | ); 128 | console.log(); 129 | } 130 | 131 | console.log(chalk.cyan('Starting the development server...\n')); 132 | openBrowser(urls.localUrlForBrowser); 133 | }); 134 | 135 | ['SIGINT', 'SIGTERM'].forEach(function(sig) { 136 | process.on(sig, function() { 137 | devServer.close(); 138 | process.exit(); 139 | }); 140 | }); 141 | }) 142 | .catch(err => { 143 | if (err && err.message) { 144 | console.log(err.message); 145 | } 146 | process.exit(1); 147 | }); 148 | -------------------------------------------------------------------------------- /packages/test-app/scripts/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'test'; 5 | process.env.NODE_ENV = 'test'; 6 | process.env.PUBLIC_URL = ''; 7 | 8 | // Makes the script crash on unhandled rejections instead of silently 9 | // ignoring them. In the future, promise rejections that are not handled will 10 | // terminate the Node.js process with a non-zero exit code. 11 | process.on('unhandledRejection', err => { 12 | throw err; 13 | }); 14 | 15 | // Ensure environment variables are read. 16 | require('../config/env'); 17 | 18 | 19 | const jest = require('jest'); 20 | const execSync = require('child_process').execSync; 21 | let argv = process.argv.slice(2); 22 | 23 | function isInGitRepository() { 24 | try { 25 | execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' }); 26 | return true; 27 | } catch (e) { 28 | return false; 29 | } 30 | } 31 | 32 | function isInMercurialRepository() { 33 | try { 34 | execSync('hg --cwd . root', { stdio: 'ignore' }); 35 | return true; 36 | } catch (e) { 37 | return false; 38 | } 39 | } 40 | 41 | // Watch unless on CI or explicitly running all tests 42 | if ( 43 | !process.env.CI && 44 | argv.indexOf('--watchAll') === -1 && 45 | argv.indexOf('--watchAll=false') === -1 46 | ) { 47 | // https://github.com/facebook/create-react-app/issues/5210 48 | const hasSourceControl = isInGitRepository() || isInMercurialRepository(); 49 | argv.push(hasSourceControl ? '--watch' : '--watchAll'); 50 | } 51 | 52 | 53 | jest.run(argv); 54 | -------------------------------------------------------------------------------- /packages/test-app/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /packages/test-app/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Redirect, Route } from 'react-router-dom'; 3 | import { IonApp, IonRouterOutlet, IonSplitPane } from '@ionic/react'; 4 | import { IonReactRouter } from '@ionic/react-router'; 5 | 6 | import Menu from './components/Menu'; 7 | import Home from './pages/Home'; 8 | import ScreenReaderPage from './pages/ScreenReaderPage'; 9 | 10 | /* Core CSS required for Ionic components to work properly */ 11 | import '@ionic/react/css/core.css'; 12 | 13 | /* Basic CSS for apps built with Ionic */ 14 | import '@ionic/react/css/normalize.css'; 15 | import '@ionic/react/css/structure.css'; 16 | import '@ionic/react/css/typography.css'; 17 | 18 | /* Optional CSS utils that can be commented out */ 19 | import '@ionic/react/css/padding.css'; 20 | import '@ionic/react/css/float-elements.css'; 21 | import '@ionic/react/css/text-alignment.css'; 22 | import '@ionic/react/css/text-transformation.css'; 23 | import '@ionic/react/css/flex-utils.css'; 24 | import '@ionic/react/css/display.css'; 25 | 26 | /* Theme variables */ 27 | import './theme/variables.css'; 28 | import AppPage from './pages/AppPage'; 29 | import BrowserPage from './pages/BrowserPage'; 30 | import CameraPage from './pages/CameraPage'; 31 | import ClipboardPage from './pages/ClipboardPage'; 32 | import DevicePage from './pages/DevicePage'; 33 | import GeolocationPage from './pages/GeolocationPage'; 34 | import KeyboardPage from './pages/KeyboardPage'; 35 | import NetworkPage from './pages/NetworkPage'; 36 | import StoragePage from './pages/StoragePage'; 37 | 38 | const App: React.FC = () => ( 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | ); 61 | 62 | export default App; 63 | -------------------------------------------------------------------------------- /packages/test-app/src/components/Menu.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | IonContent, 3 | IonHeader, 4 | IonIcon, 5 | IonItem, 6 | IonLabel, 7 | IonList, 8 | IonMenu, 9 | IonMenuToggle, 10 | IonTitle, 11 | IonToolbar, 12 | } from '@ionic/react'; 13 | import React from 'react'; 14 | import { RouteComponentProps, withRouter } from 'react-router-dom'; 15 | import { star } from 'ionicons/icons'; 16 | 17 | type MenuProps = RouteComponentProps; 18 | 19 | const Menu: React.FC = () => ( 20 | 21 | 22 | 23 | Menu 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | App 32 | 33 | 34 | 35 | Browser 36 | 37 | 38 | 39 | Camera 40 | 41 | 42 | 43 | Clipboard 44 | 45 | 46 | 47 | Device 48 | 49 | 50 | 51 | Geolocation 52 | 53 | 54 | 55 | Keyboard 56 | 57 | 58 | 59 | Network 60 | 61 | 62 | 63 | ScreenReader 64 | 65 | 66 | 67 | Storage 68 | 69 | 70 | 71 | 72 | 73 | ); 74 | 75 | export default withRouter(Menu); 76 | -------------------------------------------------------------------------------- /packages/test-app/src/declarations.ts: -------------------------------------------------------------------------------- 1 | export interface AppPage { 2 | url: string; 3 | icon: object; 4 | title: string; 5 | } 6 | -------------------------------------------------------------------------------- /packages/test-app/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import { defineCustomElements } from '@ionic/pwa-elements/loader'; 5 | 6 | ReactDOM.render(, document.getElementById('root')); 7 | 8 | defineCustomElements(window); 9 | -------------------------------------------------------------------------------- /packages/test-app/src/pages/AppPage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | IonContent, 4 | IonHeader, 5 | IonPage, 6 | IonTitle, 7 | IonToolbar, 8 | IonButtons, 9 | IonMenuButton, 10 | IonCard, 11 | IonCardHeader, 12 | IonCardTitle, 13 | IonCardContent, 14 | } from '@ionic/react'; 15 | import { Capacitor } from '@capacitor/core'; 16 | import { 17 | useAppState, 18 | useAppUrlOpen, 19 | useLaunchUrl, 20 | availableFeatures, 21 | } from '@capacitor-community/app-react'; 22 | const platform = Capacitor.getPlatform(); 23 | const AppPage: React.FC = () => { 24 | const { state } = useAppState(); 25 | const { appUrlOpen } = useAppUrlOpen(); 26 | const { launchUrl } = useLaunchUrl(); 27 | 28 | return ( 29 | 30 | 31 | 32 | 33 | 34 | 35 | App 36 | 37 | 38 | 39 | 40 | 41 | useAppState 42 | 43 | 44 |

45 | isAvailable on {platform}: {JSON.stringify(availableFeatures.appState)}{' '} 46 |

47 |

state: {JSON.stringify(state)}

48 |
49 |
50 | 51 | 52 | useAppUrlOpen 53 | 54 | 55 |

56 | isAvailable on {platform}: {JSON.stringify(availableFeatures.appUrlOpen)}{' '} 57 |

58 | {availableFeatures.appUrlOpen &&

appUrlOpen: {appUrlOpen}

} 59 | {availableFeatures.getLaunchUrl &&

launchUrl: {launchUrl}

} 60 |
61 |
62 | 63 | 64 | useLaunchUrl 65 | 66 | 67 |

68 | isAvailable on {platform}: {JSON.stringify(availableFeatures.getLaunchUrl)}{' '} 69 |

70 | {availableFeatures.getLaunchUrl &&

launchUrl: {launchUrl}

} 71 |
72 |
73 |
74 |
75 | ); 76 | }; 77 | 78 | export default AppPage; 79 | -------------------------------------------------------------------------------- /packages/test-app/src/pages/BrowserPage.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Capacitor } from '@capacitor/core'; 3 | import { 4 | IonContent, 5 | IonHeader, 6 | IonPage, 7 | IonTitle, 8 | IonToolbar, 9 | IonButtons, 10 | IonMenuButton, 11 | IonCard, 12 | IonCardHeader, 13 | IonCardTitle, 14 | IonCardContent, 15 | IonButton, 16 | IonInput, 17 | } from '@ionic/react'; 18 | 19 | import { useClose, useOpen, availableFeatures } from '@capacitor-community/browser-react'; 20 | 21 | const BrowserPage: React.FC = () => { 22 | const platform = Capacitor.getPlatform(); 23 | const { close } = useClose(); 24 | const { open } = useOpen(); 25 | const [openUrlText, setOpenUrlText] = useState('https://www.ionicframework.com'); 26 | 27 | const handleClose = () => { 28 | if (availableFeatures.close) { 29 | close(); 30 | } 31 | }; 32 | 33 | const handleOpen = () => { 34 | if (availableFeatures.open) { 35 | open({ 36 | url: openUrlText, 37 | }); 38 | } 39 | }; 40 | 41 | return ( 42 | 43 | 44 | 45 | 46 | 47 | 48 | Browser 49 | 50 | 51 | 52 | 53 | 54 | useClose 55 | 56 | 57 |

58 | isAvailable on {platform}: {JSON.stringify(availableFeatures.close)}{' '} 59 |

60 | {availableFeatures.close && Close} 61 |
62 |
63 | 64 | 65 | useOpen 66 | 67 | 68 |

69 | isAvailable on {platform}: {JSON.stringify(availableFeatures.open)}{' '} 70 |

71 | {availableFeatures.open && ( 72 |
73 |
74 | setOpenUrlText(e.detail.value!)} 77 | /> 78 |
79 |
80 | Open 81 |
82 |
83 | )} 84 |
85 |
86 |
87 |
88 | ); 89 | }; 90 | 91 | export default BrowserPage; 92 | -------------------------------------------------------------------------------- /packages/test-app/src/pages/CameraPage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | IonContent, 4 | IonHeader, 5 | IonPage, 6 | IonTitle, 7 | IonToolbar, 8 | IonButtons, 9 | IonMenuButton, 10 | IonCard, 11 | IonCardHeader, 12 | IonCardTitle, 13 | IonCardContent, 14 | IonButton, 15 | } from '@ionic/react'; 16 | import { Capacitor } from '@capacitor/core'; 17 | import { CameraResultType } from '@capacitor/camera'; 18 | import { useCamera, availableFeatures } from '@capacitor-community/camera-react'; 19 | 20 | const CameraPage: React.FC = () => { 21 | const platform = Capacitor.getPlatform(); 22 | const { photo, getPhoto } = useCamera(); 23 | 24 | const handleTakePhoto = () => { 25 | if (availableFeatures.getPhoto) { 26 | getPhoto({ 27 | quality: 100, 28 | allowEditing: false, 29 | resultType: CameraResultType.DataUrl, 30 | }); 31 | } 32 | }; 33 | 34 | return ( 35 | 36 | 37 | 38 | 39 | 40 | 41 | Camera 42 | 43 | 44 | 45 | 46 | 47 | useCamera 48 | 49 | 50 |

51 | isAvailable on {platform}: {JSON.stringify(availableFeatures.getPhoto)}{' '} 52 |

53 | {availableFeatures.getPhoto && ( 54 |
55 |
56 | Take Photo 57 |
58 |
{photo && }
59 |
60 | )} 61 |
62 |
63 |
64 |
65 | ); 66 | }; 67 | 68 | export default CameraPage; 69 | -------------------------------------------------------------------------------- /packages/test-app/src/pages/ClipboardPage.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { 3 | IonContent, 4 | IonHeader, 5 | IonPage, 6 | IonTitle, 7 | IonToolbar, 8 | IonButtons, 9 | IonMenuButton, 10 | IonCard, 11 | IonCardHeader, 12 | IonCardTitle, 13 | IonCardContent, 14 | IonInput, 15 | IonButton, 16 | } from '@ionic/react'; 17 | import { Capacitor } from '@capacitor/core'; 18 | import { useClipboard, availableFeatures } from '@capacitor-community/clipboard-react'; 19 | 20 | const ClipboardPage: React.FC = () => { 21 | const platform = Capacitor.getPlatform(); 22 | const { value, getValue, setValue } = useClipboard(); 23 | const [inputText, setInputText] = useState('Text to copy'); 24 | 25 | const handleCopy = () => { 26 | if (availableFeatures.useClipboard) { 27 | setValue(inputText); 28 | } 29 | }; 30 | 31 | const handlePaste = () => { 32 | if (availableFeatures.useClipboard) { 33 | getValue(); 34 | } 35 | }; 36 | 37 | return ( 38 | 39 | 40 | 41 | 42 | 43 | 44 | Clipboard 45 | 46 | 47 | 48 | 49 | 50 | useClipboard 51 | 52 | 53 |

54 | isAvailable on {platform}: {JSON.stringify(availableFeatures.useClipboard)}{' '} 55 |

56 | {availableFeatures.useClipboard && ( 57 | <> 58 |

59 | setInputText(e.detail.value!)} /> 60 |

61 |

62 | Copy Input Text 63 |

64 |

65 | Paste Text into Div 66 |

67 |

{value}

68 | 69 | )} 70 |
71 |
72 |
73 |
74 | ); 75 | }; 76 | 77 | export default ClipboardPage; 78 | -------------------------------------------------------------------------------- /packages/test-app/src/pages/DevicePage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | IonContent, 4 | IonHeader, 5 | IonPage, 6 | IonTitle, 7 | IonToolbar, 8 | IonButtons, 9 | IonMenuButton, 10 | IonCard, 11 | IonCardHeader, 12 | IonCardTitle, 13 | IonCardContent, 14 | } from '@ionic/react'; 15 | import { Capacitor } from '@capacitor/core'; 16 | import { 17 | useGetInfo, 18 | useGetLanguageCode, 19 | availableFeatures, 20 | } from '@capacitor-community/device-react'; 21 | 22 | const DevicePage: React.FC = () => { 23 | const platform = Capacitor.getPlatform(); 24 | const { info } = useGetInfo(); 25 | const { languageCode } = useGetLanguageCode(); 26 | 27 | return ( 28 | 29 | 30 | 31 | 32 | 33 | 34 | Device 35 | 36 | 37 | 38 | 39 | 40 | useGetInfo 41 | 42 | 43 |

44 | isAvailable on {platform}: {JSON.stringify(availableFeatures.getInfo)}{' '} 45 |

46 | {availableFeatures.getInfo && ( 47 | <> 48 |

Device info:

49 |

{JSON.stringify(info)}

50 | 51 | )} 52 |
53 |
54 | 55 | 56 | useGetLanguageCode 57 | 58 | 59 |

60 | isAvailable on {platform}: {JSON.stringify(availableFeatures.getLanguageCode)}{' '} 61 |

62 | {availableFeatures.getLanguageCode && ( 63 | <> 64 |

Language Code:

65 |

{languageCode}

66 | 67 | )} 68 |
69 |
70 |
71 |
72 | ); 73 | }; 74 | 75 | export default DevicePage; 76 | -------------------------------------------------------------------------------- /packages/test-app/src/pages/GeolocationPage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | IonContent, 4 | IonHeader, 5 | IonPage, 6 | IonTitle, 7 | IonToolbar, 8 | IonButtons, 9 | IonMenuButton, 10 | IonCard, 11 | IonCardHeader, 12 | IonCardTitle, 13 | IonCardContent, 14 | IonButton, 15 | } from '@ionic/react'; 16 | import { Capacitor } from '@capacitor/core'; 17 | import { 18 | useCurrentPosition, 19 | useWatchPosition, 20 | availableFeatures, 21 | } from '@capacitor-community/geolocation-react'; 22 | 23 | const GeolocationPage: React.FC = () => { 24 | const platform = Capacitor.getPlatform(); 25 | const { error, currentPosition, getPosition } = useCurrentPosition(); 26 | const { currentPosition: watchPosition, startWatch, clearWatch } = useWatchPosition(); 27 | 28 | const handleRefreshPosition = () => { 29 | if (availableFeatures.getCurrentPosition) { 30 | getPosition(); 31 | } 32 | }; 33 | 34 | console.log(error, currentPosition); 35 | 36 | return ( 37 | 38 | 39 | 40 | 41 | 42 | 43 | Geolocation 44 | 45 | 46 | 47 | 48 | 49 | useCurrentPosition 50 | 51 | 52 |

53 | isAvailable on {platform}: {JSON.stringify(availableFeatures.getCurrentPosition)} 54 |

55 | {availableFeatures.getCurrentPosition && ( 56 | <> 57 |

Current Position

58 |

Lat: {currentPosition && currentPosition.coords.latitude}

59 |

Lon: {currentPosition && currentPosition.coords.longitude}

60 |

61 | Refresh Current Position 62 |

63 | 64 | )} 65 |
66 |
67 | 68 | 69 | useWatchPosition 70 | 71 | 72 |

73 | isAvailable on {platform}: {JSON.stringify(availableFeatures.watchPosition)} 74 |

75 | {availableFeatures.watchPosition && ( 76 | <> 77 |

Current Position

78 |

Lat: {watchPosition && watchPosition.coords.latitude}

79 |

Lon: {watchPosition && watchPosition.coords.longitude}

80 |

81 | startWatch()}>Start watching location 82 |

83 |

84 | clearWatch()}>Stop watching location 85 |

86 | 87 | )} 88 |
89 |
90 |
91 |
92 | ); 93 | }; 94 | 95 | export default GeolocationPage; 96 | -------------------------------------------------------------------------------- /packages/test-app/src/pages/Home.css: -------------------------------------------------------------------------------- 1 | .welcome-card img { 2 | max-height: 35vh; 3 | overflow: hidden; 4 | } 5 | -------------------------------------------------------------------------------- /packages/test-app/src/pages/Home.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | IonButtons, 3 | IonCard, 4 | IonCardContent, 5 | IonCardHeader, 6 | IonCardSubtitle, 7 | IonCardTitle, 8 | IonContent, 9 | IonHeader, 10 | IonMenuButton, 11 | IonPage, 12 | IonTitle, 13 | IonToolbar, 14 | } from '@ionic/react'; 15 | import React from 'react'; 16 | import './Home.css'; 17 | 18 | const HomePage: React.FC = () => { 19 | return ( 20 | 21 | 22 | 23 | 24 | 25 | 26 | React Hooks 27 | 28 | 29 | 30 | 31 | 32 | 33 | Welcome 34 | Welcome to React Hooks Demo 35 | 36 | 37 |

View the menu to see the demos

38 |
39 |
40 |
41 |
42 | ); 43 | }; 44 | 45 | export default HomePage; 46 | -------------------------------------------------------------------------------- /packages/test-app/src/pages/KeyboardPage.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { 3 | IonContent, 4 | IonHeader, 5 | IonPage, 6 | IonTitle, 7 | IonToolbar, 8 | IonButtons, 9 | IonMenuButton, 10 | IonCard, 11 | IonCardHeader, 12 | IonCardTitle, 13 | IonCardContent, 14 | IonButton, 15 | IonInput, 16 | } from '@ionic/react'; 17 | import { Capacitor } from '@capacitor/core'; 18 | import { useKeyboard } from '@capacitor-community/keyboard-react'; 19 | 20 | const KeyboardPage: React.FC = () => { 21 | const platform = Capacitor.getPlatform(); 22 | const { isOpen, keyboardHeight, keyboard } = useKeyboard(); 23 | 24 | useEffect(() => { 25 | keyboard.setAccessoryBarVisible({ isVisible: true }); 26 | }, []); 27 | 28 | const handleOpenKeyboard = () => { 29 | keyboard.show(); 30 | }; 31 | const handleHideKeyboard = () => { 32 | keyboard.hide(); 33 | }; 34 | 35 | return ( 36 | 37 | 38 | 39 | 40 | 41 | 42 | Keyboard 43 | 44 | 45 | 46 | 47 | 48 | useKeyboard 49 | 50 | 51 | {/*

52 | isAvailable on {platform}: {JSON.stringify(availableFeatures.getPhoto)}{' '} 53 |

*/} 54 | {/* {availableFeatures.getPhoto && ( */} 55 |
56 |
57 | Open Keyboard 58 | Hide Keyboard 59 |
60 |
61 | isOpen: {isOpen ? 'true' : 'false'}, keyboard height: {keyboardHeight} 62 |
63 |
64 | Input: 65 |
66 |
67 | {/* )} */} 68 |
69 |
70 |
71 |
72 | ); 73 | }; 74 | 75 | export default KeyboardPage; 76 | -------------------------------------------------------------------------------- /packages/test-app/src/pages/NetworkPage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | IonContent, 4 | IonHeader, 5 | IonPage, 6 | IonTitle, 7 | IonToolbar, 8 | IonButtons, 9 | IonMenuButton, 10 | IonCard, 11 | IonCardHeader, 12 | IonCardTitle, 13 | IonCardContent, 14 | } from '@ionic/react'; 15 | import { Capacitor } from '@capacitor/core'; 16 | import { useStatus, availableFeatures } from '@capacitor-community/network-react'; 17 | 18 | const NetworkPage: React.FC = () => { 19 | const platform = Capacitor.getPlatform(); 20 | const { networkStatus } = useStatus(); 21 | 22 | return ( 23 | 24 | 25 | 26 | 27 | 28 | 29 | Network 30 | 31 | 32 | 33 | 34 | 35 | useStatus 36 | 37 | 38 |

39 | isAvailable on {platform}: {JSON.stringify(availableFeatures.getStatus)}{' '} 40 |

41 | {availableFeatures.getStatus && ( 42 | <> 43 |

Network status:

44 |

{JSON.stringify(networkStatus)}

45 | 46 | )} 47 |
48 |
49 |
50 |
51 | ); 52 | }; 53 | 54 | export default NetworkPage; 55 | -------------------------------------------------------------------------------- /packages/test-app/src/pages/ScreenReaderPage.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Capacitor } from '@capacitor/core'; 3 | import { 4 | IonContent, 5 | IonHeader, 6 | IonPage, 7 | IonTitle, 8 | IonToolbar, 9 | IonButtons, 10 | IonMenuButton, 11 | IonCard, 12 | IonCardHeader, 13 | IonCardTitle, 14 | IonCardContent, 15 | IonInput, 16 | IonButton, 17 | } from '@ionic/react'; 18 | import { 19 | useIsScreenReaderEnabled, 20 | availableFeatures, 21 | useSpeak, 22 | } from '@capacitor-community/screen-reader-react'; 23 | 24 | const ScreenReaderPage: React.FC = () => { 25 | const platform = Capacitor.getPlatform(); 26 | 27 | const { speak } = useSpeak(); 28 | const { isScreenReaderEnabled } = useIsScreenReaderEnabled(); 29 | const [textToSpeak, setTextToSpeak] = useState(''); 30 | 31 | const handleSpeak = () => { 32 | if (availableFeatures.speak) { 33 | speak({ value: textToSpeak }); 34 | } 35 | }; 36 | 37 | return ( 38 | 39 | 40 | 41 | 42 | 43 | 44 | Screen Reader 45 | 46 | 47 | 48 | 49 | 50 | useIsScreenReaderEnabled 51 | 52 | 53 |

54 | isAvailable on {platform}: {JSON.stringify(availableFeatures.isScreenReaderAvailable)}{' '} 55 |

56 | {availableFeatures.isScreenReaderAvailable && ( 57 |

isScreenReaderEnabled: {JSON.stringify(isScreenReaderEnabled)}

58 | )} 59 |
60 |
61 | 62 | 63 | 64 | useSpeak 65 | 66 | 67 |

68 | isAvailable on {platform}: {JSON.stringify(availableFeatures.speak)}{' '} 69 |

70 | {availableFeatures.speak && ( 71 |
72 |
73 | setTextToSpeak(e.detail.value!)} 76 | placeholder="Enter text to speak" 77 | /> 78 |
79 |
80 | Speak 81 |
82 |
83 | )} 84 |
85 |
86 |
87 |
88 | ); 89 | }; 90 | 91 | export default ScreenReaderPage; 92 | -------------------------------------------------------------------------------- /packages/test-app/src/pages/StoragePage.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { 3 | IonContent, 4 | IonHeader, 5 | IonPage, 6 | IonTitle, 7 | IonToolbar, 8 | IonButtons, 9 | IonMenuButton, 10 | IonCard, 11 | IonCardHeader, 12 | IonCardTitle, 13 | IonCardContent, 14 | IonInput, 15 | IonButton, 16 | } from '@ionic/react'; 17 | import { Capacitor } from '@capacitor/core'; 18 | import { useStorage, useStorageItem, availableFeatures } from '@capacitor-community/storage-react'; 19 | 20 | const StoragePage: React.FC = () => { 21 | const platform = Capacitor.getPlatform(); 22 | 23 | const { get, set, remove, getKeys, clear } = useStorage(); 24 | const [keyText, setKeyText] = useState(''); 25 | const [valueText, setValueText] = useState(''); 26 | const [itemValueText, setItemValueText] = useState(''); 27 | const [keys, setKeys] = useState([]); 28 | const [storageValue, setStorageValue] = useState(''); 29 | const [data, setData] = useStorageItem('name'); 30 | 31 | const handleSet = () => { 32 | if (availableFeatures.useStorage) { 33 | set(keyText, valueText); 34 | } 35 | }; 36 | 37 | const handleGet = async () => { 38 | if (availableFeatures.useStorage) { 39 | const value = await get(keyText); 40 | setStorageValue(value || ''); 41 | } 42 | }; 43 | 44 | const handleRemove = () => { 45 | if (availableFeatures.useStorage) { 46 | remove(keyText); 47 | } 48 | }; 49 | 50 | const handleClear = () => { 51 | if (availableFeatures.useStorage) { 52 | clear(); 53 | } 54 | }; 55 | 56 | const handleGetKeys = () => { 57 | if (availableFeatures.useStorage) { 58 | if (availableFeatures.useStorage) { 59 | getKeys().then((result) => setKeys(result.keys)); 60 | } 61 | } 62 | }; 63 | 64 | const handleSetItem = () => { 65 | if (availableFeatures.useStorage) { 66 | setData(itemValueText); 67 | } 68 | }; 69 | 70 | return ( 71 | 72 | 73 | 74 | 75 | 76 | 77 | Storage 78 | 79 | 80 | 81 | 82 | 83 | useStorage 84 | 85 | 86 |

87 | isAvailable on {platform}: {JSON.stringify(availableFeatures.useStorage)}{' '} 88 |

89 | {availableFeatures.useStorage && ( 90 | <> 91 |

92 | Key: setKeyText(e.detail.value!)} /> 93 |

94 |

95 | Value:{' '} 96 | setValueText(e.detail.value!)} /> 97 |

98 |

99 | Set 100 | Get 101 | Remove 102 | Clear 103 | Get Keys 104 |

105 |

Value: {storageValue}

106 |

Keys: {keys.map((k) => `${k}, `)}

107 | 108 | )} 109 |
110 |
111 | 112 | 113 | useStorageItem 114 | 115 | 116 |

117 | isAvailable on {platform}: {JSON.stringify(availableFeatures.useStorage)}{' '} 118 |

119 | {availableFeatures.useStorage && ( 120 | <> 121 |

Key: name

122 |

123 | Value:{' '} 124 | setItemValueText(e.detail.value!)} 127 | /> 128 |

129 |

130 | Set 131 |

132 |

Value: {data}

133 | 134 | )} 135 |
136 |
137 |
138 |
139 | ); 140 | }; 141 | 142 | export default StoragePage; 143 | -------------------------------------------------------------------------------- /packages/test-app/src/pages/template.tsx: -------------------------------------------------------------------------------- 1 | // import React from 'react'; 2 | // import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar, IonButtons, IonMenuButton, IonCard, IonCardHeader, IonCardTitle, IonCardContent } from '@ionic/react'; 3 | // import { usePlatform } from '@ionic/react-hooks/platform'; 4 | 5 | // const XState: React.FC = () => { 6 | // const { platform } = usePlatform(); 7 | 8 | // return ( 9 | // 10 | // 11 | // 12 | // 13 | // 14 | // 15 | // 16 | // 17 | // 18 | // 19 | // 20 | // 21 | // 22 | // X 23 | // 24 | // 25 | // 26 | //

isAvailable on {platform}: {JSON.stringify(false)}

27 | //
28 | //
29 | //
30 | //
31 | // ); 32 | // }; 33 | 34 | // export default XState; 35 | export default {}; 36 | -------------------------------------------------------------------------------- /packages/test-app/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | declare namespace NodeJS { 6 | interface ProcessEnv { 7 | readonly NODE_ENV: 'development' | 'production' | 'test'; 8 | readonly PUBLIC_URL: string; 9 | } 10 | } 11 | 12 | declare module '*.bmp' { 13 | const src: string; 14 | export default src; 15 | } 16 | 17 | declare module '*.gif' { 18 | const src: string; 19 | export default src; 20 | } 21 | 22 | declare module '*.jpg' { 23 | const src: string; 24 | export default src; 25 | } 26 | 27 | declare module '*.jpeg' { 28 | const src: string; 29 | export default src; 30 | } 31 | 32 | declare module '*.png' { 33 | const src: string; 34 | export default src; 35 | } 36 | 37 | declare module '*.webp' { 38 | const src: string; 39 | export default src; 40 | } 41 | 42 | declare module '*.svg' { 43 | import * as React from 'react'; 44 | 45 | export const ReactComponent: React.FunctionComponent>; 46 | 47 | const src: string; 48 | export default src; 49 | } 50 | 51 | declare module '*.module.css' { 52 | const classes: { readonly [key: string]: string }; 53 | export default classes; 54 | } 55 | 56 | declare module '*.module.scss' { 57 | const classes: { readonly [key: string]: string }; 58 | export default classes; 59 | } 60 | 61 | declare module '*.module.sass' { 62 | const classes: { readonly [key: string]: string }; 63 | export default classes; 64 | } 65 | -------------------------------------------------------------------------------- /packages/test-app/src/theme.css: -------------------------------------------------------------------------------- 1 | 2 | /* Ionic Variables and Theming. For more information, please see 3 | // https://beta.ionicframework.com/docs/theming/ 4 | // The app direction is used to include 5 | // rtl styles in your app. For more information, please see 6 | // https://beta.ionicframework.com/docs/layout/rtl 7 | // $app-direction: ltr; 8 | // Ionic Colors 9 | // -------------------------------------------------- 10 | // Named colors makes it easy to reuse colors on various components. 11 | // It's highly recommended to change the default colors 12 | // to match your app's branding. Ionic provides eight layered colors 13 | // that can be changed to theme an app. Additional colors can be 14 | // added as well (see below). For more information, please see 15 | // https://beta.ionicframework.com/docs/theming/advanced 16 | // To easily create custom color palettes for your app’s UI, 17 | // check out our color generator: 18 | // https://beta.ionicframework.com/docs/theming/color-generator 19 | */ 20 | 21 | :root { 22 | --ion-color-angular: #ac282b; 23 | --ion-color-communication: #8e8d93; 24 | --ion-color-tooling: #fe4c52; 25 | --ion-color-services: #fd8b2d; 26 | --ion-color-design: #fed035; 27 | --ion-color-workshop: #69bb7b; 28 | --ion-color-food: #3bc7c4; 29 | --ion-color-documentation: #b16be3; 30 | --ion-color-navigation: #6600cc; 31 | 32 | --ion-color-primary: #3880ff; 33 | --ion-color-primary-rgb: 56, 128, 255; 34 | --ion-color-primary-contrast: #ffffff; 35 | --ion-color-primary-contrast-rgb: 255, 255, 255; 36 | --ion-color-primary-shade: #3171e0; 37 | --ion-color-primary-tint: #4c8dff; 38 | 39 | --ion-color-secondary: #0cd1e8; 40 | --ion-color-secondary-rgb: 12, 209, 232; 41 | --ion-color-secondary-contrast: #ffffff; 42 | --ion-color-secondary-contrast-rgb: 255, 255, 255; 43 | --ion-color-secondary-shade: #0bb8cc; 44 | --ion-color-secondary-tint: #24d6ea; 45 | 46 | --ion-color-tertiary: #7044ff; 47 | --ion-color-tertiary-rgb: 112, 68, 255; 48 | --ion-color-tertiary-contrast: #ffffff; 49 | --ion-color-tertiary-contrast-rgb: 255, 255, 255; 50 | --ion-color-tertiary-shade: #633ce0; 51 | --ion-color-tertiary-tint: #7e57ff; 52 | 53 | --ion-color-success: #10dc60; 54 | --ion-color-success-rgb: 16, 220, 96; 55 | --ion-color-success-contrast: #ffffff; 56 | --ion-color-success-contrast-rgb: 255, 255, 255; 57 | --ion-color-success-shade: #0ec254; 58 | --ion-color-success-tint: #28e070; 59 | 60 | --ion-color-warning: #ffce00; 61 | --ion-color-warning-rgb: 255, 206, 0; 62 | --ion-color-warning-contrast: #ffffff; 63 | --ion-color-warning-contrast-rgb: 255, 255, 255; 64 | --ion-color-warning-shade: #e0b500; 65 | --ion-color-warning-tint: #ffd31a; 66 | 67 | --ion-color-danger: #f04141; 68 | --ion-color-danger-rgb: 245, 61, 61; 69 | --ion-color-danger-contrast: #ffffff; 70 | --ion-color-danger-contrast-rgb: 255, 255, 255; 71 | --ion-color-danger-shade: #d33939; 72 | --ion-color-danger-tint: #f25454; 73 | 74 | --ion-color-dark: #222428; 75 | --ion-color-dark-rgb: 34, 34, 34; 76 | --ion-color-dark-contrast: #ffffff; 77 | --ion-color-dark-contrast-rgb: 255, 255, 255; 78 | --ion-color-dark-shade: #1e2023; 79 | --ion-color-dark-tint: #383a3e; 80 | 81 | --ion-color-medium: #989aa2; 82 | --ion-color-medium-rgb: 152, 154, 162; 83 | --ion-color-medium-contrast: #ffffff; 84 | --ion-color-medium-contrast-rgb: 255, 255, 255; 85 | --ion-color-medium-shade: #86888f; 86 | --ion-color-medium-tint: #a2a4ab; 87 | 88 | --ion-color-light: #f4f5f8; 89 | --ion-color-light-rgb: 244, 244, 244; 90 | --ion-color-light-contrast: #000000; 91 | --ion-color-light-contrast-rgb: 0, 0, 0; 92 | --ion-color-light-shade: #d7d8da; 93 | --ion-color-light-tint: #f5f6f9; 94 | } 95 | 96 | /* Additional Ionic Colors 97 | // -------------------------------------------------- 98 | // In order to add colors to be used with Ionic components, 99 | // the color should be added as a class with the convention `.ion-color-{COLOR}` 100 | // where `{COLOR}` is the color to be used on the Ionic component 101 | // and each variant is defined for the color. For more information, please see 102 | // https://beta.ionicframework.com/docs/theming/advanced 103 | */ 104 | 105 | .ion-color-favorite { 106 | --ion-color-base: #69bb7b; 107 | --ion-color-base-rgb: 105, 187, 123; 108 | --ion-color-contrast: #ffffff; 109 | --ion-color-contrast-rgb: 255, 255, 255; 110 | --ion-color-shade: #5ca56c; 111 | --ion-color-tint: #78c288; 112 | } 113 | 114 | .ion-color-twitter { 115 | --ion-color-base: #1da1f4; 116 | --ion-color-base-rgb: 29, 161, 244; 117 | --ion-color-contrast: #ffffff; 118 | --ion-color-contrast-rgb: 255, 255, 255; 119 | --ion-color-shade: #1a8ed7; 120 | --ion-color-tint: #34aaf5; 121 | } 122 | 123 | .ion-color-google { 124 | --ion-color-base: #dc4a38; 125 | --ion-color-base-rgb: 220, 74, 56; 126 | --ion-color-contrast: #ffffff; 127 | --ion-color-contrast-rgb: 255, 255, 255; 128 | --ion-color-shade: #c24131; 129 | --ion-color-tint: #e05c4c; 130 | } 131 | 132 | .ion-color-vimeo { 133 | --ion-color-base: #23b6ea; 134 | --ion-color-base-rgb: 35, 182, 234; 135 | --ion-color-contrast: #ffffff; 136 | --ion-color-contrast-rgb: 255, 255, 255; 137 | --ion-color-shade: #1fa0ce; 138 | --ion-color-tint: #39bdec; 139 | } 140 | 141 | .ion-color-facebook { 142 | --ion-color-base: #3b5998; 143 | --ion-color-base-rgb: 59, 89, 152; 144 | --ion-color-contrast: #ffffff; 145 | --ion-color-contrast-rgb: 255, 255, 255; 146 | --ion-color-shade: #344e86; 147 | --ion-color-tint: #4f6aa2; 148 | } 149 | 150 | /* Shared Variables 151 | // -------------------------------------------------- 152 | // To customize the look and feel of this app, you can override 153 | // the CSS variables found in Ionic's source files. 154 | // To view all the possible Ionic variables, see: 155 | // https://beta.ionicframework.com/docs/theming/css-variables#ionic-variables 156 | */ 157 | 158 | :root { 159 | --ion-headings-font-weight: 300; 160 | 161 | --ion-color-angular: #ac282b; 162 | --ion-color-communication: #8e8d93; 163 | --ion-color-tooling: #fe4c52; 164 | --ion-color-services: #fd8b2d; 165 | --ion-color-design: #fed035; 166 | --ion-color-workshop: #69bb7b; 167 | --ion-color-food: #3bc7c4; 168 | --ion-color-documentation: #b16be3; 169 | --ion-color-navigation: #6600cc; 170 | } 171 | 172 | .md { 173 | --ion-toolbar-background: var(--ion-color-primary); 174 | --ion-toolbar-color: #fff; 175 | --ion-toolbar-color-activated: #fff; 176 | } 177 | -------------------------------------------------------------------------------- /packages/test-app/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: #0cd1e8; 16 | --ion-color-secondary-rgb: 12, 209, 232; 17 | --ion-color-secondary-contrast: #ffffff; 18 | --ion-color-secondary-contrast-rgb: 255, 255, 255; 19 | --ion-color-secondary-shade: #0bb8cc; 20 | --ion-color-secondary-tint: #24d6ea; 21 | 22 | /** tertiary **/ 23 | --ion-color-tertiary: #7044ff; 24 | --ion-color-tertiary-rgb: 112, 68, 255; 25 | --ion-color-tertiary-contrast: #ffffff; 26 | --ion-color-tertiary-contrast-rgb: 255, 255, 255; 27 | --ion-color-tertiary-shade: #633ce0; 28 | --ion-color-tertiary-tint: #7e57ff; 29 | 30 | /** success **/ 31 | --ion-color-success: #10dc60; 32 | --ion-color-success-rgb: 16, 220, 96; 33 | --ion-color-success-contrast: #ffffff; 34 | --ion-color-success-contrast-rgb: 255, 255, 255; 35 | --ion-color-success-shade: #0ec254; 36 | --ion-color-success-tint: #28e070; 37 | 38 | /** warning **/ 39 | --ion-color-warning: #ffce00; 40 | --ion-color-warning-rgb: 255, 206, 0; 41 | --ion-color-warning-contrast: #ffffff; 42 | --ion-color-warning-contrast-rgb: 255, 255, 255; 43 | --ion-color-warning-shade: #e0b500; 44 | --ion-color-warning-tint: #ffd31a; 45 | 46 | /** danger **/ 47 | --ion-color-danger: #f04141; 48 | --ion-color-danger-rgb: 245, 61, 61; 49 | --ion-color-danger-contrast: #ffffff; 50 | --ion-color-danger-contrast-rgb: 255, 255, 255; 51 | --ion-color-danger-shade: #d33939; 52 | --ion-color-danger-tint: #f25454; 53 | 54 | /** dark **/ 55 | --ion-color-dark: #222428; 56 | --ion-color-dark-rgb: 34, 34, 34; 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: #989aa2; 64 | --ion-color-medium-rgb: 152, 154, 162; 65 | --ion-color-medium-contrast: #ffffff; 66 | --ion-color-medium-contrast-rgb: 255, 255, 255; 67 | --ion-color-medium-shade: #86888f; 68 | --ion-color-medium-tint: #a2a4ab; 69 | 70 | /** light **/ 71 | --ion-color-light: #f4f5f8; 72 | --ion-color-light-rgb: 244, 244, 244; 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 | -------------------------------------------------------------------------------- /packages/test-app/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 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "noEmit": true, 20 | "jsx": "react-jsx", 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /packages/test-app/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint-react"] 3 | } 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "skipLibCheck": true, 4 | "esModuleInterop": true, 5 | "strict": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "jsx": "react", 8 | "declaration": true, 9 | "experimentalDecorators": true, 10 | "noEmitHelpers": false, 11 | "importHelpers": false, 12 | "moduleResolution": "node", 13 | "lib": ["dom", "es2017"], 14 | "pretty": true, 15 | "module": "es2015", 16 | "noImplicitAny": true, 17 | "noUnusedLocals": false, 18 | "noUnusedParameters": false, 19 | "sourceMap": true, 20 | "target": "es2017" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint-react"], 3 | "linterOptions": { 4 | "exclude": [ 5 | "**/*.spec.ts", 6 | "**/*.spec.tsx" 7 | ] 8 | }, 9 | "rules": { 10 | "no-conditional-assignment": false, 11 | "no-non-null-assertion": false, 12 | "no-unnecessary-type-assertion": false, 13 | "no-import-side-effect": false, 14 | "trailing-comma": false, 15 | "no-null-keyword": false, 16 | "no-console": false, 17 | "no-unbound-method": true, 18 | "no-floating-promises": false, 19 | "no-invalid-template-strings": true, 20 | "ban-export-const-enum": true, 21 | "only-arrow-functions": true, 22 | "strict-boolean-conditions": [false], 23 | "jsx-key": false, 24 | "jsx-self-close": false, 25 | "jsx-curly-spacing": [true, "never"], 26 | "jsx-boolean-value": [true, "never"], 27 | "jsx-no-bind": false, 28 | "jsx-no-lambda": false, 29 | "jsx-no-multiline-js": false, 30 | "jsx-wrap-multiline": false 31 | } 32 | } 33 | --------------------------------------------------------------------------------