├── .autorc
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .gitattributes
├── .github
└── ISSUE_TEMPLATE
│ └── bug_report.yml
├── .gitignore
├── .prettierrc.js
├── .yarn
└── install-state.gz
├── CHANGELOG.md
├── LICENSE
├── README.md
├── assets
├── banner.png
└── banner2.png
├── babel.config.js
├── docs
├── .gitignore
├── .yarn
│ ├── install-state.gz
│ └── releases
│ │ └── yarn-4.1.0.cjs
├── .yarnrc.yml
├── README.md
├── babel.config.js
├── docs-samples
│ ├── doc1.md
│ ├── doc2.md
│ ├── doc3.md
│ └── mdx.md
├── docs
│ ├── animate-presence.md
│ ├── animations-overview.md
│ ├── animations-repeat.md
│ ├── animations-sequence.md
│ ├── api
│ │ ├── components.md
│ │ ├── motify-svg.md
│ │ ├── motify.md
│ │ ├── props.md
│ │ └── transforms.md
│ ├── examples
│ │ ├── action-menu.md
│ │ ├── animate-presence.md
│ │ ├── auto-height.md
│ │ ├── dropdown.md
│ │ ├── exit-before-enter.md
│ │ ├── hello-world.md
│ │ ├── image-gallery.md
│ │ ├── loop.md
│ │ ├── menu-icon.md
│ │ ├── skeleton.md
│ │ └── variants.md
│ ├── hello-world.md
│ ├── hooks
│ │ ├── use-animation-state.md
│ │ └── use-dynamic-animation.md
│ ├── index.mdx
│ ├── installation.md
│ ├── interactions
│ │ ├── eslint.md
│ │ ├── merge.md
│ │ ├── overview.mdx
│ │ ├── pressable.md
│ │ ├── use-pressable-animated-props.md
│ │ ├── use-pressable-interpolate.md
│ │ ├── use-pressable-transition.md
│ │ ├── use-pressable.md
│ │ └── use-pressables.md
│ ├── next-js.md
│ ├── reanimated.md
│ ├── resources
│ │ └── videos.md
│ ├── skeleton.mdx
│ ├── starter.md
│ ├── svg.md
│ └── web.md
├── docusaurus.config.js
├── package.json
├── sidebars.js
├── src
│ ├── css
│ │ └── custom.css
│ ├── pages
│ │ └── styles.module.css
│ └── plugins
│ │ └── remark-npm2yarn
│ │ └── index.js
├── static
│ ├── .nojekyll
│ ├── font
│ │ ├── Satoshi-Bold.ttf
│ │ ├── Satoshi-Italic.ttf
│ │ ├── Satoshi-Medium.ttf
│ │ ├── Satoshi-MediumItalic.ttf
│ │ └── Satoshi-Regular.ttf
│ └── img
│ │ ├── Banner Gradient.png
│ │ ├── Banner Gradient.svg
│ │ ├── Header Logo.svg
│ │ ├── Interactions.png
│ │ ├── Logo White (flipped).svg
│ │ ├── Logo White.svg
│ │ ├── banner.svg
│ │ ├── favicon.ico
│ │ ├── favicon.svg
│ │ ├── logo-grad.svg
│ │ ├── logo.svg
│ │ ├── panda.png
│ │ ├── panda.svg
│ │ ├── undraw_docusaurus_mountain.svg
│ │ ├── undraw_docusaurus_react.svg
│ │ ├── undraw_docusaurus_tree.svg
│ │ └── white-flipped.svg
└── yarn.lock
├── examples
├── sample
│ ├── .gitignore
│ ├── .yarn
│ │ └── install-state.gz
│ ├── App.js
│ ├── app.json
│ ├── assets
│ │ ├── adaptive-icon.png
│ │ ├── favicon.png
│ │ ├── icon.png
│ │ └── splash.png
│ ├── babel.config.js
│ ├── metro.config.js
│ ├── package.json
│ ├── src
│ │ ├── Bug.tsx
│ │ ├── Color-Bug.tsx
│ │ ├── Color.tsx
│ │ ├── Drip.Color.tsx
│ │ ├── Drip.Presence.tsx
│ │ ├── Drip.Repeat.tsx
│ │ ├── Drip.tsx
│ │ ├── Easing.tsx
│ │ ├── Example.tsx
│ │ ├── Gallery.tsx
│ │ ├── Headless-Exit.tsx
│ │ ├── Ideas.tsx
│ │ ├── Measure.tsx
│ │ ├── Moti.Callbacks.tsx
│ │ ├── Moti.ChangeVariants.tsx
│ │ ├── Moti.DynamicAnimation.tsx
│ │ ├── Moti.ExitBeforeEnter.tsx
│ │ ├── Moti.Factory.tsx
│ │ ├── Moti.Gallery.tsx
│ │ ├── Moti.HelloWorld.tsx
│ │ ├── Moti.KeyPresence.tsx
│ │ ├── Moti.Loop.tsx
│ │ ├── Moti.NewTransition.tsx
│ │ ├── Moti.Presence.tsx
│ │ ├── Moti.Pressable.tsx
│ │ ├── Moti.PressableMenu.tsx
│ │ ├── Moti.PressableTooltip.tsx
│ │ ├── Moti.Progress.tsx
│ │ ├── Moti.Sequence.tsx
│ │ ├── Moti.SequenceLoop.tsx
│ │ ├── Moti.Skeleton.tsx
│ │ ├── Moti.Svg.tsx
│ │ ├── Moti.Transform.tsx
│ │ ├── Moti.Variants.tsx
│ │ ├── Perf.List.tsx
│ │ ├── Reanimated.Bg-Bug.tsx
│ │ ├── Reanimated.Width-Bug.tsx
│ │ ├── Repeat.tsx
│ │ ├── Sequence.Bug.tsx
│ │ └── Web.tsx
│ ├── tsconfig.json
│ └── yarn.lock
└── with-next
│ ├── .gitignore
│ ├── App.tsx
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── app.json
│ ├── babel.config.js
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── package.json
│ ├── pages
│ ├── _app.js
│ ├── _document.js
│ └── index.tsx
│ └── tsconfig.json
├── lerna-debug.log
├── lerna.json
├── license.md
├── package.json
├── packages
└── moti
│ ├── .eslintrc.js
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── author
│ ├── index.d.ts
│ └── index.js
│ ├── index.d.ts
│ ├── interactions
│ ├── index.d.ts
│ ├── index.js
│ └── index.js.map
│ ├── metro.config.js
│ ├── package.json
│ ├── skeleton
│ ├── index.d.ts
│ ├── index.js
│ └── react-native-linear-gradient.js
│ ├── src
│ ├── author
│ │ └── index.ts
│ ├── components
│ │ ├── image.tsx
│ │ ├── index.ts
│ │ ├── progress
│ │ │ └── index.tsx
│ │ ├── safe-area-view.tsx
│ │ ├── scroll-view.tsx
│ │ ├── text.tsx
│ │ └── view.tsx
│ ├── core
│ │ ├── constants
│ │ │ ├── color-keys.ts
│ │ │ ├── index.ts
│ │ │ └── package-name.ts
│ │ ├── index.ts
│ │ ├── motify.tsx
│ │ ├── types.ts
│ │ ├── use-animator
│ │ │ └── index.ts
│ │ ├── use-dynamic-animation
│ │ │ └── index.ts
│ │ └── use-motify.ts
│ ├── index.tsx
│ ├── interactions
│ │ ├── index.ts
│ │ └── pressable
│ │ │ ├── context.tsx
│ │ │ ├── hoverable-context.tsx
│ │ │ ├── hoverable.native.tsx
│ │ │ ├── hoverable.tsx
│ │ │ ├── index.tsx
│ │ │ ├── merge-refs.ts
│ │ │ ├── merge.ts
│ │ │ ├── pressable.tsx
│ │ │ ├── types.ts
│ │ │ ├── use-moti-pressable-animated-props.ts
│ │ │ ├── use-moti-pressable-interpolate.ts
│ │ │ ├── use-moti-pressable-transition.ts
│ │ │ ├── use-pressable.ts
│ │ │ ├── use-pressables.ts
│ │ │ └── use-validate-factory-or-id.ts
│ ├── skeleton
│ │ ├── expo.tsx
│ │ ├── index.ts
│ │ ├── native.tsx
│ │ ├── shared.ts
│ │ ├── skeleton-new.tsx
│ │ ├── skeleton.tsx
│ │ └── types.ts
│ └── svg
│ │ ├── index.ts
│ │ └── motify-svg.tsx
│ ├── svg
│ ├── index.d.ts
│ └── index.js
│ └── tsconfig.json
├── tsconfig.json
├── tsconfig.tsbuildinfo
└── yarn.lock
/.autorc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["npm", "slack", "twitter", "all-contibutors", "first-time-contributor", "released"]
3 | }
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 |
9 | indent_style = space
10 | indent_size = 2
11 |
12 | end_of_line = lf
13 | charset = utf-8
14 | trim_trailing_whitespace = true
15 | insert_final_newline = true
16 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | lib
2 | example
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | // generated by @nandorojo/lint-expo
2 | {
3 | "rules": {
4 | "react-hooks/exhaustive-deps": [
5 | "warn",
6 | {
7 | "additionalHooks": "(useDerivedValue|useAnimatedStyle|useAnimatedProps)"
8 | }
9 | ],
10 | "@typescript-eslint/array-type": "off"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.pbxproj -text
2 | # specific for windows script files
3 | *.bat text eol=crlf
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yml:
--------------------------------------------------------------------------------
1 | name: Bug report
2 | description: 🐞 🐞 🐞 🐞 🐞
3 | labels: [Bug, Needs Triage]
4 | body:
5 | - type: checkboxes
6 | attributes:
7 | label: Is there an existing issue for this?
8 | description: Please search to see if an issue already exists for the bug you encountered.
9 | options:
10 | - label: I have searched the existing issues
11 | required: true
12 | - type: checkboxes
13 | attributes:
14 | label: Do you want this issue prioritized?
15 | description: If this issue is urgent, you can add prioritize it via a sponsorship on GitHub. Feel free to tag @nandorojo here in the issue description if you sponsor.
16 | options:
17 | - label: Yes, I have sponsored
18 | - label: Not urgent
19 | - type: textarea
20 | attributes:
21 | label: Current Behavior
22 | description: Describe what's happening in 1-2 sentences.
23 | validations:
24 | required: false
25 | - type: textarea
26 | attributes:
27 | label: Expected Behavior
28 | description: A concise description of what you expected to happen.
29 | validations:
30 | required: false
31 | - type: textarea
32 | attributes:
33 | label: Steps To Reproduce
34 | description: Steps to reproduce the behavior.
35 | placeholder: |
36 | 1. Add a MotiView component...
37 | 2. Change to Animated.View...
38 | validations:
39 | required: false
40 | - type: textarea
41 | attributes:
42 | label: Versions
43 | description: |
44 | examples:
45 | - **Moti**: 0.18.0
46 | - **Reanimated**: 2.5.0
47 | - **React Native**: 0.68.0
48 | value: |
49 | - Moti:
50 | - Reanimated:
51 | - React Native:
52 | render: markdown
53 | validations:
54 | required: true
55 | - type: textarea
56 | attributes:
57 | label: Screenshots
58 | description: |
59 | Please add screenshots / videos that show the behavior if applicable.
60 | validations:
61 | required: false
62 | - type: textarea
63 | attributes:
64 | label: Reproduction
65 | description: |
66 | Please add a link to an Expo Snack or a **minimal reproduction**.
67 |
68 | If you can reproduce on Web, the easiest way to share a reproduction is by forking the StackBlitz starter.
69 |
70 | If the issue only happens on native, you can use the starter app:
71 |
72 | ```sh
73 | npx create-react-native-app -t with-moti
74 | ```
75 | validations:
76 | required: true
77 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OSX
2 | #
3 | .DS_Store
4 |
5 | # XDE
6 | .expo/
7 |
8 | # VSCode
9 | .vscode/
10 | jsconfig.json
11 |
12 | # Xcode
13 | #
14 | build/
15 | *.pbxuser
16 | !default.pbxuser
17 | *.mode1v3
18 | !default.mode1v3
19 | *.mode2v3
20 | !default.mode2v3
21 | *.perspectivev3
22 | !default.perspectivev3
23 | xcuserdata
24 | *.xccheckout
25 | *.moved-aside
26 | DerivedData
27 | *.hmap
28 | *.ipa
29 | *.xcuserstate
30 | project.xcworkspace
31 |
32 | # Android/IJ
33 | #
34 | .idea
35 | .gradle
36 | local.properties
37 | android.iml
38 |
39 | # Cocoapods
40 | #
41 | example/ios/Pods
42 |
43 | # node.js
44 | #
45 | node_modules/
46 | npm-debug.log
47 | yarn-debug.log
48 | yarn-error.log
49 |
50 | # BUCK
51 | buck-out/
52 | \.buckd/
53 | android/app/libs
54 | android/keystores/debug.keystore
55 |
56 | # Expo
57 | .expo/*
58 |
59 | # generated by bob
60 | lib/
61 |
62 |
63 | # Expo
64 | **/web-build
65 | **/.expo
66 | .env
67 |
68 | **/*/ios
69 |
70 | # CocoaPods
71 | Pods
72 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | // generated by @nandorojo/lint-expo
2 | module.exports = {
3 | tabWidth: 2,
4 | useTabs: false,
5 | semi: false,
6 | singleQuote: true,
7 | }
8 |
--------------------------------------------------------------------------------
/.yarn/install-state.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nandorojo/moti/1854a19e5f18446b3ea459f8ba88028fbb6dce2c/.yarn/install-state.gz
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2022 Fernando Rojo.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | The universal React Native animation library, powered by Reanimated 3.
4 |
5 | ```jsx
6 |
7 | ```
8 |
9 | # Documentation & Examples
10 |
11 | - [Documentation](https://moti.fyi)
12 | - [Installation](https://moti.fyi/installation)
13 | - [Examples](https://moti.fyi/examples/hello-world) *(please use Chrome, other browsers are partially supported)*
14 |
15 | ## Next.js Conf
16 |
17 |
22 |
23 | I spoke at at [Next.js Conf 2021](https://fernandorojo.co/conf) on October 26 about React Native + Next.js. [Watch the video](https://t.co/LkmxHXVz3K?amp=1) to see how we do it.
24 |
25 | # Highlights
26 |
27 | - Universal: works on all platforms
28 | - 60 FPS animations on the native thread
29 | - Mount/unmount animations, like `framer-motion`
30 | - Powered by Reanimated 3
31 | - Web support, out-of-the-box
32 | - Expo support
33 | - Intuitive API
34 | - Variants
35 | - Strong TypeScript support
36 | - Highly-configurable animations
37 | - Sequence animations
38 | - Loop & repeat animations
39 |
40 | # Preview
41 |
42 | - [API](https://twitter.com/FernandoTheRojo/status/1348093995277299712)
43 | - [Unmount animations with `exit`](https://twitter.com/FernandoTheRojo/status/1349884929765765123)
44 | - [`exitBeforeEnter` animations](https://twitter.com/FernandoTheRojo/status/1351234878902333445)
45 |
46 | # Follow
47 |
48 | Follow me [on Twitter](https://twitter.com/fernandotherojo) to stay up to date.
49 |
50 | # Sponsor
51 |
52 | Sponsorships via GitHub are appreciated.
53 |
54 |
55 |
56 |
57 |
58 | # License
59 |
60 | Moti has an MIT license. That said, a lot of free work goes into it, so if your company uses it, please sponsor, write a blog post, or tweet about it!
61 |
--------------------------------------------------------------------------------
/assets/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nandorojo/moti/1854a19e5f18446b3ea459f8ba88028fbb6dce2c/assets/banner.png
--------------------------------------------------------------------------------
/assets/banner2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nandorojo/moti/1854a19e5f18446b3ea459f8ba88028fbb6dce2c/assets/banner2.png
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ["module:metro-react-native-babel-preset"],
3 | };
4 |
--------------------------------------------------------------------------------
/docs/.gitignore:
--------------------------------------------------------------------------------
1 | # Dependencies
2 | /node_modules
3 |
4 | # Production
5 | /build
6 |
7 | # Generated files
8 | .docusaurus
9 | .cache-loader
10 |
11 | # Misc
12 | .DS_Store
13 | .env.local
14 | .env.development.local
15 | .env.test.local
16 | .env.production.local
17 |
18 | npm-debug.log*
19 | yarn-debug.log*
20 | yarn-error.log*
21 |
--------------------------------------------------------------------------------
/docs/.yarn/install-state.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nandorojo/moti/1854a19e5f18446b3ea459f8ba88028fbb6dce2c/docs/.yarn/install-state.gz
--------------------------------------------------------------------------------
/docs/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | nodeLinker: node-modules
2 |
3 | yarnPath: .yarn/releases/yarn-4.1.0.cjs
4 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # Website
2 |
3 | This website is built using [Docusaurus 2](https://v2.docusaurus.io/), a modern static website generator.
4 |
5 | ## Installation
6 |
7 | ```console
8 | yarn install
9 | ```
10 |
11 | ## Local Development
12 |
13 | ```console
14 | yarn start
15 | ```
16 |
17 | This command starts a local development server and open up a browser window. Most changes are reflected live without having to restart the server.
18 |
19 | ## Build
20 |
21 | ```console
22 | yarn build
23 | ```
24 |
25 | This command generates static content into the `build` directory and can be served using any static contents hosting service.
26 |
27 | ## Deployment
28 |
29 | ```console
30 | GIT_USER= USE_SSH=true yarn deploy
31 | ```
32 |
33 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
34 |
--------------------------------------------------------------------------------
/docs/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
3 | };
4 |
--------------------------------------------------------------------------------
/docs/docs-samples/doc2.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: doc2
3 | title: Document Number 2
4 | ---
5 |
6 | This is a link to [another document.](doc3.md) This is a link to an [external page.](http://www.example.com/)
7 |
--------------------------------------------------------------------------------
/docs/docs-samples/doc3.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: doc3
3 | title: This is Document Number 3
4 | ---
5 |
6 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. In ac euismod odio, eu consequat dui. Nullam molestie consectetur risus id imperdiet. Proin sodales ornare turpis, non mollis massa ultricies id. Nam at nibh scelerisque, feugiat ante non, dapibus tortor. Vivamus volutpat diam quis tellus elementum bibendum. Praesent semper gravida velit quis aliquam. Etiam in cursus neque. Nam lectus ligula, malesuada et mauris a, bibendum faucibus mi. Phasellus ut interdum felis. Phasellus in odio pulvinar, porttitor urna eget, fringilla lectus. Aliquam sollicitudin est eros. Mauris consectetur quam vitae mauris interdum hendrerit. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
7 |
8 | Duis et egestas libero, imperdiet faucibus ipsum. Sed posuere eget urna vel feugiat. Vivamus a arcu sagittis, fermentum urna dapibus, congue lectus. Fusce vulputate porttitor nisl, ac cursus elit volutpat vitae. Nullam vitae ipsum egestas, convallis quam non, porta nibh. Morbi gravida erat nec neque bibendum, eu pellentesque velit posuere. Fusce aliquam erat eu massa eleifend tristique.
9 |
10 | Sed consequat sollicitudin ipsum eget tempus. Integer a aliquet velit. In justo nibh, pellentesque non suscipit eget, gravida vel lacus. Donec odio ante, malesuada in massa quis, pharetra tristique ligula. Donec eros est, tristique eget finibus quis, semper non nisl. Vivamus et elit nec enim ornare placerat. Sed posuere odio a elit cursus sagittis.
11 |
12 | Phasellus feugiat purus eu tortor ultrices finibus. Ut libero nibh, lobortis et libero nec, dapibus posuere eros. Sed sagittis euismod justo at consectetur. Nulla finibus libero placerat, cursus sapien at, eleifend ligula. Vivamus elit nisl, hendrerit ac nibh eu, ultrices tempus dui. Nam tellus neque, commodo non rhoncus eu, gravida in risus. Nullam id iaculis tortor.
13 |
14 | Nullam at odio in sem varius tempor sit amet vel lorem. Etiam eu hendrerit nisl. Fusce nibh mauris, vulputate sit amet ex vitae, congue rhoncus nisl. Sed eget tellus purus. Nullam tempus commodo erat ut tristique. Cras accumsan massa sit amet justo consequat eleifend. Integer scelerisque vitae tellus id consectetur.
15 |
--------------------------------------------------------------------------------
/docs/docs-samples/mdx.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: mdx
3 | title: Powered by MDX
4 | ---
5 |
6 | You can write JSX and use React components within your Markdown thanks to [MDX](https://mdxjs.com/).
7 |
8 | export const Highlight = ({children, color}) => ( {children} );
14 |
15 | Docusaurus green and Facebook blue are my favorite colors.
16 |
17 | I can write **Markdown** alongside my _JSX_!
18 |
--------------------------------------------------------------------------------
/docs/docs/animate-presence.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nandorojo/moti/1854a19e5f18446b3ea459f8ba88028fbb6dce2c/docs/docs/animate-presence.md
--------------------------------------------------------------------------------
/docs/docs/animations-repeat.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nandorojo/moti/1854a19e5f18446b3ea459f8ba88028fbb6dce2c/docs/docs/animations-repeat.md
--------------------------------------------------------------------------------
/docs/docs/animations-sequence.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nandorojo/moti/1854a19e5f18446b3ea459f8ba88028fbb6dce2c/docs/docs/animations-sequence.md
--------------------------------------------------------------------------------
/docs/docs/api/components.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: imports
3 | title: Imports
4 | ---
5 |
6 | Currently, moti exports typical React Native components.
7 |
8 | You can import like so:
9 |
10 | ```ts
11 | import { MotiView, MotiText, MotiScrollView, MotiSafeAreaView, MotiImage } from 'moti'
12 | ```
13 |
14 | Which looks like this in use:
15 |
16 | ```tsx
17 |
18 | ```
19 |
--------------------------------------------------------------------------------
/docs/docs/api/motify-svg.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: motify-svg
3 | title: motifySvg()
4 | ---
5 |
6 | ```tsx
7 | import { motifySvg } from 'moti/svg'
8 | ```
9 |
10 | A higher-order component that turns any React Native SVG component into an animated `moti` component. It's the same as `motify`, but for SVG elements.
11 |
12 | ```ts
13 | import { motifySvg } from 'moti/svg'
14 | import { Svg, Rect } from 'react-native-svg'
15 |
16 | const MotiRect = motifySvg(Rect)()
17 | ```
18 |
19 | You can now pass any SVG props to the `animate` property, and they will animate there:
20 |
21 | ```tsx
22 |
36 | ```
37 |
38 | This functionality is compatible with all Moti features, including hooks like `useDynamicAnimation`:
39 |
40 | ```tsx
41 | import { Rect } from 'react-native-svg'
42 | import { motifySvg } from 'moti/svg'
43 | import { useDynamicAnimation } from 'moti'
44 |
45 | const MotiRect = motifySvg(Rect)()
46 |
47 | export default function App() {
48 | const animation = useDynamicAnimation>(() => ({
49 | x: 0,
50 | }))
51 |
52 | return
53 | }
54 | ```
55 |
56 | ## How it works
57 |
58 | Under the hood, `motifySvg` runs `Animated.createAnimatedComponent` for you, so don't call that yourself.
59 |
60 | Instead, just pass a normal `View` (or its equivalent).
61 |
62 | Notice that `motifySvg()` returns a function. At the moment, the function it returns doesn't take any arguments. But I like this composition pattern, so I built the API this way to account for using the returned function in the future.
63 |
--------------------------------------------------------------------------------
/docs/docs/api/motify.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: motify
3 | title: motify()
4 | ---
5 |
6 | ```ts
7 | import { motify } from 'moti'
8 |
9 | const MotifiedComponent = motify(MyComponent)()
10 | ```
11 |
12 | A higher-order component that turns any React Native class component into an animated `moti` component.
13 |
14 | You can now animate like you normally would:
15 |
16 | ```tsx
17 | // height sequence animation
18 |
19 | ```
20 |
21 | Under the hood, `motify` runs `Animated.createAnimatedComponent` for you, so don't pass an `Animated.View`.
22 |
23 | Instead, just pass a normal `View` (or its equivalent).
24 |
25 | Notice that `motify()` returns a function. At the moment, the function it returns doesn't take any arguments. But I like this composition pattern, so I built the API this way to account for using the returned function in the future.
26 |
27 | ## Context
28 |
29 | `motify` is the function that Moti uses under the hood to create its primitives.
30 |
31 | This is the component file in `moti`:
32 |
33 | ```tsx
34 | import { motify } from '../moti'
35 | import {
36 | View as RView,
37 | Text as RText,
38 | Image as RImage,
39 | ScrollView as RScrollView,
40 | SafeAreaView as RSafeAreaView,
41 | } from 'react-native'
42 |
43 | export const View = motify(RView)()
44 | export const Text = motify(RText)()
45 | export const Image = motify(RImage)()
46 | export const ScrollView = motify(RScrollView)()
47 | export const SafeAreaView = motify(RSafeAreaView)()
48 | ```
49 |
50 | That should be all you need for any custom usage.
51 |
52 | - Fernando Rojo
53 |
--------------------------------------------------------------------------------
/docs/docs/api/props.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: props
3 | title: Component Props
4 | sidebar_label: Component Props
5 | ---
6 |
7 | All `moti` components have a few useful props:
8 |
9 | - `animate` Magically animate any value passed here.
10 | - `from` The initial animation styles when the component mounts.
11 | - `exit` Unmount animation styles. This works the exact same as `framer-motion`'s exit prop, and requires wrapping your component with `AnimatePresence`. The only difference is you import `AnimatePresence` from `moti`.
12 | - `transition` Take full control over your animation configuration. You can set options for your entire animation (such as `type: 'timing'`), or set transition options per-style (`translateY: { type: 'timing' }`).
13 | - `exitTransition` The exact same as `transition`, except that it only applies to `exit` animations.
14 | - `state` If you're using the `useAnimationState` hook, you should pass the state it returns to this prop.
15 | - `onDidAnimate` Callback function called after finishing a given animation.
16 | - First argument is the style prop string (`opacity`, `scale`, etc.)
17 | - The second argument is whether the animation `finished` or not (boolean)
18 | - The third argument is `value`, which only exists in a sequence.
19 | - The fourth argument is an event dictionary.
20 | - It has an `attemptedValue` field, which indicates the value that the animation tried to get to.
21 | - This is probably a better field to check for than the third argument.
22 | - As long as the second argument, `finished`, is `true`, then the `attemptedValue` represents that the value that was animated to.
23 |
24 | ## `exitTransition`
25 |
26 |
34 |
50 |
51 |
52 | Define animation configurations for exiting components.
53 |
54 | Options passed to `exitTransition` will only apply to the `exit` prop, when a component is exiting.
55 |
56 | ```jsx
57 |
65 | ```
66 |
67 | By default, `exit` uses `transition` to configure its animations, so this prop is not required.
68 |
69 | However, if you pass `exitTransition`, it will override `transition` for exit animations.
70 |
71 | To see how to use this prop, see the `transition` docs.
72 |
--------------------------------------------------------------------------------
/docs/docs/api/transforms.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: transforms
3 | title: Transforms
4 | ---
5 |
6 | Moti has the same API for transforms as a normal React Native component.
7 |
8 | It also comes with some added convenience.
9 |
10 | Like always, you can use a `transform` array:
11 |
12 | ```tsx
13 |
21 | ```
22 |
23 | Writing a transform array can be bulky. You can also pass your transforms directly:
24 |
25 | ```tsx
26 |
34 | ```
35 |
36 | ## Using multiple transforms
37 |
38 | If you're using multiple transforms, be sure to retain their order inside of each prop.
39 |
40 | ```tsx
41 | // ✅ scale first, then translateX
42 |
52 | ```
53 |
54 | > This only works with Reanimated `2.3.0`+. For older versions, scroll down.
55 |
56 | This will break your animation, since they have different orders:
57 |
58 | ```tsx
59 | // 🚨 from & animate don't have the same orders for transforms!
60 | // this will break
61 |
71 | ```
72 |
73 | If you prefer to use an array for multiple transforms, you can do that too. Be sure to retain the order of your transforms across props.
74 |
75 | ```tsx
76 |
84 | ```
85 |
86 | ## Using multiple transforms (on old versions of Reanimated)
87 |
88 | > The following only applies if you're using Reanimated `2.2` or older. As of `2.3.0`, you can use multiple transforms without an array if you want.
89 |
90 | If you're using multiple transforms together, such as `scale` and `translateY`, and you're using Reanimated `2.2` or older, you _must_ use an array.
91 |
92 | This example is okay, since `scale` is the only transform:
93 |
94 | ```tsx
95 | // ✅ if you're only using one transform
96 |
104 | ```
105 |
106 | But this won't work:
107 |
108 | ```tsx
109 | // 🚨 if you're only using multiple transforms, use an array
110 |
120 | ```
121 |
122 | Instead, pass `transform` arrays.
123 |
124 | ```tsx
125 | // ✅ for multiple transforms, use an array
126 |
141 | ```
142 |
143 | Make sure the order in the `from` and `animate` prop is the same. In this case, we put `scale` before `translateY`.
144 |
145 | ## Sequences
146 |
147 | Sequences for `transform` works like normal. Simply pass an array in place of your value
148 |
149 | ```tsx
150 |
155 | ```
156 |
157 | That's equivalent to doing this:
158 |
159 | ```tsx
160 |
165 | ```
166 |
--------------------------------------------------------------------------------
/docs/docs/examples/action-menu.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: action-menu
3 | title: Action Menu
4 | hide_title: true
5 | hide_table_of_contents: true
6 | ---
7 |
8 |
9 |
--------------------------------------------------------------------------------
/docs/docs/examples/animate-presence.md:
--------------------------------------------------------------------------------
1 | ---
2 | hide_title: true
3 | hide_table_of_contents: true
4 | sidebar_label: Animate Presence
5 | ---
6 |
7 |
8 |
9 |
66 |
--------------------------------------------------------------------------------
/docs/docs/examples/auto-height.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: auto-height
3 | title: Accordion (New!)
4 | ---
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/docs/docs/examples/exit-before-enter.md:
--------------------------------------------------------------------------------
1 | ---
2 | hide_title: true
3 | hide_table_of_contents: true
4 | sidebar_label: Exit Before Enter
5 | ---
6 |
7 |
8 |
--------------------------------------------------------------------------------
/docs/docs/examples/hello-world.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: hello-world
3 | title: Hello World
4 | hide_table_of_contents: true
5 | ---
6 |
7 |
8 |
9 |
63 |
--------------------------------------------------------------------------------
/docs/docs/examples/image-gallery.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Image Gallery
3 | hide_title: true
4 | hide_table_of_contents: true
5 | ---
6 |
7 |
8 |
--------------------------------------------------------------------------------
/docs/docs/examples/loop.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: loop
3 | title: Loop Animation
4 | hide_table_of_contents: true
5 | ---
6 |
7 | Create a loop animation of a box that goes up and down infinitely.
8 |
9 | :::tip
10 | Loop animations cannot be changed on the fly. If you want to restart a loop, you need to update a component's `key` prop.
11 |
12 | See the explanation at the bottom.
13 | :::
14 |
15 |
16 |
17 | ### Warning
18 |
19 | It's worth noting that using the `loop` cannot be changed. For example, you can't set `loop` to be `true` at a random time. It must be `true` when the component mounts, and stay true.
20 |
21 | Similarly, the styles passed to `from` and `animate` must exist when the component mounts, and cannot change over time. If they do, we **cannot** guarantee a working loop animation.
22 |
23 | ### Why?
24 |
25 | We're using Reanimated's `withRepeat` function under the hood, which repeats back to the **previous value**. That means that if you change the value on the fly, that is where it will repeat back to.
26 |
27 | If you want a loop that's constant, make sure you set `loop: true` when the component mounts, and make sure that your `from` and `animate` prop **do not** change throughout the component's lifecycle.
28 |
29 | ### Sequences
30 |
31 | Sequence animations cannot be paired with `loop: true` or with `repeat`.
32 |
--------------------------------------------------------------------------------
/docs/docs/examples/menu-icon.md:
--------------------------------------------------------------------------------
1 | ---
2 | hide_title: true
3 | hide_table_of_contents: true
4 | sidebar_label: Animated Menu Icon
5 | ---
6 |
7 |
8 |
--------------------------------------------------------------------------------
/docs/docs/examples/skeleton.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Skeleton
3 | hide_title: true
4 | hide_table_of_contents: true
5 | ---
6 |
7 |
8 |
--------------------------------------------------------------------------------
/docs/docs/examples/variants.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: variants
3 | title: Variants
4 | hide_title: true
5 | hide_table_of_contents: true
6 | ---
7 |
8 |
9 |
--------------------------------------------------------------------------------
/docs/docs/hello-world.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: hello-world
3 | title: Hello World
4 | hide_table_of_contents: true
5 | ---
6 |
7 |
8 |
9 |
63 |
--------------------------------------------------------------------------------
/docs/docs/installation.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: installation
3 | title: Installation
4 | ---
5 |
6 | Moti uses [Reanimated 3](https://docs.swmansion.com/react-native-reanimated/) under the hood to drive high-performance animations on iOS, Android and Web.
7 |
8 | ## Starter project
9 |
10 | If you're starting a project from scratch, or just want to play around, you can use the Expo + Moti [starter](https://github.com/expo/examples/tree/master/with-moti).
11 |
12 | `npx create-react-native-app -t with-moti`
13 |
14 | ## Install Moti
15 |
16 | First, install `moti` in your app:
17 |
18 | ### yarn
19 |
20 | ```sh
21 | yarn add moti
22 | ```
23 |
24 | ### npm
25 |
26 | ```sh
27 | npm i moti --legacy-peer-deps
28 | ```
29 |
30 | ## Install Reanimated 3+
31 |
32 | Moti requires that you install `react-native-reanimated`. Version 2 and 3 are both compatible.
33 |
34 |
35 |
36 | View Reanimated compatibility options
37 |
38 |
39 | Moti `0.17.x` requires Reanimated `2.3.0` or higher. This version is compatible with Expo SDK 44.
40 |
41 | Moti `0.16.x` is compatible with Reanimated `2.2.0`. This is compatible with Expo SDK 43.
42 |
43 | Moti `0.8.x` and higher requires at least Reanimated v2 stable (`2.0.0` or higher). This version is compatible with Expo starting SDK 41.
44 |
45 |
46 |
47 | ### If you're using Expo
48 |
49 | Please follow the [Expo instructions](https://docs.expo.io/versions/latest/sdk/reanimated) for installing `react-native-reanimated`.
50 |
51 | ### If you aren't using Expo
52 |
53 | Please follow Reanimated's [installation instructions](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started/#installation).
54 |
55 | ## Import Reanimated
56 |
57 | Lastly, add this to the top of your `App.tsx`:
58 |
59 | ```ts
60 | import 'react-native-reanimated'
61 | import 'react-native-gesture-handler'
62 | ```
63 |
64 | If you're using Next.js or [Solito](https://solito.dev), you should not add this import to your `_app.tsx`. See the Web instructions below for more.
65 |
66 | ## Web support
67 |
68 | Please see the following guides:
69 |
70 | - [Expo Web](/web)
71 | - [Next.js](/next)
72 | - [React Native Web](/web)
73 |
74 | ## Hermes
75 |
76 | Moti works with Hermes as of version [`0.22`](https://github.com/nandorojo/moti/releases/tag/v0.22.0). It's been tested with React Native `0.70+`.
77 |
78 |
79 |
80 | Click here if you're using React Native 0.63.x with Hermes
81 |
82 |
83 | Moti uses `Proxy` under the hood, which is not supported on older versions of Hermes (see [hermes#33](https://github.com/facebook/hermes/issues/33)). Follow the steps below if you're using Hermes.
84 |
85 | ### If you're using React Native 0.63.x
86 |
87 | Install `v0.5.2-rc.1` of Hermes:
88 |
89 | ```bash npm2yarn
90 | npm install hermes-engine@v0.5.2-rc1
91 | ```
92 |
93 | Relevant release notes for v0.5.2-rc1 [here](https://github.com/facebook/hermes/releases/tag/v0.5.2-rc1).
94 |
95 | ### If you're using React Native 0.64.x
96 |
97 | Upgrade Hermes to `0.7.*`.
98 |
99 | ## Possible errors
100 |
101 | ### Property 'Proxy' doesn't exist
102 |
103 | As mentioned in this [Moti issue](https://github.com/nandorojo/moti/issues/13), if you don't install the correct version of Hermes, you might see this error:
104 |
105 | ```sh
106 | Property 'Proxy' doesn't exist, js engine: hermes [Mon Feb 08 2021 19:21:54.427] ERROR Invariant Violation: Module AppRegistry is not a registered callable module (calling runApplication), js engine: hermes
107 | ```
108 |
109 |
110 |
111 | ## Create your first animation
112 |
113 | ```tsx
114 | import { MotiView } from 'moti'
115 |
116 | export const FadeIn = () => (
117 |
122 | )
123 | ```
124 |
125 | Your component will now fade in on the native thread at 60 FPS.
126 |
--------------------------------------------------------------------------------
/docs/docs/interactions/eslint.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: eslint
3 | title: ESLint usage
4 | ---
5 |
6 | It is recommended to use a dependency array with all Moti Interaction hooks, just like `useMemo` and `useCallback`.
7 |
8 | To enforce this with your ESLint plugin, you can use the `additionalHooks` field in your ESLint config:
9 |
10 | ```json
11 | {
12 | "rules": {
13 | // ...
14 | "react-hooks/exhaustive-deps": [
15 | "error",
16 | {
17 | "additionalHooks": "(useMotiPressableTransition|useMotiPressable|useMotiPressables|useMotiPressableAnimatedProps|useInterpolateMotiPressable)"
18 | }
19 | ]
20 | }
21 | }
22 | ```
23 |
24 | This assumes you've already installed the `react-hooks` eslint [plugin](https://www.npmjs.com/package/eslint-plugin-react-hooks).
25 |
--------------------------------------------------------------------------------
/docs/docs/interactions/pressable.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: pressable
3 | title:
4 | ---
5 |
6 | A near-replacement for React Native's `Pressable` component, with animations run on the native thread.
7 |
8 | ```tsx
9 | import { MotiPressable } from 'moti/interactions'
10 | import { useCallback } from 'react'
11 |
12 | export const Pressable = () => {
13 | const onPress = () => Linking.openURL('beatgig.com')
14 |
15 | return (
16 | ({ hovered, pressed }) => {
20 | 'worklet'
21 |
22 | return {
23 | opacity: hovered || pressed ? 0.5 : 1,
24 | }
25 | },
26 | []
27 | )}
28 | />
29 | )
30 | }
31 | ```
32 |
33 | ## Customized transitions
34 |
35 | You can also use the `hovered` and `pressed` state to customize your `transition` prop.
36 |
37 | For example, if you want to delay your animations when someone releases your button, you can pass a function to `transition`:
38 |
39 | ```tsx
40 | import { MotiPressable } from 'moti/interactions'
41 | import { useCallback } from 'react'
42 |
43 | export const Pressable = () => {
44 | const onPress = () => Linking.openURL('beatgig.com')
45 |
46 | return (
47 | ({ hovered, pressed }) => {
51 | 'worklet'
52 |
53 | return {
54 | opacity: hovered || pressed ? 0.5 : 1,
55 | }
56 | },
57 | []
58 | )}
59 | transition={useMemo(
60 | () => ({ hovered, pressed }) => {
61 | 'worklet'
62 |
63 | return {
64 | delay: hovered || pressed ? 0 : 100,
65 | }
66 | },
67 | []
68 | )}
69 | />
70 | )
71 | }
72 | ```
73 |
74 | Here, `opacity` will stay at `0.5` for an extra `100` milliseconds after hovering/pressing both become `false`.
75 |
76 | ---
77 |
78 | For more info, see the [interactions overview](/interactions/overview).
79 |
80 | # Props
81 |
82 | ````tsx
83 | export type MotiPressableProps = {
84 | /*
85 | * Worklet that returns the `animated` prop. Or, just a normal `animate` prop, similar to `MotiView`.
86 | *
87 | * It's recomended that you memoize this prop with `useCallback`.
88 | *
89 | * ```tsx
90 | * ({
92 | * opacity: hovered ? 0.8 : 1
93 | * }), [])}
94 | * />
95 | * ```
96 | *
97 | * @worklet
98 | */
99 | animate?: MotiPressableProp
100 | onPress?: () => void
101 | onPressIn?: () => void
102 | onPressOut?: () => void
103 | onHoverIn?: () => void
104 | onHoverOut?: () => void
105 | onLongPress?: () => void
106 | hitSlop?: Insets
107 | /*
108 | * (Optional) Unique ID to identify this interaction.
109 | *
110 | * If set, then other children of this component can access the interaction state
111 | */
112 | id?: string
113 | disabled?: boolean
114 | containerStyle?: ViewStyle
115 | dangerouslySilenceDuplicateIdsWarning?: boolean
116 | /*
117 | * (Optional) a custom shared value to track the `pressed` state.
118 | * This lets you get access to the pressed state from outside of the component in a controlled fashion.
119 | */
120 | pressedValue?: Animated.SharedValue
121 | /*
122 | * (Optional) a custom shared value to track the `pressed` state.
123 | * This lets you get access to the pressed state from outside of the component in a controlled fashion.
124 | */
125 | hoveredValue?: Animated.SharedValue
126 | } & Pick<
127 | ComponentProps,
128 | 'children' | 'exit' | 'from' | 'transition' | 'exitTransition' | 'style'
129 | >
130 | ````
131 |
--------------------------------------------------------------------------------
/docs/docs/interactions/use-pressable-animated-props.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: use-pressable-animated-props
3 | title: useMotiPressableAnimatedProps()
4 | ---
5 |
6 | ```tsx
7 | import { useMotiPressableAnimatedProps } from 'moti/interactions'
8 | ```
9 |
10 | If you've used `useAnimatedProps` from `react-native-reanimated` before, then this will look familiar. It serves the same purpose, with the added feature of using the `hovered` and `pressed` states.
11 |
12 | ## Usage
13 |
14 | ```tsx
15 | const Menu = () => {
16 | return (
17 |
18 |
19 |
20 |
21 | )
22 | }
23 |
24 | const MenuItems = () => {
25 | const animatedProps = useMotiPressableAnimatedProps(({ hovered }) => {
26 | 'worklet'
27 |
28 | return {
29 | pointerEvents: hovered ? 'auto' : 'none',
30 | }
31 | }, [])
32 | return (
33 | {/* Menu items here...*/}
34 | )
35 | }
36 | ```
37 |
38 | ## API
39 |
40 | The following usages are valid:
41 |
42 | ```tsx
43 | useMotiPressableAnimatedProps(factory, deps?)
44 | ```
45 |
46 | If there's a unique MotiPressable component with an `id` prop as the parent, then pass `id` as the first argument:
47 |
48 | ```tsx
49 | useMotiPressableAnimatedProps(id, factory, deps?)
50 | ```
51 |
52 | ### Arguments
53 |
54 | - `factory` is a worklet that receives the interaction state as the first argument, and returns props that should be animated.
55 | - `id` is a unique string to identify the parent `MotiPressable` component whose interaction state you're trying to access.
56 | - `deps` is a dependency array, just like `useMemo`
57 |
58 | ### Returns
59 |
60 | Animated props, to be passed a Reanimated or Moti component's `animatedProps` prop.
61 |
62 | ## Web
63 |
64 | `animatedProps` cannot be used with `animate` on the same prop on Web.
65 |
66 | ```tsx
67 | // 🚨 bad
68 | const animateProps = useMotiPressableAnimatedProps(...)
69 |
70 |
71 |
72 |
73 | ```
74 |
75 | If you need to do both, please split your usage into two components; one that receives the `animate` prop, and another that receives `animateProps`. This is a limitation in Reanimated 3.
76 |
77 | ```tsx
78 | // ✅ good
79 | const animateProps = useMotiPressableAnimatedProps(...)
80 |
81 |
82 |
83 |
84 | ```
85 |
--------------------------------------------------------------------------------
/docs/docs/interactions/use-pressable-interpolate.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: use-pressable-interpolate
3 | title: useInterpolateMotiPressable()
4 | ---
5 |
6 | ```tsx
7 | import { useInterpolateMotiPressable } from 'moti/interactions'
8 | ```
9 |
10 | In the rare case that you want more customization over pressable state, you can use `useInterpolateMotiPressable`. As the name implies, it lets you interpolate based on the current interaction state of a parent `MotiPressable`.
11 |
12 | ## Usage
13 |
14 | ```tsx
15 | import { useSharedValue } from 'react-native-reanimated'
16 | import { useInterpolateMotiPressable } from 'moti/interactions'
17 |
18 | // in your component
19 | const mySharedValue = useSharedValue(0)
20 | useInterpolateMotiPressable(({ pressed }) => {
21 | 'worklet'
22 |
23 | mySharedValue.value = pressed ? 1 : 0
24 | })
25 | ```
26 |
27 | ## Access a unique parent
28 |
29 | If you're passing a unique `id` prop to your pressable, you can also isolate this hook to that pressable.
30 |
31 | Say the parent pressable has `id="menu"`, and you want to isolate this hook to the `menu` pressable:
32 |
33 | ```tsx
34 |
37 | ```
38 |
39 | Then, in the `Item` component:
40 |
41 | ```tsx
42 | const mySharedValue = useSharedValue(0)
43 | useInterpolateMotiPressable('menu', ({ pressed }) => {
44 | 'worklet'
45 |
46 | mySharedValue.value = pressed ? 1 : 0
47 | })
48 | ```
49 |
50 | ## TypeScript support
51 |
52 | `useInterpolateMotiPressable` returns an `Animated.DerivedValue`. You can also type it with a generic:
53 |
54 | ```tsx
55 | const swipePosition = useSharedValue(0)
56 | const interpolatedValue = useInterpolateMotiPressable<{ done: boolean }>(
57 | 'menu',
58 | ({ pressed }) => {
59 | 'worklet'
60 |
61 | return {
62 | done: swipePosition.value > 50 && !pressed,
63 | }
64 | }
65 | )
66 | ```
67 |
68 | ## Use the result of the interpolation
69 |
70 | Just like any derived value, you can read the value it returns with `.value`:
71 |
72 | ```tsx
73 | const swipePosition = useSharedValue(0)
74 | const interpolatedValue = useInterpolateMotiPressable<{ done: boolean }>(
75 | 'menu',
76 | ({ pressed }) => {
77 | 'worklet'
78 |
79 | return {
80 | done: swipePosition.value > 50 && !pressed,
81 | }
82 | }
83 | )
84 |
85 | // then, in some worklet
86 | const done = state.value.done
87 | ```
88 |
89 | ## Performance
90 |
91 | Similar to `useMemo`, you can also pass in a dependency array as the last argument:
92 |
93 | ```tsx
94 | const swipePosition = useSharedValue(0)
95 | const interpolatedValue = useInterpolateMotiPressable(({ pressed }) => {
96 | 'worklet'
97 |
98 | return {
99 | done: swipePosition.value > 50 && !pressed,
100 | }
101 | }, [])
102 | ```
103 |
104 | ## API
105 |
106 | The following usages are valid:
107 |
108 | ```tsx
109 | useInterpolateMotiPressable(factory, deps?)
110 | ```
111 |
112 | If there's a unique MotiPressable component with an `id` prop as the parent:
113 |
114 | ```tsx
115 | useInterpolateMotiPressable(id, factory, deps?)
116 | ```
117 |
118 | ### Arguments
119 |
120 | - `factory` is a worklet that receives the interaction state as the first argument, and can return whatever it wants.
121 | - `id` is a unique string to identify the parent `MotiPressable` component whose interaction state you're trying to access.
122 | - `deps` is a dependency array, just like `useMemo`
123 |
124 | ### Returns
125 |
126 | A derived value, which you can use in Reanimated worklets.
127 |
--------------------------------------------------------------------------------
/docs/docs/interactions/use-pressable-transition.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: use-pressable-transition
3 | title: useMotiPressableTransition()
4 | ---
5 |
6 | ```tsx
7 | import { useMotiPressableTransition } from 'moti/interactions'
8 | ```
9 |
10 | `useMotiPressableTransition` lets you motify any Moti component's `transition` prop based on a parent's interaction state.
11 |
12 | Please refer to the Moti `transition` prop options to see what this hook should return.
13 |
14 | Example:
15 |
16 | ```tsx
17 | const transition = useMotiPressableTransition(({ pressed, hovered }) => {
18 | 'worklet'
19 |
20 | if (pressed) {
21 | return {
22 | type: 'timing',
23 | }
24 | }
25 |
26 | return {
27 | type: 'spring',
28 | delay: 50,
29 | }
30 | })
31 |
32 | return
33 | ```
34 |
35 | ## Usage
36 |
37 | Wrap your component with `MotiPressable`.
38 |
39 | ```tsx
40 |
41 |
42 |
43 | ```
44 |
45 | Then, in the `Item` component:
46 |
47 | ```tsx
48 | const Item = () => {
49 | const transition = useMotiPressableTransition(({ pressed }) => {
50 | 'worklet'
51 |
52 | if (pressed) {
53 | return {
54 | type: 'timing',
55 | }
56 | }
57 |
58 | return {
59 | type: 'spring',
60 | delay: 50,
61 | }
62 | })
63 |
64 | const state = useMotiPressableState(({ pressed }) => {
65 | return {
66 | translateY: pressed ? -10 : 0,
67 | }
68 | })
69 |
70 | return
71 | }
72 | ```
73 |
74 | ### Access a unique ID
75 |
76 | You can also access a pressable via unique ID. Say you have mutliple nested pressables:
77 |
78 | ```tsx
79 |
80 |
81 |
82 |
83 |
84 | ```
85 |
86 | By adding `id="list"`, we can now access that unique component's interaction state.
87 |
88 | Then, in the `Item` component, add `list` as the first argument of `useMotiPressableTransition`:
89 |
90 | ```tsx
91 | const state = useMotiPressableTransition('list', ({ pressed }) => {
92 | 'worklet'
93 |
94 | return {
95 | opactiy: pressed ? 0.5 : 1,
96 | }
97 | })
98 |
99 | return
100 | ```
101 |
102 | This lets you uniquely transition based on interactionns made on the `list` pressable.
103 |
104 | ## Performance
105 |
106 | This hook runs on the native thread and triggers zero re-renders. Like all things moti, it has great performance out-of-the-box.
107 |
108 | Similar to `useMemo`, you can also pass in a dependency array as the last argument to reduce updates:
109 |
110 | ```tsx
111 | const state = useMotiPressableTransition(
112 | 'list',
113 | ({ pressed, hovered }) => {
114 | 'worklet'
115 |
116 | return {
117 | opactiy: pressed && !loading ? 0.5 : 1,
118 | }
119 | },
120 | [loading] // pass an empty array if there are no dependencies
121 | )
122 | ```
123 |
124 | ## API
125 |
126 | The following usages are valid:
127 |
128 | ```tsx
129 | useMotiPressableTransition(factory, deps?)
130 | ```
131 |
132 | If there's a unique MotiPressable component with an `id` prop as the parent:
133 |
134 | ```tsx
135 | useMotiPressableTransition(id, factory, deps?)
136 | ```
137 |
138 | ### Arguments
139 |
140 | - `factory` is a worklet that receives the interaction state as the first argument, and returns a transition object.
141 | - `id` is a unique string to identify the parent `MotiPressable` component whose interaction state you're trying to access.
142 | - `deps` is a dependency array, just like `useMemo`
143 |
144 | ### Returns
145 |
146 | A Moti `transition` object, meant to be passed to any Moti component's `transition` prop.
147 |
--------------------------------------------------------------------------------
/docs/docs/interactions/use-pressable.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: use-pressable
3 | title: useMotiPressable()
4 | ---
5 |
6 | ```tsx
7 | import { useMotiPressable } from 'moti/interactions'
8 | ```
9 |
10 | `useMotiPressable` lets you access the interaction state of a parent `MotiPressable` component.
11 |
12 | (If you need to access the interaction state of multiple `MotiPressable` parents, use `useMotiPressables` instead.)
13 |
14 | ## Usage
15 |
16 | Wrap your component with `MotiPressable`.
17 |
18 | ```tsx
19 |
20 |
21 |
22 | ```
23 |
24 | Then, in the `Item` component:
25 |
26 | ```tsx
27 | const Item = () => {
28 | const state = useMotiPressable(({ pressed }) => {
29 | 'worklet'
30 |
31 | return {
32 | opacity: pressed ? 0.5 : 1,
33 | }
34 | })
35 |
36 | return
37 | }
38 | ```
39 |
40 | ### Access a unique ID
41 |
42 | You can also access a pressable via unique ID. Say you have mutliple nested pressables:
43 |
44 | ```tsx
45 |
46 |
47 |
48 |
49 |
50 | ```
51 |
52 | By adding `id="list"`, we can now access that unique component's interaction state.
53 |
54 | Then, in the `Item` component, add `list` as the first argument of `useMotiPressable`:
55 |
56 | ```tsx
57 | const state = useMotiPressable('list', ({ pressed }) => {
58 | 'worklet'
59 |
60 | return {
61 | opacity: pressed ? 0.5 : 1,
62 | }
63 | })
64 |
65 | return
66 | ```
67 |
68 | ## Performance
69 |
70 | This hook runs on the native thread and triggers zero re-renders. Like all things moti, it has great performance out-of-the-box.
71 |
72 | Similar to `useMemo`, you can also pass in a dependency array as the last argument to reduce updates:
73 |
74 | ```tsx
75 | const state = useMotiPressable(
76 | 'list',
77 | ({ pressed, hovered }) => {
78 | 'worklet'
79 |
80 | return {
81 | opacity: pressed && !loading ? 0.5 : 1,
82 | }
83 | },
84 | [loading] // pass an empty array if there are no dependencies
85 | )
86 | ```
87 |
88 | ## API
89 |
90 | The following usages are valid:
91 |
92 | ```tsx
93 | useMotiPressable(factory, deps?)
94 | ```
95 |
96 | If there's a unique MotiPressable component with an `id` prop as the parent:
97 |
98 | ```tsx
99 | useMotiPressable(id, factory, deps?)
100 | ```
101 |
102 | ### Arguments
103 |
104 | - `factory` is a worklet that receives the interaction state as the first argument, and returns a style object.
105 | - `id` is a unique string to identify the parent `MotiPressable` component whose interaction state you're trying to access.
106 | - `deps` is a dependency array, just like `useMemo`
107 |
108 | ### Returns
109 |
110 | A Moti `state` object, meant to be passed to any Moti component's `state` prop.
111 |
--------------------------------------------------------------------------------
/docs/docs/interactions/use-pressables.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: use-pressables
3 | title: useMotiPressables()
4 | ---
5 |
6 | ```tsx
7 | import { useMotiPressables } from 'moti/interactions'
8 | ```
9 |
10 | `useMotiPressables` lets you access the interaction state of multiple `MotiPressable` parents. This lets you combine multiple different interactions and build animation states with ease.
11 |
12 | ## Usage
13 |
14 | ```tsx
15 | const ListItem = ({ id }) => {
16 | const state = useMotiPressables((containers) => {
17 | 'worklet'
18 |
19 | // access items by their unique IDs
20 | const list = containers.list.value
21 | const item = containers[`item-${id}`].value
22 |
23 | let opacity = 1
24 |
25 | if (list.hovered && !item.hovered) {
26 | opacity = 0.5
27 | }
28 |
29 | return {
30 | opacity,
31 | }
32 | }, [])
33 |
34 | return
35 | }
36 | ```
37 |
38 | ## Walk Through
39 |
40 | Imagine we have a list. The outer container component is a `MotiPressable`, and each item has a `MotiPressable` too.
41 |
42 | ```tsx
43 |
44 | {items.map(id =>
45 |
46 |
47 |
48 | )}
49 |
50 | ```
51 |
52 | Now, the `ListItem` component can call `useMotiPressables` instead of `useMotiPressable`.
53 |
54 | Say we want to make all items fade away when you hover over the list, _except_ for the actual item you're hovering.
55 |
56 | ```tsx
57 | import { useMotiPressables } from 'moti/interactions'
58 |
59 | const ListItem = ({ id }) => {
60 | const state = useMotiPressables((containers) => {
61 | 'worklet'
62 |
63 | // access items by their unique IDs
64 | const list = containers.list.value
65 | const item = containers[`item-${id}`].value
66 |
67 | let opacity = 1
68 |
69 | if (list.hovered && !item.hovered) {
70 | opacity = 0.5
71 | }
72 |
73 | return {
74 | opacity,
75 | }
76 | }, [])
77 | return
78 | }
79 | ```
80 |
81 | ## Performance
82 |
83 | This hook runs on the native thread and triggers zero re-renders. Like all things moti, it has great performance out-of-the-box.
84 |
85 | Similar to `useMemo`, you can also pass in a dependency array as the last argument to reduce updates:
86 |
87 | ```tsx
88 | const state = useMotiPressables(
89 | (containers) => {
90 | 'worklet'
91 |
92 | const list = containers.list.value
93 |
94 | return {
95 | opacity: list.pressed && !loading ? 0.5 : 1,
96 | }
97 | },
98 | [loading] // pass an empty array if there are no dependencies
99 | )
100 | ```
101 |
102 | ## API
103 |
104 | The following usages are valid:
105 |
106 | ```tsx
107 | useMotiPressables(factory, deps?)
108 | ```
109 |
110 | ### Arguments
111 |
112 | - `factory` is a worklet that returns a style object.
113 | - Its only argument is a `containers` dictionary, whose keys are the `id` props of the containers, and the values are the derived values of each interaction state.
114 | - `id` is a unique string to identify the parent `MotiPressable` component whose interaction state you're trying to access.
115 | - `deps` is a dependency array, just like `useMemo`
116 |
117 | ### Returns
118 |
119 | A Moti `state` object, meant to be passed to any Moti component's `state` prop.
120 |
--------------------------------------------------------------------------------
/docs/docs/next-js.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: next
3 | title: Moti + Next.js
4 | sidebar_label: Next.js Usage
5 | ---
6 |
7 | There are 2 quick steps to getting Moti setup in a Next.js app.
8 |
9 | ## Step 1
10 |
11 | Add `moti` to transpile modules.
12 |
13 | ```sh
14 | yarn add next-transpile-modules
15 | ```
16 |
17 | Your `next.config.js` file should look something like this:
18 |
19 | ```js
20 | const { withExpo } = require('@expo/next-adapter')
21 | const withFonts = require('next-fonts')
22 | const withImages = require('next-images')
23 | const withPlugins = require('next-compose-plugins')
24 |
25 | const withTM = require('next-transpile-modules')(['moti'])
26 | module.exports = withPlugins(
27 | [withTM, withFonts, withImages, [withExpo, { projectRoot: __dirname }]],
28 | {
29 | // ...
30 | }
31 | )
32 | ```
33 |
34 | ## Step 2
35 |
36 | Add the `raf` polyfill.
37 |
38 | `yarn add raf`
39 |
40 | Then add this in `pages/_app.js`
41 |
42 | ```jsx
43 | import 'raf/polyfill' // add this at the top
44 |
45 | export default function App({ Component, pageProps }) {
46 | return
47 | }
48 | ```
49 |
50 | We're going to use `requestAnimationFrame` with Reanimated web, so that polyfill makes it usable with server-side rendering frameworks.
51 |
--------------------------------------------------------------------------------
/docs/docs/resources/videos.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: videos
3 | title: Videos
4 | ---
5 |
6 | ## Zero to $10m with React Native + Next.js
7 |
8 | [Fernando Rojo](https://fernandorojo.co), the creator of Moti, gave a talk at Next.js Conf 2021 about using React Native + Next.js.
9 |
10 | You can skip to `6:30` for discussion about Moti.
11 |
12 |
13 |
14 | For more info on Fernando's talk, head to [fernandorojo.co/conf](https://fernandorojo.co/conf).
15 |
16 | ## Moti Animations in 4 min
17 |
18 | A great overview of Moti by [Arunaud, AKA Evening Kid](https://twitter.com/eveningkid).
19 |
20 |
21 |
22 | Check out Evening Kid's [YouTube channel](https://www.youtube.com/c/eveningkid) for more React Native content.
23 |
24 | ## Can Moti simplify React Native animations?
25 |
26 | [Evening Kid](https://twitter.com/eveningkid) takes us through Moti's potential to simplify performant animations.
27 |
28 |
29 |
30 | ## Live Stream: Fernando Rojo & Catalin Miron
31 |
32 | When Fernando Rojo released Moti in February 2021, [Catalin Miron](https://twitter.com/mironcatalin) invited him onto his popular YouTube channel for a (nearly) 2 hour walk through.
33 |
34 |
35 |
36 | ## Catalin Miron Tutorials
37 |
38 | [Catalin Miron](https://twitter.com/mironcatalin) has a number of great tutorials that show the power of Moti.
39 |
40 | ### Phone Ring Indicator Wave with Moti
41 |
42 |
43 |
44 | ### Loading indicator with Moti
45 |
46 |
47 |
48 | ### 🎛 Custom Animated Switch component with Moti
49 |
50 |
51 |
--------------------------------------------------------------------------------
/docs/docs/starter.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: starter
3 | title: Moti Starter Project
4 | sidebar_label: Starter Project
5 | ---
6 |
7 | If you're starting a project from scratch, or just want to play around, you can use the Expo + Moti [starter template](https://github.com/expo/examples/tree/master/with-moti).
8 |
9 | ```sh
10 | npx create-react-native-app -t with-moti
11 | ```
12 |
13 | Thanks to Evan Bacon for making that!
14 |
--------------------------------------------------------------------------------
/docs/docs/svg.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: SVG Animations
3 | ---
4 |
5 | As of `0.23`, Moti has built-in support for animating SVG elements with [`motifySvg`](/api/motify-svg).
6 |
--------------------------------------------------------------------------------
/docs/docs/web.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: web
3 | title: Web Support
4 | sidebar_label: Web Support
5 | ---
6 |
7 | Moti works on all platforms, including Web. Make sure you've installed `react-native-web`.
8 |
9 | ## Expo Web support
10 |
11 | Expo Web should work out of the box.
12 |
13 | ## Expo Router / Metro Web
14 |
15 | You'll need to add `mjs` to your `sourceExts` in `metro.config.js`. For example:
16 |
17 | ```js
18 | const { getDefaultConfig } = require('expo/metro-config');
19 |
20 | const config = getDefaultConfig(__dirname);
21 |
22 | config.resolver.assetExts.push(
23 | 'mjs’
24 | );
25 |
26 | module.exports = config;
27 | ```
28 |
29 | ### Troubleshooting
30 |
31 | If you get the following Reanimated error in your console: `ReferenceError: _frameTimestamp is not defined`, you can add add this to `App.js` at the top:
32 |
33 | ```ts
34 | import { Platform } from 'react-native'
35 | if (Platform.OS === 'web') {
36 | global._frameTimestamp = null
37 | }
38 | ```
39 |
40 | This should go away in Reanimated v3. See [react-native-reanimated#3355](https://github.com/software-mansion/react-native-reanimated/issues/3355).
41 |
42 | ## Vanilla React Native Web
43 |
44 | You may need to add a custom `webpack.config.js` to your project and export the config from `@expo/webpack-config`.
45 |
46 |
70 |
--------------------------------------------------------------------------------
/docs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "docs",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "docusaurus": "docusaurus",
7 | "start": "NODE_OPTIONS='--openssl-legacy-provider' docusaurus start",
8 | "build": "NODE_OPTIONS='--openssl-legacy-provider' docusaurus build",
9 | "swizzle": "docusaurus swizzle",
10 | "deploy": "docusaurus deploy",
11 | "serve": "docusaurus serve",
12 | "clear": "docusaurus clear"
13 | },
14 | "dependencies": {
15 | "@docusaurus/core": "^2",
16 | "@docusaurus/preset-classic": "^2",
17 | "@mdx-js/react": "^1.6.21",
18 | "clsx": "^1.1.1",
19 | "react": "17.0.1",
20 | "react-dom": "17.0.1"
21 | },
22 | "browserslist": {
23 | "production": [
24 | ">0.5%",
25 | "not dead",
26 | "not op_mini all"
27 | ],
28 | "development": [
29 | "last 1 chrome version",
30 | "last 1 firefox version",
31 | "last 1 safari version"
32 | ]
33 | },
34 | "packageManager": "yarn@4.1.0"
35 | }
36 |
--------------------------------------------------------------------------------
/docs/sidebars.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | someSidebar: {
3 | Welcome: ['welcome', 'installation', 'starter', 'hello-world'],
4 | Usage: ['animations', 'api/transforms', 'reanimated', 'svg'],
5 | Examples: [
6 | 'examples/hello-world',
7 | 'examples/animate-presence',
8 | 'examples/image-gallery',
9 | 'examples/menu-icon',
10 | // 'examples/auto-height',
11 | 'examples/exit-before-enter',
12 | 'examples/loop',
13 | 'examples/variants',
14 | // 'examples/dropdown',
15 | 'examples/action-menu',
16 | 'examples/skeleton',
17 | ],
18 | Hooks: ['hooks/use-animation-state', 'hooks/use-dynamic-animation'],
19 | ['Interactions']: [
20 | 'interactions/overview',
21 | 'interactions/pressable',
22 | 'interactions/use-pressable',
23 | 'interactions/use-pressables',
24 | 'interactions/use-pressable-animated-props',
25 | 'interactions/use-pressable-transition',
26 | 'interactions/use-pressable-interpolate',
27 | 'interactions/merge',
28 | 'interactions/eslint',
29 | ],
30 | Skeleton: ['skeleton'],
31 |
32 | Resources: ['resources/videos'],
33 | Web: ['web', 'next'],
34 | 'API Reference': [
35 | 'api/motify',
36 | 'api/motify-svg',
37 | 'api/imports',
38 | 'api/props',
39 | ],
40 | },
41 | }
42 |
--------------------------------------------------------------------------------
/docs/src/pages/styles.module.css:
--------------------------------------------------------------------------------
1 | /* stylelint-disable docusaurus/copyright-header */
2 |
3 | /**
4 | * CSS files with the .module.css suffix will be treated as CSS modules
5 | * and scoped locally.
6 | */
7 |
8 | .heroBanner {
9 | padding: 4rem 0;
10 | text-align: center;
11 | position: relative;
12 | overflow: hidden;
13 | min-height: calc(100vh - 4rem);
14 | background-color: var(--ifm-color-background);
15 | color: var(--ifm-color-text);
16 | }
17 |
18 | .hero__primary .hero__title {
19 | font-size: 3rem;
20 | }
21 |
22 | @media screen and (max-width: 966px) {
23 | .heroBanner {
24 | padding: 2rem;
25 | }
26 | }
27 |
28 | .buttons {
29 | display: flex;
30 | align-items: center;
31 | justify-content: center;
32 | }
33 |
34 | .features {
35 | display: flex;
36 | align-items: center;
37 | padding: 2rem 0;
38 | width: 100%;
39 | }
40 |
41 | .featureImage {
42 | height: 200px;
43 | width: 200px;
44 | }
45 |
--------------------------------------------------------------------------------
/docs/src/plugins/remark-npm2yarn/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Facebook, Inc. and its affiliates.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | // This is a very naive implementation of converting npm commands to yarn commands
9 | // Works well for our use case since we only use either 'npm install', or 'npm run '
10 | // Its impossible to convert it right since some commands at npm are not available in yarn and vice/versa
11 | const convertNpmToYarn = (npmCode) => {
12 | // global install: 'npm i' -> 'yarn'
13 | return (
14 | npmCode
15 | .replace(/^npm i$/gm, 'yarn')
16 | // install: 'npm install foo' -> 'yarn add foo'
17 | .replace(/npm install/gm, 'yarn add')
18 | // run command: 'npm run start' -> 'yarn run start'
19 | .replace(/npm run/gm, 'yarn run')
20 | )
21 | }
22 |
23 | const transformNode = (node) => {
24 | const npmCode = node.value
25 | const yarnCode = convertNpmToYarn(node.value)
26 | return [
27 | {
28 | type: 'jsx',
29 | value:
30 | `
36 | `,
37 | },
38 | {
39 | type: node.type,
40 | lang: node.lang,
41 | value: npmCode,
42 | },
43 | {
44 | type: 'jsx',
45 | value: '\n',
46 | },
47 | {
48 | type: node.type,
49 | lang: node.lang,
50 | value: yarnCode,
51 | },
52 | {
53 | type: 'jsx',
54 | value: '\n',
55 | },
56 | ]
57 | }
58 |
59 | const matchNode = (node) => node.type === 'code' && node.meta === 'npm2yarn'
60 | const nodeForImport = {
61 | type: 'import',
62 | value:
63 | "import Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';",
64 | }
65 |
66 | module.exports = () => {
67 | let transformed = false
68 | const transformer = (node) => {
69 | if (matchNode(node)) {
70 | transformed = true
71 | return transformNode(node)
72 | }
73 | if (Array.isArray(node.children)) {
74 | let index = 0
75 | while (index < node.children.length) {
76 | const result = transformer(node.children[index])
77 | if (result) {
78 | node.children.splice(index, 1, ...result)
79 | index += result.length
80 | } else {
81 | index += 1
82 | }
83 | }
84 | }
85 | if (node.type === 'root' && transformed) {
86 | node.children.unshift(nodeForImport)
87 | }
88 | return null
89 | }
90 | return transformer
91 | }
92 |
--------------------------------------------------------------------------------
/docs/static/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nandorojo/moti/1854a19e5f18446b3ea459f8ba88028fbb6dce2c/docs/static/.nojekyll
--------------------------------------------------------------------------------
/docs/static/font/Satoshi-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nandorojo/moti/1854a19e5f18446b3ea459f8ba88028fbb6dce2c/docs/static/font/Satoshi-Bold.ttf
--------------------------------------------------------------------------------
/docs/static/font/Satoshi-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nandorojo/moti/1854a19e5f18446b3ea459f8ba88028fbb6dce2c/docs/static/font/Satoshi-Italic.ttf
--------------------------------------------------------------------------------
/docs/static/font/Satoshi-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nandorojo/moti/1854a19e5f18446b3ea459f8ba88028fbb6dce2c/docs/static/font/Satoshi-Medium.ttf
--------------------------------------------------------------------------------
/docs/static/font/Satoshi-MediumItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nandorojo/moti/1854a19e5f18446b3ea459f8ba88028fbb6dce2c/docs/static/font/Satoshi-MediumItalic.ttf
--------------------------------------------------------------------------------
/docs/static/font/Satoshi-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nandorojo/moti/1854a19e5f18446b3ea459f8ba88028fbb6dce2c/docs/static/font/Satoshi-Regular.ttf
--------------------------------------------------------------------------------
/docs/static/img/Banner Gradient.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nandorojo/moti/1854a19e5f18446b3ea459f8ba88028fbb6dce2c/docs/static/img/Banner Gradient.png
--------------------------------------------------------------------------------
/docs/static/img/Interactions.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nandorojo/moti/1854a19e5f18446b3ea459f8ba88028fbb6dce2c/docs/static/img/Interactions.png
--------------------------------------------------------------------------------
/docs/static/img/Logo White (flipped).svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/docs/static/img/Logo White.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/docs/static/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nandorojo/moti/1854a19e5f18446b3ea459f8ba88028fbb6dce2c/docs/static/img/favicon.ico
--------------------------------------------------------------------------------
/docs/static/img/favicon.svg:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/docs/static/img/logo-grad.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/docs/static/img/panda.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nandorojo/moti/1854a19e5f18446b3ea459f8ba88028fbb6dce2c/docs/static/img/panda.png
--------------------------------------------------------------------------------
/docs/static/img/panda.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/examples/sample/.gitignore:
--------------------------------------------------------------------------------
1 | # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
2 |
3 | # dependencies
4 | node_modules/
5 |
6 | # Expo
7 | .expo/
8 | dist/
9 | web-build/
10 |
11 | # Native
12 | *.orig.*
13 | *.jks
14 | *.p8
15 | *.p12
16 | *.key
17 | *.mobileprovision
18 |
19 | # Metro
20 | .metro-health-check*
21 |
22 | # debug
23 | npm-debug.*
24 | yarn-debug.*
25 | yarn-error.*
26 |
27 | # macOS
28 | .DS_Store
29 | *.pem
30 |
31 | # local env files
32 | .env*.local
33 |
34 | # typescript
35 | *.tsbuildinfo
36 |
--------------------------------------------------------------------------------
/examples/sample/.yarn/install-state.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nandorojo/moti/1854a19e5f18446b3ea459f8ba88028fbb6dce2c/examples/sample/.yarn/install-state.gz
--------------------------------------------------------------------------------
/examples/sample/App.js:
--------------------------------------------------------------------------------
1 | export { default } from './src/Perf.List'
2 |
--------------------------------------------------------------------------------
/examples/sample/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "sample",
4 | "slug": "sample",
5 | "version": "1.0.0",
6 | "orientation": "portrait",
7 | "icon": "./assets/icon.png",
8 | "userInterfaceStyle": "light",
9 | "splash": {
10 | "image": "./assets/splash.png",
11 | "resizeMode": "contain",
12 | "backgroundColor": "#ffffff"
13 | },
14 | "assetBundlePatterns": [
15 | "**/*"
16 | ],
17 | "ios": {
18 | "supportsTablet": true
19 | },
20 | "android": {
21 | "adaptiveIcon": {
22 | "foregroundImage": "./assets/adaptive-icon.png",
23 | "backgroundColor": "#ffffff"
24 | }
25 | },
26 | "web": {
27 | "favicon": "./assets/favicon.png"
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/examples/sample/assets/adaptive-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nandorojo/moti/1854a19e5f18446b3ea459f8ba88028fbb6dce2c/examples/sample/assets/adaptive-icon.png
--------------------------------------------------------------------------------
/examples/sample/assets/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nandorojo/moti/1854a19e5f18446b3ea459f8ba88028fbb6dce2c/examples/sample/assets/favicon.png
--------------------------------------------------------------------------------
/examples/sample/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nandorojo/moti/1854a19e5f18446b3ea459f8ba88028fbb6dce2c/examples/sample/assets/icon.png
--------------------------------------------------------------------------------
/examples/sample/assets/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nandorojo/moti/1854a19e5f18446b3ea459f8ba88028fbb6dce2c/examples/sample/assets/splash.png
--------------------------------------------------------------------------------
/examples/sample/babel.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = function (api) {
4 | api.cache(true)
5 | return {
6 | presets: ['babel-preset-expo'],
7 | plugins: [
8 | 'react-native-reanimated/plugin',
9 | [
10 | 'module-resolver',
11 | {
12 | extensions: ['.tsx', '.ts', '.js', '.json'],
13 | alias: {
14 | // For development, we want to alias the library to the source
15 | moti$: path.join(__dirname, '../../packages/moti/src/index.tsx'),
16 | 'moti/skeleton': path.join(
17 | __dirname,
18 | '../../packages/moti/src/skeleton/index.ts'
19 | ),
20 | 'moti/interactions': path.join(
21 | __dirname,
22 | '../../packages/moti/src/interactions/index.ts'
23 | ),
24 | },
25 | },
26 | ],
27 | ],
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/examples/sample/metro.config.js:
--------------------------------------------------------------------------------
1 | // Learn more https://docs.expo.io/guides/customizing-metro
2 | const { getDefaultConfig } = require('expo/metro-config')
3 | const path = require('path')
4 |
5 | const config = getDefaultConfig(__dirname)
6 |
7 | // npm v7+ will install ../node_modules/react-native because of peerDependencies.
8 | // To prevent the incompatible react-native bewtween ./node_modules/react-native and ../node_modules/react-native,
9 | // excludes the one from the parent folder when bundling.
10 | config.resolver.blockList = [
11 | ...Array.from(config.resolver.blockList ?? []),
12 | new RegExp(path.resolve('../..', 'node_modules', 'react-native')),
13 | ]
14 |
15 | config.resolver.nodeModulesPaths = [
16 | path.resolve(__dirname, './node_modules'),
17 | path.resolve(__dirname, '../../node_modules'),
18 | ]
19 |
20 | config.resolver.assetExts.push('mjs')
21 | config.resolver.assetExts.push('cjs')
22 |
23 | config.watchFolders = [path.resolve(__dirname, '../..')]
24 |
25 | config.transformer.getTransformOptions = async () => ({
26 | transform: {
27 | experimentalImportSupport: false,
28 | inlineRequires: true,
29 | },
30 | })
31 |
32 | module.exports = config
33 |
--------------------------------------------------------------------------------
/examples/sample/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sample",
3 | "version": "1.0.0",
4 | "scripts": {
5 | "start": "expo start",
6 | "android": "expo start --android",
7 | "ios": "expo start --ios",
8 | "web": "expo start --web"
9 | },
10 | "dependencies": {
11 | "expo": "~51.0.6",
12 | "expo-status-bar": "~1.12.1",
13 | "moti": "^0.27.2",
14 | "react": "18.2.0",
15 | "react-native": "^0.74.1",
16 | "react-native-gesture-handler": "~2.16.1",
17 | "react-native-reanimated": "~3.10.1"
18 | },
19 | "devDependencies": {
20 | "@babel/core": "^7.20.0",
21 | "babel-plugin-module-resolver": "^5.0.0"
22 | },
23 | "private": true
24 | }
25 |
--------------------------------------------------------------------------------
/examples/sample/src/Bug.tsx:
--------------------------------------------------------------------------------
1 | import { View, Button, StyleSheet } from 'react-native'
2 | import React from 'react'
3 | import Animated, {
4 | useAnimatedStyle,
5 | useSharedValue,
6 | withSpring,
7 | withTiming,
8 | processColor,
9 | } from 'react-native-reanimated'
10 |
11 | export default function AnimatedStyleUpdateExample() {
12 | const size = useSharedValue(200)
13 |
14 | const style = useAnimatedStyle(
15 | () => ({
16 | width: withTiming(size.value),
17 | height: withTiming(size.value),
18 | backgroundColor: withTiming(
19 | size.value > 150 ? processColor('#333333') : processColor('#e8e')
20 | ),
21 | }),
22 | [size]
23 | )
24 |
25 | return (
26 |
27 |
28 |
41 | )
42 | }
43 |
44 | const styles = StyleSheet.create({
45 | text: {
46 | textAlign: 'center',
47 | alignSelf: 'center',
48 | color: 'hotpink',
49 | },
50 | box: { justifyContent: 'center', backgroundColor: 'blue' },
51 | container: {
52 | flex: 1,
53 | alignItems: 'center',
54 | justifyContent: 'center',
55 | flexDirection: 'column',
56 | },
57 | })
58 |
--------------------------------------------------------------------------------
/examples/sample/src/Color-Bug.tsx:
--------------------------------------------------------------------------------
1 | import { View, Button, StyleSheet } from 'react-native'
2 | import React, { useState } from 'react'
3 | import Animated, {
4 | useAnimatedStyle,
5 | useSharedValue,
6 | withTiming,
7 | processColor,
8 | useDerivedValue,
9 | Easing,
10 | } from 'react-native-reanimated'
11 |
12 | const colors = ['#41b87a', '#533592']
13 |
14 | export default function ColorBug() {
15 | const color = useSharedValue(colors[0])
16 |
17 | const [state, setState] = useState(colors[0])
18 |
19 | const toggleColor = () => {
20 | if (color.value === colors[0]) {
21 | color.value = colors[1]
22 | setState(colors[1])
23 | } else {
24 | color.value = colors[0]
25 | setState(colors[0])
26 | }
27 | }
28 |
29 | const bg = useDerivedValue(() => {
30 | return JSON.stringify({ backgroundColor: state })
31 | })
32 |
33 | const style = useAnimatedStyle(() => {
34 | const final = {}
35 | const style = JSON.parse(bg.value)
36 | Object.keys(style).forEach((key) => {
37 | const value = style[key]
38 | final[key] = withTiming(value, {}, (finished, value) => {
39 | console.log('done', { finished, value })
40 | })
41 | })
42 | return final
43 | })
44 |
45 | return (
46 |
47 |
48 |
49 |
50 | )
51 | }
52 |
53 | const styles = StyleSheet.create({
54 | box: {
55 | justifyContent: 'center',
56 | backgroundColor: 'blue',
57 | height: 100,
58 | width: 100,
59 | },
60 | container: {
61 | flex: 1,
62 | alignItems: 'center',
63 | justifyContent: 'center',
64 | flexDirection: 'column',
65 | },
66 | })
67 |
--------------------------------------------------------------------------------
/examples/sample/src/Color.tsx:
--------------------------------------------------------------------------------
1 | import React, { useReducer } from 'react'
2 | import Animated, { useAnimatedStyle, withSpring } from 'react-native-reanimated'
3 | import { Button } from 'react-native'
4 |
5 | export default function Colorz() {
6 | const [backgroundColor, toggle] = useReducer(
7 | (bg) => (bg === 'red' ? 'green' : 'red'),
8 | 'red'
9 | )
10 | const style = useAnimatedStyle(() => ({
11 | backgroundColor: withSpring(backgroundColor),
12 | }))
13 | return (
14 |
24 |
25 |
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/examples/sample/src/Drip.Color.tsx:
--------------------------------------------------------------------------------
1 | import React, { useReducer, useState } from 'react'
2 | import { Button, StyleSheet } from 'react-native'
3 | import { View } from 'moti'
4 |
5 | function AnimatedCircle() {
6 | const colors = ['#41b87a', '#533592']
7 |
8 | const [state, setState] = useState<0 | 1>(0)
9 | const toggle = () => setState((state) => (state ? 0 : 1))
10 | const backgroundColor = colors[state]
11 | return (
12 |
13 |
24 |
25 |
26 | )
27 | }
28 |
29 | export default function DripRepeat() {
30 | const [shown, toggle] = useReducer((s) => !s, true)
31 | return (
32 |
33 | {shown && }
34 |
35 |
36 | )
37 | }
38 |
39 | const styles = StyleSheet.create({
40 | circle: {
41 | justifyContent: 'center',
42 | height: 100,
43 | width: 100,
44 | borderRadius: 50,
45 | marginRight: 10,
46 | },
47 | container: {
48 | flex: 1,
49 | alignItems: 'center',
50 | justifyContent: 'center',
51 | flexDirection: 'row',
52 | backgroundColor: 'black',
53 | },
54 | })
55 |
--------------------------------------------------------------------------------
/examples/sample/src/Drip.Presence.tsx:
--------------------------------------------------------------------------------
1 | import { AnimatePresence } from 'framer-motion'
2 | import React, { useReducer } from 'react'
3 | import { StyleSheet, Pressable } from 'react-native'
4 | import { View } from 'moti'
5 |
6 | function Shape() {
7 | return (
8 |
23 | )
24 | }
25 |
26 | export default function Presence() {
27 | const [visible, toggle] = useReducer((s) => !s, true)
28 |
29 | return (
30 |
31 | {visible && }
32 |
33 | )
34 | }
35 |
36 | const styles = StyleSheet.create({
37 | shape: {
38 | justifyContent: 'center',
39 | height: 250,
40 | width: 250,
41 | borderRadius: 25,
42 | marginRight: 10,
43 | backgroundColor: 'white',
44 | },
45 | container: {
46 | flex: 1,
47 | alignItems: 'center',
48 | justifyContent: 'center',
49 | flexDirection: 'row',
50 | backgroundColor: '#9c1aff',
51 | },
52 | })
53 |
--------------------------------------------------------------------------------
/examples/sample/src/Drip.Repeat.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { StyleSheet } from 'react-native'
3 | import * as Moti from 'moti'
4 |
5 | const { View } = Moti
6 |
7 | function AnimatedCircle() {
8 | return (
9 | {
17 | console.log('[complete]', key, finished) // [complete] scale, true
18 | }}
19 | style={styles.circle}
20 | />
21 | )
22 | }
23 |
24 | export default function DripRepeat() {
25 | return (
26 |
27 | {/* */}
28 |
29 | {/* */}
30 |
31 | )
32 | }
33 |
34 | const styles = StyleSheet.create({
35 | circle: {
36 | justifyContent: 'center',
37 | backgroundColor: '#FF0080',
38 | height: 100,
39 | width: 100,
40 | borderRadius: 50,
41 | marginRight: 10,
42 | },
43 | container: {
44 | flex: 1,
45 | alignItems: 'center',
46 | justifyContent: 'center',
47 | flexDirection: 'row',
48 | backgroundColor: 'black',
49 | },
50 | })
51 |
--------------------------------------------------------------------------------
/examples/sample/src/Drip.tsx:
--------------------------------------------------------------------------------
1 | import { View as NativeView, Button, Text, StyleSheet } from 'react-native'
2 | import React from 'react'
3 | import * as Moti from 'moti'
4 |
5 | export default function AnimatedStyleUpdateExample() {
6 | const box = Moti.useAnimationState({
7 | from: {
8 | width: 100,
9 | height: 100,
10 | opacity: 0.2,
11 | },
12 | open: {
13 | width: 300,
14 | height: 300,
15 | opacity: 1,
16 | },
17 | })
18 |
19 | return (
20 |
21 | {/*
28 | Animator
29 | */}
30 |
48 | Style Props
49 |
50 |
62 | )
63 | }
64 |
65 | const styles = StyleSheet.create({
66 | text: {
67 | textAlign: 'center',
68 | alignSelf: 'center',
69 | color: 'hotpink',
70 | },
71 | box: {
72 | justifyContent: 'center',
73 | backgroundColor: 'blue',
74 | height: 100,
75 | width: 100,
76 | },
77 | container: {
78 | flex: 1,
79 | alignItems: 'center',
80 | justifyContent: 'center',
81 | flexDirection: 'column',
82 | },
83 | })
84 |
--------------------------------------------------------------------------------
/examples/sample/src/Easing.tsx:
--------------------------------------------------------------------------------
1 | import { Easing } from 'react-native'
2 | import * as Reanimated from 'react-native-reanimated'
3 |
--------------------------------------------------------------------------------
/examples/sample/src/Example.tsx:
--------------------------------------------------------------------------------
1 | import Animated, {
2 | useSharedValue,
3 | withTiming,
4 | useAnimatedStyle,
5 | Easing,
6 | } from 'react-native-reanimated'
7 | import { View, Button } from 'react-native'
8 | import React from 'react'
9 |
10 | export default function AnimatedStyleUpdateExample() {
11 | const randomWidth = useSharedValue(10)
12 |
13 | const config = {
14 | duration: 500,
15 | easing: Easing.bezier(0.5, 0.01, 0, 1),
16 | }
17 |
18 | const style = useAnimatedStyle(() => {
19 | return {
20 | width: withTiming(randomWidth.value, config),
21 | }
22 | })
23 |
24 | return (
25 |
33 |
39 |
46 | )
47 | }
48 |
--------------------------------------------------------------------------------
/examples/sample/src/Headless-Exit.tsx:
--------------------------------------------------------------------------------
1 | import React, { useReducer, useEffect, useRef } from 'react'
2 | import { StyleSheet, Pressable, Animated } from 'react-native'
3 | import { AnimatePresence, usePresence } from 'framer-motion'
4 |
5 | function FadeOut() {
6 | const [present, safeToUnmount] = usePresence()
7 |
8 | const opacity = useRef(new Animated.Value(1))
9 |
10 | const unmount = useRef(safeToUnmount)
11 | useEffect(() => {
12 | unmount.current = safeToUnmount
13 | })
14 |
15 | useEffect(
16 | function exit() {
17 | if (!present) {
18 | Animated.timing(opacity.current, {
19 | toValue: 0,
20 | useNativeDriver: true,
21 | }).start(({ finished }) => {
22 | if (finished) {
23 | unmount.current()
24 | }
25 | })
26 | }
27 | },
28 | [present]
29 | )
30 |
31 | return
32 | }
33 |
34 | export default function HelloWorld() {
35 | const [visible, toggle] = useReducer((s) => !s, true)
36 |
37 | return (
38 |
39 | {visible && }
40 |
41 | )
42 | }
43 |
44 | const styles = StyleSheet.create({
45 | shape: {
46 | justifyContent: 'center',
47 | height: 250,
48 | width: 250,
49 | borderRadius: 25,
50 | marginRight: 10,
51 | backgroundColor: 'white',
52 | },
53 | container: {
54 | flex: 1,
55 | alignItems: 'center',
56 | justifyContent: 'center',
57 | flexDirection: 'row',
58 | backgroundColor: '#9c1aff',
59 | },
60 | })
61 |
--------------------------------------------------------------------------------
/examples/sample/src/Measure.tsx:
--------------------------------------------------------------------------------
1 | import React, { ComponentProps, useReducer, useState } from 'react'
2 | import { View as MotiView } from 'moti'
3 | import { Button, View } from 'react-native'
4 |
5 | function useLayout() {
6 | const [layout, setLayout] = useState({
7 | height: 0,
8 | })
9 | const onLayout: ComponentProps['onLayout'] = ({
10 | nativeEvent,
11 | }) => {
12 | setLayout(nativeEvent.layout)
13 | }
14 |
15 | return [layout, onLayout] as const
16 | }
17 |
18 | function Measure() {
19 | const [{ height }, onLayout] = useLayout()
20 |
21 | const [open, toggle] = useReducer((s) => !s, false)
22 |
23 | return (
24 | <>
25 |
26 |
30 |
31 |
32 | >
33 | )
34 | }
35 |
36 | export default function App() {
37 | return (
38 |
39 |
40 |
41 | )
42 | }
43 |
--------------------------------------------------------------------------------
/examples/sample/src/Moti.Callbacks.tsx:
--------------------------------------------------------------------------------
1 | import React, { useReducer } from 'react'
2 | import { StyleSheet, Pressable } from 'react-native'
3 | import { MotiView } from 'moti'
4 |
5 | export default function HelloWorld() {
6 | const [visible, toggle] = useReducer((s) => !s, true)
7 |
8 | return (
9 |
10 |
27 |
28 | )
29 | }
30 |
31 | const styles = StyleSheet.create({
32 | shape: {
33 | justifyContent: 'center',
34 | height: 250,
35 | width: 250,
36 | borderRadius: 25,
37 | marginRight: 10,
38 | backgroundColor: 'white',
39 | },
40 | container: {
41 | flex: 1,
42 | alignItems: 'center',
43 | justifyContent: 'center',
44 | flexDirection: 'row',
45 | backgroundColor: '#9c1aff',
46 | },
47 | })
48 |
--------------------------------------------------------------------------------
/examples/sample/src/Moti.ChangeVariants.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { StyleSheet, Pressable } from 'react-native'
3 | import { View, useAnimationState } from 'moti'
4 |
5 | const useFadeInDown = () => {
6 | return useAnimationState({
7 | from: {
8 | opacity: 0,
9 | translateY: -15,
10 | },
11 | to: {
12 | opacity: 1,
13 | translateY: 0,
14 | },
15 | })
16 | }
17 |
18 | function Shape() {
19 | const fadeInDown = useFadeInDown()
20 |
21 | return (
22 |
23 |
31 |
32 | )
33 | }
34 |
35 | export default function Variants() {
36 | return (
37 |
38 |
39 |
40 | )
41 | }
42 |
43 | const styles = StyleSheet.create({
44 | shape: {
45 | justifyContent: 'center',
46 | height: 250,
47 | width: 250,
48 | borderRadius: 25,
49 | marginRight: 10,
50 | backgroundColor: 'black',
51 | },
52 | container: {
53 | flex: 1,
54 | alignItems: 'center',
55 | justifyContent: 'center',
56 | flexDirection: 'row',
57 | backgroundColor: 'cyan',
58 | },
59 | })
60 |
--------------------------------------------------------------------------------
/examples/sample/src/Moti.DynamicAnimation.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { StyleSheet, Pressable } from 'react-native'
3 | import { View, useDynamicAnimation } from 'moti'
4 |
5 | function Shape() {
6 | const animation = useDynamicAnimation(() => ({
7 | backgroundColor: 'blue',
8 | }))
9 |
10 | return (
11 | {
13 | animation.animateTo({
14 | backgroundColor: 'green',
15 | })
16 | }}
17 | onPressOut={() => {
18 | animation.animateTo({
19 | ...animation.current,
20 | backgroundColor: 'blue',
21 | })
22 | }}
23 | >
24 |
32 |
33 | )
34 | }
35 |
36 | export default function Variants() {
37 | return (
38 |
39 |
40 |
41 | )
42 | }
43 |
44 | const styles = StyleSheet.create({
45 | shape: {
46 | justifyContent: 'center',
47 | height: 250,
48 | width: 250,
49 | borderRadius: 25,
50 | marginRight: 10,
51 | backgroundColor: 'black',
52 | },
53 | shape2: {
54 | backgroundColor: 'hotpink',
55 | marginTop: 16,
56 | },
57 | container: {
58 | flex: 1,
59 | alignItems: 'center',
60 | justifyContent: 'center',
61 | flexDirection: 'row',
62 | backgroundColor: 'cyan',
63 | },
64 | })
65 |
--------------------------------------------------------------------------------
/examples/sample/src/Moti.ExitBeforeEnter.tsx:
--------------------------------------------------------------------------------
1 | import React, { useReducer } from 'react'
2 | import { StyleSheet, Pressable } from 'react-native'
3 | import { AnimatePresence, View } from 'moti'
4 |
5 | function Shape({ bg }: { bg: string }) {
6 | return (
7 |
22 | )
23 | }
24 |
25 | export default function ExitBeforeEnter() {
26 | const [visible, toggle] = useReducer((s) => !s, true)
27 |
28 | return (
29 |
30 |
31 | {visible && }
32 | {!visible && }
33 |
34 |
35 | )
36 | }
37 |
38 | const styles = StyleSheet.create({
39 | shape: {
40 | justifyContent: 'center',
41 | height: 250,
42 | width: 250,
43 | borderRadius: 25,
44 | marginRight: 10,
45 | backgroundColor: 'white',
46 | },
47 | container: {
48 | flex: 1,
49 | alignItems: 'center',
50 | justifyContent: 'center',
51 | flexDirection: 'row',
52 | backgroundColor: '#9c1aff',
53 | },
54 | })
55 |
--------------------------------------------------------------------------------
/examples/sample/src/Moti.Factory.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { MotiPressable } from 'moti/interactions'
3 | import { useSharedValue } from 'react-native-reanimated'
4 | import { MotiView } from 'moti'
5 |
6 | global.shouldDebugMoti = false
7 |
8 | export default function Factory() {
9 | const on = useSharedValue(true)
10 | return (
11 | {
15 | 'worklet'
16 |
17 | // console.log('[animate] pressable', interaction)
18 |
19 | return {
20 | // opacity: on.value ? 1 : 0,
21 | }
22 | }}
23 | >
24 | {(interaction) => (
25 | {
27 | 'worklet'
28 | const { pressed } = interaction.value
29 |
30 | console.log('[moti-pressable]', interaction.value)
31 |
32 | return {
33 | scale: pressed ? 0.7 : 1,
34 | }
35 | }}
36 | style={{
37 | height: 300,
38 | width: 300,
39 | borderRadius: 16,
40 | backgroundColor: 'white',
41 | }}
42 | />
43 | )}
44 |
45 | )
46 | }
47 |
--------------------------------------------------------------------------------
/examples/sample/src/Moti.Gallery.tsx:
--------------------------------------------------------------------------------
1 | import { MotiImage, AnimatePresence, Text } from 'moti'
2 | import React, { useState } from 'react'
3 | import { StyleSheet, View, Dimensions } from 'react-native'
4 |
5 | const photos = [
6 | `https://images.unsplash.com/photo-1551871812-10ecc21ffa2f?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=929&q=80`,
7 | `https://images.unsplash.com/photo-1530447920184-b88c8872?ixid=MXwxMjA3fDB8MHxzZWFyY2h8MTN8fHJvY2tldHxlbnwwfHwwfA%3D%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60`,
8 | `https://images.unsplash.com/photo-1581069700310-8cf2e1b6baf0?ixid=MXwxMjA3fDB8MHxzZWFyY2h8MjF8fHJvY2tldHxlbnwwfHwwfA%3D%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60`,
9 | `https://images.unsplash.com/photo-1562802378-063ec186a863?ixid=MXwxMjA3fDB8MHxzZWFyY2h8MTJ8fHN1c2hpfGVufDB8fDB8&ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60`,
10 | ].map((url) => ({ url }))
11 |
12 | const size = Dimensions.get('window')
13 |
14 | const width = size.width
15 |
16 | export default function Gallery() {
17 | const [[index, direction], setIndex] = useState([0, 0])
18 |
19 | const paginate = (direction: 1 | -1) => () => {
20 | setIndex(([current]) => {
21 | const nextIndex = current + direction
22 |
23 | const normalizedIndex = Math.max(
24 | 0,
25 | Math.min(nextIndex, photos.length - 1)
26 | )
27 | return [normalizedIndex, direction]
28 | })
29 | }
30 |
31 | const { url } = photos[index]
32 |
33 | return (
34 |
35 |
36 | {
46 | 'worklet'
47 | console.log('[gallery] exiting', { custom })
48 | return {
49 | opacity: 0,
50 | translateX: custom < 0 ? -width : width,
51 | }
52 | }}
53 | style={styles.image}
54 | key={url}
55 | source={{ uri: url }}
56 | />
57 |
58 |
59 |
60 |
61 | 👈
62 |
63 |
64 |
65 |
66 | 👉
67 |
68 |
69 |
70 |
71 | )
72 | }
73 |
74 | const styles = StyleSheet.create({
75 | container: {
76 | flex: 1,
77 | justifyContent: 'flex-end',
78 | backgroundColor: '#0D1117',
79 | },
80 | padded: {
81 | padding: 16,
82 | },
83 | image: {
84 | ...StyleSheet.absoluteFillObject,
85 | width,
86 | alignSelf: 'center',
87 | },
88 | actions: {
89 | flexDirection: 'row',
90 | margin: 16,
91 | justifyContent: 'space-between',
92 | },
93 | button: {
94 | fontSize: 42,
95 | },
96 | action: {
97 | backgroundColor: 'white',
98 | paddingHorizontal: 8,
99 | borderRadius: 16,
100 | },
101 | })
102 |
--------------------------------------------------------------------------------
/examples/sample/src/Moti.HelloWorld.tsx:
--------------------------------------------------------------------------------
1 | import React, { useReducer } from 'react'
2 | import { StyleSheet, Pressable } from 'react-native'
3 | import { View } from 'moti'
4 |
5 | function Shape() {
6 | return (
7 |
22 | )
23 | }
24 |
25 | export default function HelloWorld() {
26 | const [visible, toggle] = useReducer((s) => !s, true)
27 |
28 | return (
29 |
30 | {visible && }
31 |
32 | )
33 | }
34 |
35 | const styles = StyleSheet.create({
36 | shape: {
37 | justifyContent: 'center',
38 | height: 250,
39 | width: 250,
40 | borderRadius: 25,
41 | marginRight: 10,
42 | backgroundColor: 'white',
43 | },
44 | container: {
45 | flex: 1,
46 | alignItems: 'center',
47 | justifyContent: 'center',
48 | flexDirection: 'row',
49 | backgroundColor: '#9c1aff',
50 | },
51 | })
52 |
--------------------------------------------------------------------------------
/examples/sample/src/Moti.KeyPresence.tsx:
--------------------------------------------------------------------------------
1 | import React, { useReducer } from 'react'
2 | import { StyleSheet, Pressable } from 'react-native'
3 | import { View, AnimatePresence } from 'moti'
4 |
5 | function Shape() {
6 | return (
7 |
26 | )
27 | }
28 |
29 | export default function Presence() {
30 | const [key, increment] = useReducer((state) => state + 1, 0)
31 |
32 | return (
33 |
34 |
35 |
55 |
56 |
57 | )
58 | }
59 |
60 | const styles = StyleSheet.create({
61 | shape: {
62 | justifyContent: 'center',
63 | height: 250,
64 | width: 250,
65 | borderRadius: 25,
66 | marginRight: 10,
67 | backgroundColor: 'white',
68 | },
69 | container: {
70 | flex: 1,
71 | alignItems: 'center',
72 | justifyContent: 'center',
73 | flexDirection: 'row',
74 | backgroundColor: '#9c1aff',
75 | },
76 | })
77 |
--------------------------------------------------------------------------------
/examples/sample/src/Moti.Loop.tsx:
--------------------------------------------------------------------------------
1 | import React, { useReducer } from 'react'
2 | import { StyleSheet } from 'react-native'
3 | import { View } from 'moti'
4 |
5 | function Shape() {
6 | return (
7 |
22 | )
23 | }
24 |
25 | export default function ExitBeforeEnter() {
26 | return (
27 |
28 |
29 |
30 | )
31 | }
32 |
33 | const styles = StyleSheet.create({
34 | shape: {
35 | justifyContent: 'center',
36 | height: 250,
37 | width: 250,
38 | borderRadius: 25,
39 | marginRight: 10,
40 | backgroundColor: 'white',
41 | },
42 | container: {
43 | flex: 1,
44 | alignItems: 'center',
45 | justifyContent: 'center',
46 | flexDirection: 'row',
47 | backgroundColor: '#9c1aff',
48 | },
49 | })
50 |
--------------------------------------------------------------------------------
/examples/sample/src/Moti.NewTransition.tsx:
--------------------------------------------------------------------------------
1 | import { useAnimationState, useDynamicAnimation } from 'moti'
2 | import React from 'react'
3 |
4 | export function App() {
5 | const state = useAnimationState({
6 | from: {
7 | transition: {
8 | type: 'timing',
9 | },
10 | },
11 | })
12 | const state2 = useDynamicAnimation(() => ({
13 | scale: 0.5,
14 | transition: {
15 | type: 'timing',
16 | },
17 | }))
18 |
19 | const toggleTransition = () => {
20 | state.transitionTo('from')
21 | }
22 |
23 | const animateTo = () => {
24 | state2.animateTo({
25 | opacity: 1,
26 | transition: {
27 | type: 'timing',
28 | },
29 | })
30 | }
31 |
32 | return <>>
33 | }
34 |
--------------------------------------------------------------------------------
/examples/sample/src/Moti.Presence.tsx:
--------------------------------------------------------------------------------
1 | import React, { useReducer } from 'react'
2 | import { StyleSheet, Pressable } from 'react-native'
3 | import { View, AnimatePresence } from 'moti'
4 |
5 | function Shape() {
6 | return (
7 | {
17 | 'worklet'
18 | console.log('[exit]', { custom })
19 | return {
20 | opacity: 0,
21 | scale: 0.9,
22 | }
23 | }}
24 | // exit={{
25 | // opacity: 0,
26 | // scale: 0.9,
27 | // }}
28 | exitTransition={{
29 | type: 'timing',
30 | duration: 2500,
31 | }}
32 | style={styles.shape}
33 | />
34 | )
35 | }
36 |
37 | export default function Presence() {
38 | const [visible, toggle] = useReducer((s) => !s, true)
39 |
40 | return (
41 |
42 |
43 | {visible && }
44 |
45 |
46 | )
47 | }
48 |
49 | const styles = StyleSheet.create({
50 | shape: {
51 | justifyContent: 'center',
52 | height: 250,
53 | width: 250,
54 | borderRadius: 25,
55 | marginRight: 10,
56 | backgroundColor: 'white',
57 | },
58 | container: {
59 | flex: 1,
60 | alignItems: 'center',
61 | justifyContent: 'center',
62 | flexDirection: 'row',
63 | backgroundColor: '#9c1aff',
64 | },
65 | })
66 |
--------------------------------------------------------------------------------
/examples/sample/src/Moti.Pressable.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { StyleSheet, View } from 'react-native'
3 | import { MotiImage, MotiView } from 'moti'
4 | import { MotiPressable, useMotiPressable } from 'moti/interactions'
5 |
6 | function Logo() {
7 | const state = useMotiPressable(
8 | 'logo',
9 | ({ pressed, hovered }) => {
10 | 'worklet'
11 |
12 | return {
13 | // opacity: pressed ? 0.25 : hovered ? 0.8 : 1,
14 | scale: pressed ? 0.97 : hovered ? 1.05 : 1,
15 | backgroundColor: pressed ? '#ffffff' : '#000000',
16 | }
17 | },
18 | []
19 | )
20 |
21 | return (
22 |
27 | )
28 | }
29 |
30 | function App() {
31 | return (
32 | <>
33 |
34 |
35 |
36 |
37 | {(interaction) => {
38 | return (
39 | {
42 | 'worklet'
43 |
44 | const { pressed, hovered } = interaction.value
45 |
46 | return {}
47 | }}
48 | />
49 | )
50 | }}
51 |
52 | >
53 | )
54 | }
55 |
56 | export default function HelloWorld() {
57 | return (
58 |
59 |
60 |
61 | )
62 | }
63 |
64 | const styles = StyleSheet.create({
65 | shape: {
66 | justifyContent: 'center',
67 | height: 180,
68 | width: 250,
69 | borderRadius: 25,
70 | marginRight: 10,
71 | backgroundColor: 'black',
72 | },
73 | container: {
74 | flex: 1,
75 | alignItems: 'center',
76 | justifyContent: 'center',
77 | flexDirection: 'row',
78 | backgroundColor: 'black',
79 | },
80 | logo: {
81 | alignSelf: 'center',
82 | height: 50,
83 | width: 200,
84 | },
85 | })
86 |
87 | const beatGigLogo = `https://beatgig.com/_next/static/images/beatgig-256-a2ce12989084a7604b2cb2994e29fccb.png`
88 |
--------------------------------------------------------------------------------
/examples/sample/src/Moti.PressableTooltip.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { StyleSheet, View } from 'react-native'
3 | import { MotiImage } from 'moti'
4 | import {
5 | mergeAnimateProp,
6 | MotiPressable,
7 | useMotiPressable,
8 | useMotiPressableTransition,
9 | } from 'moti/interactions'
10 |
11 | function Logo() {
12 | const state = useMotiPressable(
13 | 'logo',
14 | ({ pressed, hovered }) => {
15 | 'worklet'
16 |
17 | return {
18 | opacity: pressed || hovered ? 0.5 : 1,
19 | }
20 | },
21 | []
22 | )
23 |
24 | const transition = useMotiPressableTransition(({ pressed }) => {
25 | 'worklet'
26 |
27 | return {
28 | delay: pressed ? 0 : 100,
29 | }
30 | })
31 |
32 | return (
33 | ({ type: 'timing' }), [])}
41 | transition={transition}
42 | />
43 | )
44 | }
45 |
46 | function App() {
47 | return (
48 | {
50 | 'worklet'
51 |
52 | return mergeAnimateProp(state, {
53 | scale: state.pressed ? 0.9 : 1,
54 | })
55 | }}
56 | id="logo"
57 | style={styles.shape}
58 | >
59 |
60 |
61 | )
62 | }
63 |
64 | export default function HelloWorld() {
65 | return (
66 |
67 |
68 |
69 | )
70 | }
71 |
72 | const styles = StyleSheet.create({
73 | shape: {
74 | justifyContent: 'center',
75 | height: 180,
76 | width: 250,
77 | borderRadius: 25,
78 | marginRight: 10,
79 | backgroundColor: 'black',
80 | },
81 | container: {
82 | flex: 1,
83 | alignItems: 'center',
84 | justifyContent: 'center',
85 | flexDirection: 'row',
86 | backgroundColor: 'black',
87 | },
88 | logo: {
89 | alignSelf: 'center',
90 | height: 50,
91 | width: 200,
92 | },
93 | })
94 |
95 | const beatGigLogo = `https://beatgig.com/_next/static/images/beatgig-256-a2ce12989084a7604b2cb2994e29fccb.png`
96 |
--------------------------------------------------------------------------------
/examples/sample/src/Moti.Progress.tsx:
--------------------------------------------------------------------------------
1 | import React, { useReducer } from 'react'
2 | import { StyleSheet, Pressable, View } from 'react-native'
3 | import { MotiProgressBar } from 'moti'
4 |
5 | function Bar() {
6 | const [progress, increment] = useReducer((progress) => {
7 | if (progress + 0.1 > 1) {
8 | return 0
9 | }
10 | return progress + 0.1
11 | }, 0.1)
12 |
13 | return (
14 |
15 |
16 |
17 | )
18 | }
19 |
20 | export default function HelloWorld() {
21 | return (
22 |
23 |
24 |
25 | )
26 | }
27 |
28 | const styles = StyleSheet.create({
29 | bar: {
30 | padding: 16,
31 | },
32 | container: {
33 | flex: 1,
34 | justifyContent: 'center',
35 | // flexDirection: 'row',
36 | backgroundColor: '#000',
37 | padding: 16,
38 | },
39 | })
40 |
--------------------------------------------------------------------------------
/examples/sample/src/Moti.Sequence.tsx:
--------------------------------------------------------------------------------
1 | import React, { useReducer } from 'react'
2 | import { StyleSheet, Pressable } from 'react-native'
3 | import { MotiView } from 'moti'
4 |
5 | function Shape() {
6 | return (
7 |
20 | )
21 | }
22 |
23 | function ShapeRotator() {
24 | return (
25 | {
39 | finished && console.log('[moti]', key, value, attemptedValue)
40 | }}
41 | transition={{
42 | rotateZ: {
43 | delay: 900,
44 | },
45 | borderRadius: {
46 | delay: 950,
47 | type: 'timing',
48 | duration: 300,
49 | },
50 | }}
51 | style={styles.shape}
52 | />
53 | )
54 | }
55 |
56 | export default function HelloWorld() {
57 | const [visible, toggle] = useReducer((s) => !s, true)
58 |
59 | return (
60 |
61 | {visible && }
62 |
63 | )
64 | }
65 |
66 | const styles = StyleSheet.create({
67 | shape: {
68 | justifyContent: 'center',
69 | height: 250,
70 | width: 250,
71 | marginRight: 10,
72 | backgroundColor: 'black',
73 | },
74 | container: {
75 | flex: 1,
76 | alignItems: 'center',
77 | justifyContent: 'center',
78 | flexDirection: 'row',
79 | backgroundColor: '#9c1aff',
80 | },
81 | })
82 |
--------------------------------------------------------------------------------
/examples/sample/src/Moti.SequenceLoop.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react'
2 | import { StyleSheet, View } from 'react-native'
3 | import { MotiView } from 'moti'
4 | import {
5 | useSharedValue,
6 | withRepeat,
7 | withTiming,
8 | Easing,
9 | useAnimatedStyle,
10 | interpolate,
11 | } from 'react-native-reanimated'
12 |
13 | const dots = 3
14 |
15 | const Dot = React.memo(function Dot({ index }: { index: number }) {
16 | const timing = useSharedValue(0)
17 |
18 | useEffect(() => {
19 | timing.value = withRepeat(
20 | withTiming(dots + 1, {
21 | duration: 2500,
22 | easing: Easing.inOut(Easing.linear),
23 | }),
24 | -1,
25 | false
26 | )
27 | }, [timing])
28 |
29 | const style = useAnimatedStyle(() => {
30 | return {
31 | opacity: interpolate(
32 | timing.value,
33 | [index, index + 1, index + 2],
34 | [0, 1, 0]
35 | ),
36 | }
37 | }, [])
38 |
39 | return
40 | })
41 |
42 | export default function HelloWorld() {
43 | return (
44 |
45 | {new Array(dots).fill('').map((_, i) => {
46 | return
47 | })}
48 |
49 | )
50 | }
51 |
52 | const styles = StyleSheet.create({
53 | shape: {
54 | justifyContent: 'center',
55 | height: 100,
56 | width: 100,
57 | borderRadius: 25,
58 | marginRight: 10,
59 | backgroundColor: 'cyan',
60 | },
61 | container: {
62 | flex: 1,
63 | alignItems: 'center',
64 | justifyContent: 'center',
65 | flexDirection: 'row',
66 | backgroundColor: '#111',
67 | },
68 | })
69 |
70 | // const delay = 3000
71 |
72 | // const duration = delay / dots
73 | // function Shape({ index }: { index: number }) {
74 | // return (
75 | //
88 | // )
89 | // }
90 |
--------------------------------------------------------------------------------
/examples/sample/src/Moti.Skeleton.tsx:
--------------------------------------------------------------------------------
1 | import React, { useReducer } from 'react'
2 | import { StyleSheet, Pressable } from 'react-native'
3 | import { View } from 'moti'
4 | import { Skeleton } from 'moti/skeleton'
5 |
6 | const Spacer = ({ height = 16 }) =>
7 |
8 | export default function HelloWorld() {
9 | const [dark, toggle] = useReducer((s) => !s, true)
10 |
11 | const colorMode = dark ? 'dark' : 'light'
12 |
13 | return (
14 |
15 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | )
32 | }
33 |
34 | const styles = StyleSheet.create({
35 | shape: {
36 | justifyContent: 'center',
37 | height: 250,
38 | width: 250,
39 | borderRadius: 25,
40 | marginRight: 10,
41 | backgroundColor: 'white',
42 | },
43 | container: {
44 | flex: 1,
45 | // alignItems: 'center',
46 | justifyContent: 'center',
47 | // flexDirection: 'row',
48 | // backgroundColor: '#50E3C2',
49 | },
50 | padded: {
51 | padding: 16,
52 | },
53 | })
54 |
--------------------------------------------------------------------------------
/examples/sample/src/Moti.Svg.tsx:
--------------------------------------------------------------------------------
1 | import React, { ComponentProps } from 'react'
2 |
3 | import { Rect } from 'react-native-svg'
4 | import { MotiView } from 'moti'
5 | import { motifySvg } from '../../../packages/moti/src/svg'
6 | import { useDerivedValue } from 'react-native-reanimated'
7 |
8 | const MotiRect = motifySvg(Rect)()
9 |
10 | type Animate = Omit<
11 | React.PropsWithoutRef>,
12 | 'children'
13 | >
14 |
15 | const animate: Animate = {}
16 |
17 | export default function Svg() {
18 | return (
19 |
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/examples/sample/src/Moti.Transform.tsx:
--------------------------------------------------------------------------------
1 | import React, { useReducer } from 'react'
2 | import { StyleSheet, Pressable } from 'react-native'
3 | import { MotiView } from 'moti'
4 |
5 | function Shape() {
6 | return (
7 |
32 | )
33 | }
34 |
35 | export default function HelloWorld() {
36 | const [visible, toggle] = useReducer((s) => !s, true)
37 |
38 | return (
39 |
40 | {visible && }
41 |
42 | )
43 | }
44 |
45 | const styles = StyleSheet.create({
46 | shape: {
47 | justifyContent: 'center',
48 | height: 250,
49 | width: 250,
50 | borderRadius: 25,
51 | marginRight: 10,
52 | backgroundColor: 'white',
53 | },
54 | container: {
55 | flex: 1,
56 | alignItems: 'center',
57 | justifyContent: 'center',
58 | flexDirection: 'row',
59 | backgroundColor: '#9c1aff',
60 | },
61 | })
62 |
--------------------------------------------------------------------------------
/examples/sample/src/Moti.Variants.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { StyleSheet, Pressable } from 'react-native'
3 | import { View, useAnimationState } from 'moti'
4 |
5 | const useFadeInDown = () => {
6 | return useAnimationState({
7 | from: {
8 | opacity: 0,
9 | translateY: -15,
10 | },
11 | to: {
12 | opacity: 1,
13 | translateY: 0,
14 | },
15 | })
16 | }
17 |
18 | function Shape() {
19 | const fadeInDown = useFadeInDown()
20 |
21 | const scaleIn = useAnimationState({
22 | from: {
23 | // opacity: 0,
24 | // opacity: 1,
25 | scale: 0.5,
26 | },
27 | open: {
28 | // opacity: 1,
29 | // opacity: 1,
30 | scale: 1,
31 | },
32 | small: {
33 | // opacity: 1,
34 | // opacity: 1,
35 | scale: 1.5,
36 | },
37 | })
38 |
39 | const onPress = () => {
40 | fadeInDown.transitionTo((state) => {
41 | if (state === 'from') {
42 | return 'to'
43 | } else {
44 | return 'from'
45 | }
46 | })
47 |
48 | if (scaleIn.current === 'from') {
49 | scaleIn.transitionTo('open')
50 | } else if (scaleIn.current === 'open') {
51 | scaleIn.transitionTo('small')
52 | } else {
53 | scaleIn.transitionTo('from')
54 | }
55 | }
56 |
57 | return (
58 |
59 |
60 |
68 |
69 | )
70 | }
71 |
72 | export default function Variants() {
73 | return (
74 |
75 |
76 |
77 | )
78 | }
79 |
80 | const styles = StyleSheet.create({
81 | shape: {
82 | justifyContent: 'center',
83 | height: 250,
84 | width: 250,
85 | borderRadius: 25,
86 | marginRight: 10,
87 | backgroundColor: 'black',
88 | },
89 | shape2: {
90 | backgroundColor: 'hotpink',
91 | marginTop: 16,
92 | },
93 | container: {
94 | flex: 1,
95 | alignItems: 'center',
96 | justifyContent: 'center',
97 | flexDirection: 'row',
98 | backgroundColor: 'cyan',
99 | },
100 | })
101 |
--------------------------------------------------------------------------------
/examples/sample/src/Perf.List.tsx:
--------------------------------------------------------------------------------
1 | import { useMotify } from 'moti'
2 | import { useEffect, useReducer, useState } from 'react'
3 | import { Button, View, ViewStyle } from 'react-native'
4 | import Animated, { useAnimatedStyle } from 'react-native-reanimated'
5 |
6 | global.shouldDebugMoti = false
7 |
8 | const modes = ['Animated', 'Moti'] as const
9 |
10 | let logs: Record = {}
11 |
12 | const list = new Array(1).fill(null).map((_, i) => i)
13 |
14 | export default function List() {
15 | const [mode, setMode] = useState<(typeof modes)[number]>(modes[0])
16 | const [, render] = useReducer((s) => s + 1, 0)
17 |
18 | let renderStartAt = Date.now()
19 |
20 | useEffect(() => {
21 | const interval = setInterval(() => {
22 | render()
23 |
24 | const maxRendersPerMode = 4
25 |
26 | if (logs[mode]?.length === maxRendersPerMode) {
27 | // remove biggest and smallest values
28 | clearInterval(interval)
29 | const nextMode = modes[modes.indexOf(mode) + 1]
30 |
31 | if (nextMode) {
32 | setMode(nextMode)
33 | } else {
34 | console.log(logs)
35 |
36 | let means = {}
37 | let medians = {}
38 |
39 | for (let key in logs) {
40 | means[key] = logs[key].reduce((a, b) => a + b, 0) / logs[key].length
41 | }
42 |
43 | for (let key in logs) {
44 | const list = logs[key].slice().sort()
45 | medians[key] = list[list.length / 2] ?? list[(list.length - 1) / 2]
46 | }
47 |
48 | console.log('Averages: ', JSON.stringify(means, null, 2))
49 |
50 | console.log('Medians: ', JSON.stringify(medians, null, 2))
51 |
52 | logs = {}
53 | }
54 | }
55 | }, 600)
56 |
57 | return () => {
58 | clearInterval(interval)
59 | }
60 | }, [mode])
61 |
62 | useEffect(
63 | function measurePerf() {
64 | let renderEndAt = Date.now()
65 |
66 | const duration = renderEndAt - renderStartAt
67 |
68 | logs[mode] = logs[mode] || []
69 | logs[mode].push(duration)
70 | },
71 | [mode, renderStartAt]
72 | )
73 |
74 | return (
75 |
82 | {modes.map((m) => (
83 |
95 | )
96 | }
97 |
98 | const Lists = ({ mode }) => {
99 | return (
100 | <>
101 | <>{mode === 'Animated' && list.map((_, i) => )}>
102 | <>{mode === 'Moti' && list.map((_, i) => )}>
103 | >
104 | )
105 | }
106 | const Reanimated = () => {
107 | return (
108 | ({}), [])}>
109 | )
110 | }
111 |
112 | const Moti = () => {
113 | const { style } = useMotify({})
114 | return
115 | }
116 |
--------------------------------------------------------------------------------
/examples/sample/src/Reanimated.Bg-Bug.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Pressable, StyleSheet } from 'react-native'
3 | import Animated, {
4 | useAnimatedStyle,
5 | useSharedValue,
6 | withSpring,
7 | withTiming,
8 | } from 'react-native-reanimated'
9 |
10 | export default function HelloWorld() {
11 | const pressed = useSharedValue(false)
12 | const style = useAnimatedStyle(() => {
13 | return {
14 | backgroundColor: pressed.value ? '#ffffff' : '#000000',
15 | }
16 | })
17 | return (
18 | (pressed.value = true)}
20 | style={styles.container}
21 | onPressOut={() => (pressed.value = false)}
22 | >
23 |
24 |
25 | )
26 | }
27 |
28 | const styles = StyleSheet.create({
29 | shape: {
30 | justifyContent: 'center',
31 | height: 250,
32 | width: 250,
33 | borderRadius: 25,
34 | marginRight: 10,
35 | },
36 | container: {
37 | flex: 1,
38 | alignItems: 'center',
39 | justifyContent: 'center',
40 | flexDirection: 'row',
41 | backgroundColor: '#9c1aff',
42 | },
43 | })
44 |
--------------------------------------------------------------------------------
/examples/sample/src/Reanimated.Width-Bug.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Pressable, StyleSheet } from 'react-native'
3 | import Animated, {
4 | useAnimatedStyle,
5 | useSharedValue,
6 | withTiming,
7 | } from 'react-native-reanimated'
8 |
9 | export default function HelloWorld() {
10 | const pressed = useSharedValue(false)
11 | const style = useAnimatedStyle(() => {
12 | return {
13 | width: pressed.value ? 100 : withTiming(300),
14 | }
15 | })
16 |
17 | return (
18 | (pressed.value = true)}
20 | style={styles.container}
21 | onPressOut={() => (pressed.value = false)}
22 | >
23 |
24 |
25 | )
26 | }
27 |
28 | const styles = StyleSheet.create({
29 | shape: {
30 | justifyContent: 'center',
31 | height: 250,
32 | width: 250,
33 | borderRadius: 25,
34 | marginRight: 10,
35 | backgroundColor: 'black',
36 | },
37 | container: {
38 | flex: 1,
39 | alignItems: 'center',
40 | justifyContent: 'center',
41 | flexDirection: 'row',
42 | backgroundColor: '#9c1aff',
43 | },
44 | })
45 |
--------------------------------------------------------------------------------
/examples/sample/src/Repeat.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { View } from 'react-native'
3 | import {
4 | TapGestureHandler,
5 | TapGestureHandlerGestureEvent,
6 | } from 'react-native-gesture-handler'
7 | import Animated, {
8 | useAnimatedGestureHandler,
9 | useAnimatedStyle,
10 | useSharedValue,
11 | withDelay,
12 | withRepeat,
13 | withTiming,
14 | } from 'react-native-reanimated'
15 | import * as Drip from 'moti'
16 |
17 | export default function Repeat() {
18 | const sv = useSharedValue(false)
19 | const width = useSharedValue(100)
20 |
21 | const onGestureEvent = useAnimatedGestureHandler(
22 | {
23 | onActive: () => {
24 | sv.value = !sv.value
25 | // width.value = withRepeat(
26 | // withTiming(
27 | // 300,
28 | // {
29 | // duration: 3000,
30 | // },
31 | // (finished) => {
32 | // console.log({ finished })
33 | // if (finished) {
34 | // } else {
35 | // console.log('inner animation cancelled')
36 | // }
37 | // }
38 | // ),
39 | // Infinity,
40 | // true
41 | // )
42 | },
43 | }
44 | )
45 |
46 | const style = useAnimatedStyle(() => ({
47 | width: withDelay(
48 | 1000,
49 | withRepeat(
50 | withTiming(
51 | sv.value ? 300 : 100,
52 | {
53 | duration: 1000,
54 | },
55 | (finished, value) => {
56 | console.log({ finished, width: true, value })
57 | }
58 | ),
59 | Infinity,
60 | true
61 | )
62 | ),
63 | }))
64 |
65 | return (
66 |
74 |
75 |
78 |
79 |
80 | )
81 | }
82 |
--------------------------------------------------------------------------------
/examples/sample/src/Sequence.Bug.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { StyleSheet } from 'react-native'
3 | import Animated, {
4 | useAnimatedStyle,
5 | withSequence,
6 | withSpring,
7 | } from 'react-native-reanimated'
8 |
9 | export default function SequenceBug() {
10 | const animatedStyle = useAnimatedStyle(() => ({
11 | transform: [
12 | // {
13 | // scale: withSequence(withSpring(0), withSpring(1), withSpring(2)),
14 | // },
15 | {
16 | translateY: withSequence(withSpring(0), withSpring(10), withSpring(50)),
17 | },
18 | ],
19 | }))
20 | return
21 | }
22 |
23 | const style = StyleSheet.create({
24 | item: {
25 | margin: 100,
26 | width: 300,
27 | height: 300,
28 | backgroundColor: 'blue',
29 | alignSelf: 'center',
30 | borderRadius: 999,
31 | },
32 | })
33 |
--------------------------------------------------------------------------------
/examples/sample/src/Web.tsx:
--------------------------------------------------------------------------------
1 | export default 'web'
--------------------------------------------------------------------------------
/examples/sample/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "paths": {
4 | "moti": ["../../packages/moti/src"]
5 | },
6 | "composite": true,
7 | "allowUnreachableCode": false,
8 | "allowUnusedLabels": false,
9 | "esModuleInterop": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "jsx": "react-jsx",
12 | "lib": ["esnext", "dom"],
13 | "module": "esnext",
14 | "moduleResolution": "node",
15 | "noFallthroughCasesInSwitch": true,
16 | "noImplicitReturns": true,
17 | "noImplicitUseStrict": false,
18 | "noStrictGenericChecks": false,
19 | "noUnusedLocals": true,
20 | "noUnusedParameters": true,
21 | "resolveJsonModule": true,
22 | "skipLibCheck": true,
23 | "strict": true,
24 | "target": "esnext",
25 | "noImplicitAny": false
26 | },
27 | "extends": "expo/tsconfig.base"
28 | }
29 |
--------------------------------------------------------------------------------
/examples/with-next/.gitignore:
--------------------------------------------------------------------------------
1 | # @generated: @expo/next-adapter@2.1.0
2 | node_modules/**/*
3 | .expo/*
4 | npm-debug.*
5 | *.jks
6 | *.p8
7 | *.p12
8 | *.key
9 | *.mobileprovision
10 | *.orig.*
11 | web-build/
12 | web-report/
13 |
14 | # debug
15 | yarn-debug.log*
16 | yarn-error.log*
17 |
18 | # macOS
19 | .DS_Store
20 |
21 | # misc
22 | .DS_Store
23 | .env*
24 |
25 | .next
--------------------------------------------------------------------------------
/examples/with-next/App.tsx:
--------------------------------------------------------------------------------
1 | import { AnimatePresence } from 'framer-motion'
2 | import React, { useEffect, useReducer, useState } from 'react'
3 | import { StyleSheet, Pressable } from 'react-native'
4 | import * as Moti from 'moti'
5 | import Animated, {
6 | useAnimatedStyle,
7 | withTiming,
8 | useSharedValue,
9 | } from 'react-native-reanimated'
10 |
11 | const { View } = Moti
12 |
13 | function MotiShape() {
14 | return (
15 |
33 | )
34 | }
35 |
36 | function Shape() {
37 | const scale = useSharedValue(0)
38 | const style = useAnimatedStyle(() => {
39 | return {
40 | transform: [
41 | {
42 | scale: scale.value,
43 | opacity: scale.value,
44 | },
45 | ],
46 | }
47 | })
48 |
49 | useEffect(() => {
50 | scale.value = withTiming(1)
51 | }, [scale])
52 |
53 | return
54 | }
55 |
56 | export default function Presence() {
57 | const [visible, toggle] = useReducer((s) => !s, true)
58 |
59 | // return
60 | return (
61 |
62 |
63 | {/* {visible && }
64 | {!visible && } */}
65 | {/* {visible && } */}
66 |
67 | )
68 | }
69 |
70 | const styles = StyleSheet.create({
71 | shape: {
72 | justifyContent: 'center',
73 | height: 250,
74 | width: 250,
75 | borderRadius: 25,
76 | backgroundColor: 'white',
77 | },
78 | container: {
79 | flex: 1,
80 | alignItems: 'center',
81 | justifyContent: 'center',
82 | flexDirection: 'row',
83 | backgroundColor: '#9c1aff',
84 | },
85 | })
86 |
--------------------------------------------------------------------------------
/examples/with-next/README.md:
--------------------------------------------------------------------------------
1 | # Dripsy + [Next.js Example](https://www.nextjs.org/)
2 |
3 | To run this example, clone the root repository.
4 |
5 | ```sh
6 | git clone https://github.com/nandorojo/dripsy
7 | cd dripsy
8 | ```
9 |
10 | Link & install dependencies
11 |
12 | ```sh
13 | yarn
14 | yarn link
15 | cd next-example
16 | yarn
17 | yarn link dripsy
18 | ```
19 |
20 | Run it
21 |
22 | ```sh
23 | yarn next
24 | ```
25 |
--------------------------------------------------------------------------------
/examples/with-next/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dripsy-next-example",
3 | "displayName": "Dripsy Example",
4 | "expo": {
5 | "name": "dripsy-next-example",
6 | "slug": "dripsy-next-example",
7 | "description": "Example app for expo-theme-ui",
8 | "privacy": "public",
9 | "version": "1.0.0",
10 | "platforms": ["ios", "android", "web"],
11 | "ios": {
12 | "supportsTablet": true
13 | },
14 | "assetBundlePatterns": ["**/*"],
15 | "packagerOpts": {
16 | "config": "metro.config.js"
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/examples/with-next/babel.config.js:
--------------------------------------------------------------------------------
1 | // @generated: @expo/next-adapter@2.1.32
2 | // Learn more: https://github.com/expo/expo/blob/master/docs/pages/versions/unversioned/guides/using-nextjs.md#shared-steps
3 |
4 | // module.exports = { presets: ['@expo/next-adapter/babel'] }
5 |
6 | module.exports = function (api) {
7 | api.cache(true)
8 | return {
9 | presets: ['@expo/next-adapter/babel'],
10 | plugins: ['react-native-reanimated/plugin'],
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/with-next/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
--------------------------------------------------------------------------------
/examples/with-next/next.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/camelcase */
2 | /* eslint-disable @typescript-eslint/no-var-requires */
3 | // @generated: @expo/next-adapter@2.1.0
4 | // Learn more: https://github.com/expo/expo/blob/master/docs/pages/versions/unversioned/guides/using-nextjs.md#withexpo
5 | const { withExpo } = require('@expo/next-adapter')
6 | const withPlugins = require('next-compose-plugins')
7 | const path = require('path')
8 | const fs = require('fs')
9 |
10 | const packages = path.resolve(__dirname, '../..', 'packages')
11 |
12 | const getPackageName = (name) => {
13 | if (name === 'moti') return 'moti'
14 | else return `@motify/${name}`
15 | }
16 |
17 | const packageFolderNames = fs
18 | .readdirSync(packages)
19 | .filter((name) => !name.startsWith('.'))
20 |
21 | const withTM = require('next-transpile-modules')(
22 | packageFolderNames.map((name) => getPackageName(name))
23 | )
24 |
25 | module.exports = withPlugins([
26 | withTM({
27 | webpack: (config) => {
28 | packageFolderNames.forEach((name) => {
29 | config.resolve.alias[getPackageName(name)] = path.resolve(
30 | packages,
31 | name,
32 | require(`../../packages/${name}/package.json`).source
33 | )
34 | })
35 |
36 | return config
37 | },
38 | }),
39 | [
40 | withExpo,
41 | {
42 | projectRoot: __dirname,
43 | },
44 | ],
45 | ])
46 |
--------------------------------------------------------------------------------
/examples/with-next/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next-dripsy-example",
3 | "version": "0.27.2",
4 | "main": "__generated__/AppEntry.js",
5 | "dependencies": {
6 | "expo": "^40.0.0",
7 | "next": "9.4.4",
8 | "next-compose-plugins": "^2.2.0",
9 | "next-transpile-modules": "3.3.0",
10 | "raf": "^3.4.1",
11 | "react": "17.0.1",
12 | "react-dom": "17.0.1",
13 | "react-native-reanimated": "3.11.0",
14 | "react-native-unimodules": "~0.9.0",
15 | "react-native-web": "0.15.3"
16 | },
17 | "devDependencies": {
18 | "@babel/core": "7.9.0",
19 | "@expo/next-adapter": "2.1.0"
20 | },
21 | "scripts": {
22 | "start": "expo start",
23 | "android": "expo start --android",
24 | "ios": "expo start --ios",
25 | "web": "yarn next dev",
26 | "eject": "expo eject"
27 | },
28 | "private": true
29 | }
30 |
--------------------------------------------------------------------------------
/examples/with-next/pages/_app.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | // pages/_app.js
4 | import 'raf/polyfill'
5 | // order matters here
6 | global.setImmediate = requestAnimationFrame
7 |
8 | export default function App({ Component, pageProps }) {
9 | return
10 | }
11 |
--------------------------------------------------------------------------------
/examples/with-next/pages/_document.js:
--------------------------------------------------------------------------------
1 | // Based on https://github.com/zeit/next.js/tree/canary/examples/with-react-native-web
2 | // and https://github.com/expo/expo-cli/blob/master/packages/webpack-config/web-default/index.html
3 | import NextDocument, { Head, Main, NextScript } from 'next/document'
4 | import * as React from 'react'
5 | // import { SSRStyleReset } from 'dripsy'
6 |
7 | import { AppRegistry } from 'react-native'
8 |
9 | export const style = `
10 | /**
11 | * Building on the RNWeb reset:
12 | * https://github.com/necolas/react-native-web/blob/master/packages/react-native-web/src/exports/StyleSheet/initialRules.js
13 | */
14 | html, body, #__next {
15 | width: 100%;
16 | /* To smooth any scrolling behavior */
17 | -webkit-overflow-scrolling: touch;
18 | margin: 0px;
19 | padding: 0px;
20 | /* Allows content to fill the viewport and go beyond the bottom */
21 | min-height: 100%;
22 | }
23 | #__next {
24 | flex-shrink: 0;
25 | flex-basis: auto;
26 | flex-direction: column;
27 | flex-grow: 1;
28 | display: flex;
29 | flex: 1;
30 | }
31 | html {
32 | scroll-behavior: smooth;
33 | /* Prevent text size change on orientation change https://gist.github.com/tfausak/2222823#file-ios-8-web-app-html-L138 */
34 | -webkit-text-size-adjust: 100%;
35 | height: 100%;
36 | }
37 | body {
38 | display: flex;
39 | /* Allows you to scroll below the viewport; default value is visible */
40 | overflow-y: auto;
41 | overscroll-behavior-y: none;
42 | text-rendering: optimizeLegibility;
43 | -webkit-font-smoothing: antialiased;
44 | -moz-osx-font-smoothing: grayscale;
45 | -ms-overflow-style: scrollbar;
46 | }
47 | `
48 |
49 | export async function getInitialProps({ renderPage }) {
50 | AppRegistry.registerComponent('Main', () => Main)
51 | const { getStyleElement } = AppRegistry.getApplication('Main')
52 | const page = renderPage()
53 | const styles = [
54 | ,
58 | getStyleElement(),
59 | ]
60 | return { ...page, styles: React.Children.toArray(styles) }
61 | }
62 |
63 | export class Document extends NextDocument {
64 | static getInitialProps = getInitialProps
65 | render() {
66 | return (
67 |
68 |
69 | {/* */}
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | )
78 | }
79 | }
80 |
81 | export default Document
82 |
--------------------------------------------------------------------------------
/examples/with-next/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import 'raf/polyfill'
2 |
3 | // @ts-ignore really annoying reanimated bug
4 | global.setImmediate = requestAnimationFrame
5 | export { default } from '../App'
6 |
--------------------------------------------------------------------------------
/examples/with-next/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "baseUrl": ".",
6 | "paths": {
7 | "moti": ["../../packages/moti/src"]
8 | },
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "strict": false,
12 | "forceConsistentCasingInFileNames": true,
13 | "noEmit": true,
14 | "esModuleInterop": true,
15 | "module": "esnext",
16 | "moduleResolution": "node",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "jsx": "preserve"
20 | },
21 | "exclude": ["node_modules"],
22 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
23 | }
24 |
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "packages": [
3 | "packages/*",
4 | "examples/*"
5 | ],
6 | "npmClient": "npm",
7 | "useWorkspaces": true,
8 | "version": "0.29.0",
9 | "command": {
10 | "publish": {
11 | "allowBranch": "master",
12 | "conventionalCommits": true,
13 | "createRelease": "github",
14 | "message": "chore: publish",
15 | "ignoreChanges": [
16 | "**/__fixtures__/**",
17 | "**/__tests__/**",
18 | "**/*.md",
19 | "**/example/**"
20 | ]
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/license.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Fernando Rojo
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "workspaces": {
4 | "packages": [
5 | "packages/*"
6 | ]
7 | },
8 | "publishConfig": {
9 | "registry": "https://registry.npmjs.org/"
10 | },
11 | "license": "MIT",
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/nandorojo/moti.git"
15 | },
16 | "scripts": {
17 | "lint": "eslint \"**/*.{js,ts,tsx}\"",
18 | "fix": "eslint --fix \"**/*.{js,ts,tsx}\" && manypkg fix && prettier --write \"**/*.{js,ts,tsx}\"",
19 | "typescript": "tsc --noEmit --composite false",
20 | "test": "jest",
21 | "build": "lerna run prepare",
22 | "prerelease": "lerna run clean",
23 | "release-old": "lerna publish",
24 | "release": "auto shipit",
25 | "example": "yarn --cwd example",
26 | "prepare": "lerna run prepare"
27 | },
28 | "commitlint": {
29 | "extends": [
30 | "@commitlint/config-conventional"
31 | ]
32 | },
33 | "jest": {
34 | "preset": "react-native",
35 | "modulePathIgnorePatterns": [
36 | "/example/node_modules",
37 | "/lib/"
38 | ]
39 | },
40 | "eslintConfig": {
41 | "extends": [
42 | "eslint-config-nando"
43 | ]
44 | },
45 | "detox": {
46 | "test-runner": "jest",
47 | "runner-config": "example/e2e/config.json",
48 | "configurations": {
49 | "ios.sim.debug": {
50 | "binaryPath": "example/ios/build/Build/Products/Debug-iphonesimulator/ReactNavigationExample.app",
51 | "build": "set -o pipefail; xcodebuild -workspace example/ios/ReactNavigationExample.xcworkspace -scheme ReactNavigationExample -configuration Debug -sdk iphonesimulator -derivedDataPath example/ios/build",
52 | "type": "ios.simulator",
53 | "device": {
54 | "type": "iPhone 11 Pro"
55 | }
56 | },
57 | "ios.sim.release": {
58 | "binaryPath": "example/ios/build/Build/Products/Release-iphonesimulator/ReactNavigationExample.app",
59 | "build": "export RCT_NO_LAUNCH_PACKAGER=true; set -o pipefail; xcodebuild -workspace example/ios/ReactNavigationExample.xcworkspace -scheme ReactNavigationExample -configuration Release -sdk iphonesimulator -derivedDataPath example/ios/build",
60 | "type": "ios.simulator",
61 | "device": {
62 | "type": "iPhone 11 Pro"
63 | }
64 | }
65 | }
66 | },
67 | "devDependencies": {
68 | "@babel/helper-string-parser": "^7.22.5",
69 | "@commitlint/config-conventional": "^11.0.0",
70 | "@manypkg/cli": "^0.17.0",
71 | "@types/jest": "^26.0.15",
72 | "@typescript-eslint/eslint-plugin": "^4.14.1",
73 | "@typescript-eslint/parser": "^4.14.1",
74 | "auto": "^10.13.3",
75 | "babel-jest": "^26.6.1",
76 | "codecov": "^3.8.0",
77 | "commitlint": "^11.0.0",
78 | "eslint": "^7.18.0",
79 | "husky": "^4.3.8",
80 | "jest": "^26.6.1",
81 | "lerna": "^3.22.1",
82 | "prettier": "^2.2.1",
83 | "react-native": "^0.74.1",
84 | "typescript": "^5.2.0"
85 | },
86 | "author": "Fernando Rojo ",
87 | "auto": {
88 | "plugins": [
89 | "npm",
90 | "all-contributors",
91 | "first-time-contributor",
92 | "released",
93 | "slack",
94 | "twitter"
95 | ],
96 | "onlyPublishWithReleaseLabel": true,
97 | "labels": [
98 | {
99 | "name": "minor",
100 | "changelogTitle": "🚀 Enhancement",
101 | "description": "Increment the minor version when merged",
102 | "releaseType": "minor",
103 | "color": "8000c5",
104 | "overwrite": true
105 | }
106 | ]
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/packages/moti/.eslintrc.js:
--------------------------------------------------------------------------------
1 | // @generated by expo-module-scripts
2 | module.exports = require('expo-module-scripts/eslintrc.base.js');
3 |
--------------------------------------------------------------------------------
/packages/moti/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | The universal React Native animation library, powered by Reanimated 3.
4 |
5 | ```jsx
6 |
7 | ```
8 |
9 | # Documentation & Examples
10 |
11 | - [Documentation](https://moti.fyi)
12 | - [Installation](https://moti.fyi/installation)
13 | - [Examples](https://moti.fyi/examples/hello-world)
14 |
15 | ## Next.js Conf
16 |
17 |
22 |
23 | I spoke at at [Next.js Conf 2021](https://fernandorojo.co/conf) on October 26 about React Native + Next.js. [Watch the video](https://t.co/LkmxHXVz3K?amp=1) to see how we do it.
24 |
25 | # Highlights
26 |
27 | - Universal: works on all platforms
28 | - 60 FPS animations on the native thread
29 | - Mount/unmount animations, like `framer-motion`
30 | - Powered by Reanimated 3
31 | - Web support, out-of-the-box
32 | - Expo support
33 | - Intuitive API
34 | - Variants
35 | - Strong TypeScript support
36 | - Highly-configurable animations
37 | - Sequence animations
38 | - Loop & repeat animations
39 |
40 | # Preview
41 |
42 | - [API](https://twitter.com/FernandoTheRojo/status/1348093995277299712)
43 | - [Unmount animations with `exit`](https://twitter.com/FernandoTheRojo/status/1349884929765765123)
44 | - [`exitBeforeEnter` animations](https://twitter.com/FernandoTheRojo/status/1351234878902333445)
45 |
46 | # Follow
47 |
48 | Follow me [on Twitter](https://twitter.com/fernandotherojo) to stay up to date.
49 |
50 | # Sponsor
51 |
52 | Please reach out to [Fernando Rojo](https://twitter.com/fernandotherojo) if you're interested in sponsoring Moti.
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/packages/moti/author/index.d.ts:
--------------------------------------------------------------------------------
1 | export * from '../build/author'
2 |
--------------------------------------------------------------------------------
/packages/moti/author/index.js:
--------------------------------------------------------------------------------
1 | export * from '../build/author'
2 |
--------------------------------------------------------------------------------
/packages/moti/index.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 | ///
4 | ///
5 |
6 | export * from './build'
7 |
--------------------------------------------------------------------------------
/packages/moti/interactions/index.d.ts:
--------------------------------------------------------------------------------
1 | export * from '../build/interactions'
2 |
--------------------------------------------------------------------------------
/packages/moti/interactions/index.js:
--------------------------------------------------------------------------------
1 | export * from '../build/interactions'
2 | //# sourceMappingURL=index.js.map
3 |
--------------------------------------------------------------------------------
/packages/moti/interactions/index.js.map:
--------------------------------------------------------------------------------
1 | {
2 | "version": 3,
3 | "file": "index.js",
4 | "sourceRoot": "",
5 | "sources": [
6 | "../src/interactions/index.ts"
7 | ],
8 | "names": [],
9 | "mappings": "AAAA,cAAc,aAAa,CAAA",
10 | "sourcesContent": [
11 | "export * from './pressable'\n"
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/packages/moti/metro.config.js:
--------------------------------------------------------------------------------
1 | // Learn more https://docs.expo.io/guides/customizing-metro
2 | const { getDefaultConfig } = require('expo/metro-config')
3 | const path = require('path')
4 |
5 | const config = getDefaultConfig(__dirname)
6 |
7 | // npm v7+ will install ../node_modules/react-native because of peerDependencies.
8 | // To prevent the incompatible react-native bewtween ./node_modules/react-native and ../node_modules/react-native,
9 | // excludes the one from the parent folder when bundling.
10 | config.resolver.blockList = [
11 | ...Array.from(config.resolver.blockList ?? []),
12 | new RegExp(path.resolve('../..', 'node_modules', 'react-native')),
13 | ]
14 |
15 | config.resolver.nodeModulesPaths = [
16 | path.resolve(__dirname, './node_modules'),
17 | path.resolve(__dirname, '../../node_modules'),
18 | ]
19 |
20 | config.resolver.assetExts.push('mjs')
21 | config.resolver.assetExts.push('cjs')
22 |
23 | config.watchFolders = [path.resolve(__dirname, '../..')]
24 |
25 | config.transformer.getTransformOptions = async () => ({
26 | transform: {
27 | experimentalImportSupport: false,
28 | inlineRequires: true,
29 | },
30 | })
31 |
32 | module.exports = config
33 |
--------------------------------------------------------------------------------
/packages/moti/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "moti",
3 | "description": "The universal React Native animation library, powered by Reanimated 3. 🦉",
4 | "version": "0.30.0",
5 | "keywords": [
6 | "react-native",
7 | "ios",
8 | "android",
9 | "web"
10 | ],
11 | "license": "MIT",
12 | "repository": {
13 | "type": "git",
14 | "url": "https://github.com/nandorojo/moti.git",
15 | "directory": "packages/moti"
16 | },
17 | "bugs": {
18 | "url": "https://github.com/nandorojo/moti/issues"
19 | },
20 | "homepage": "https://github.com/nandorojo/moti.git#readme",
21 | "main": "build/index.js",
22 | "react-native": "src/index.tsx",
23 | "source": "src/index.tsx",
24 | "types": "./index.d.ts",
25 | "files": [
26 | "src",
27 | "build",
28 | "!**/__tests__",
29 | "interactions",
30 | "skeleton",
31 | "index.d.ts",
32 | "svg",
33 | "author"
34 | ],
35 | "exports": {
36 | ".": {
37 | "import": "./build/index.js",
38 | "require": "./build/index.js",
39 | "types": "./index.d.ts"
40 | },
41 | "./interactions": {
42 | "import": "./build/interactions/index.js",
43 | "require": "./build/interactions/index.js",
44 | "types": "./interactions/index.d.ts"
45 | },
46 | "./skeleton": {
47 | "import": "./build/skeleton/index.js",
48 | "require": "./build/skeleton/index.js",
49 | "types": "./skeleton/index.d.ts"
50 | },
51 | "./svg": {
52 | "import": "./build/svg/index.js",
53 | "require": "./build/svg/index.js",
54 | "types": "./svg/index.d.ts"
55 | },
56 | "./author": {
57 | "import": "./build/author/index.js",
58 | "require": "./build/author/index.js",
59 | "types": "./author/index.d.ts"
60 | }
61 | },
62 | "sideEffects": false,
63 | "publishConfig": {
64 | "access": "public"
65 | },
66 | "scripts": {
67 | "prepare:bob": "bob build",
68 | "clean:bob": "del lib",
69 | "build": "expo-module build",
70 | "clean": "expo-module clean",
71 | "test": "expo-module test",
72 | "prepare": "yarn expo-module prepare",
73 | "prepublishOnly": "expo-module prepublishOnly"
74 | },
75 | "peerDependencies": {
76 | "react-native-reanimated": "*"
77 | },
78 | "devDependencies": {
79 | "@react-navigation/native": "^6.1.18",
80 | "expo-linear-gradient": "^10.0.3",
81 | "expo-module-scripts": "^3.4.1",
82 | "react-native-linear-gradient": "^2.6.2",
83 | "react-native-reanimated": "3.11.0",
84 | "typescript": "^5.2.0"
85 | },
86 | "react-native-builder-bob": {
87 | "source": "src",
88 | "output": "lib",
89 | "targets": [
90 | "commonjs",
91 | "module",
92 | [
93 | "typescript",
94 | {
95 | "project": "tsconfig.build.json"
96 | }
97 | ]
98 | ]
99 | },
100 | "gitHead": "36d489545d0a4e3b83af8650bdb9881a251fa777",
101 | "dependencies": {
102 | "framer-motion": "^6.5.1"
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/packages/moti/skeleton/index.d.ts:
--------------------------------------------------------------------------------
1 | export * from '../build/skeleton'
2 |
--------------------------------------------------------------------------------
/packages/moti/skeleton/index.js:
--------------------------------------------------------------------------------
1 | export * from '../build/skeleton'
2 | //# sourceMappingURL=index.js.map
3 |
--------------------------------------------------------------------------------
/packages/moti/skeleton/react-native-linear-gradient.js:
--------------------------------------------------------------------------------
1 | export * from '../build/skeleton/native'
2 |
--------------------------------------------------------------------------------
/packages/moti/src/author/index.ts:
--------------------------------------------------------------------------------
1 | export * from '../core/use-motify'
2 |
--------------------------------------------------------------------------------
/packages/moti/src/components/image.tsx:
--------------------------------------------------------------------------------
1 | import { Image as RImage } from 'react-native'
2 |
3 | import { motify } from '../core'
4 |
5 | export const Image = motify(RImage)()
6 |
--------------------------------------------------------------------------------
/packages/moti/src/components/index.ts:
--------------------------------------------------------------------------------
1 | export { Image } from './image'
2 | export { Image as MotiImage } from './image'
3 | export { SafeAreaView } from './safe-area-view'
4 | export { SafeAreaView as MotiSafeAreaView } from './safe-area-view'
5 | export { ScrollView } from './scroll-view'
6 | export { ScrollView as MotiScrollView } from './scroll-view'
7 | export { Text } from './text'
8 | export { Text as MotiText } from './text'
9 | export { View } from './view'
10 | export { View as MotiView } from './view'
11 |
12 | export { MotiProgressBar } from './progress'
13 | export type { MotiProgressBarProps } from './progress'
14 |
--------------------------------------------------------------------------------
/packages/moti/src/components/safe-area-view.tsx:
--------------------------------------------------------------------------------
1 | import { SafeAreaView as RSafeAreaView } from 'react-native'
2 |
3 | import { motify } from '../core'
4 |
5 | export const SafeAreaView = motify(RSafeAreaView)()
6 |
--------------------------------------------------------------------------------
/packages/moti/src/components/scroll-view.tsx:
--------------------------------------------------------------------------------
1 | import { ScrollView as RScrollView } from 'react-native'
2 |
3 | import { motify } from '../core'
4 |
5 | export const ScrollView = motify(RScrollView)()
6 |
--------------------------------------------------------------------------------
/packages/moti/src/components/text.tsx:
--------------------------------------------------------------------------------
1 | import { Text as RText } from 'react-native'
2 |
3 | import { motify } from '../core'
4 |
5 | export const Text = motify(RText)()
6 |
--------------------------------------------------------------------------------
/packages/moti/src/components/view.tsx:
--------------------------------------------------------------------------------
1 | import { View as RView } from 'react-native'
2 |
3 | import { motify } from '../core'
4 |
5 | export const View = motify(RView)()
6 |
--------------------------------------------------------------------------------
/packages/moti/src/core/constants/color-keys.ts:
--------------------------------------------------------------------------------
1 | export const colorKeys = [
2 | 'backgroundColor',
3 | 'borderBottomColor',
4 | 'borderColor',
5 | 'borderEndColor',
6 | 'borderLeftColor',
7 | 'borderRightColor',
8 | 'borderStartColor',
9 | 'borderTopColor',
10 | 'color',
11 | ]
12 |
--------------------------------------------------------------------------------
/packages/moti/src/core/constants/index.ts:
--------------------------------------------------------------------------------
1 | export * from './color-keys';
2 | export * from './package-name';
--------------------------------------------------------------------------------
/packages/moti/src/core/constants/package-name.ts:
--------------------------------------------------------------------------------
1 | // import Package from '../../package.json'
2 | export const PackageName = 'moti'
3 |
--------------------------------------------------------------------------------
/packages/moti/src/core/index.ts:
--------------------------------------------------------------------------------
1 | export { default as motify } from './motify'
2 | export { AnimatePresence } from 'framer-motion'
3 |
4 | export * from './types'
5 | export { default as useAnimationState } from './use-animator'
6 | export { default as useDynamicAnimation } from './use-dynamic-animation'
7 | export { useMotify } from './use-motify'
8 | export { colorKeys, PackageName } from './constants'
9 |
--------------------------------------------------------------------------------
/packages/moti/src/core/motify.tsx:
--------------------------------------------------------------------------------
1 | import { usePresence, PresenceContext } from 'framer-motion'
2 | import React, {
3 | forwardRef,
4 | ComponentType,
5 | FunctionComponent,
6 | useContext,
7 | } from 'react'
8 | import type { ImageStyle, TextStyle, ViewStyle } from 'react-native'
9 | import Animated, {
10 | BaseAnimationBuilder,
11 | EntryExitAnimationFunction,
12 | LayoutAnimationFunction,
13 | } from 'react-native-reanimated'
14 |
15 | import type { MotiProps } from './types'
16 | import { useMotify } from './use-motify'
17 |
18 | export default function motify<
19 | Props extends object,
20 | Ref,
21 | Animate = ViewStyle | ImageStyle | TextStyle
22 | >(ComponentWithoutAnimation: ComponentType) {
23 | const Component = Animated.createAnimatedComponent(
24 | ComponentWithoutAnimation as FunctionComponent
25 | )
26 |
27 | const withAnimations = () => {
28 | const Motified = forwardRef<
29 | Ref,
30 | Props &
31 | AnimatedProps &
32 | MotiProps & {
33 | children?: React.ReactNode
34 | }
35 | >(function Moti(props, ref) {
36 | const animated = useMotify({
37 | ...props,
38 | usePresenceValue: usePresence(),
39 | presenceContext: useContext(PresenceContext),
40 | })
41 |
42 | const style = (props as any).style
43 |
44 | return (
45 |
50 | )
51 | })
52 |
53 | Motified.displayName = `Moti.${
54 | ComponentWithoutAnimation.displayName ||
55 | ComponentWithoutAnimation.name ||
56 | 'NoName'
57 | }`
58 |
59 | return Motified
60 | }
61 |
62 | return withAnimations
63 | }
64 |
65 | // copied from reanimated
66 | // if we use Animated.AnimateProps
67 | // then we get this TypeScript error:
68 | // Exported variable 'View' has or is using name 'AnimatedNode' from external module "react-native-reanimated" but cannot be named.
69 | type AnimatedProps = {
70 | animatedProps?: Partial
71 | layout?:
72 | | BaseAnimationBuilder
73 | | LayoutAnimationFunction
74 | | typeof BaseAnimationBuilder
75 | entering?:
76 | | BaseAnimationBuilder
77 | | typeof BaseAnimationBuilder
78 | | EntryExitAnimationFunction
79 | | Keyframe
80 | exiting?:
81 | | BaseAnimationBuilder
82 | | typeof BaseAnimationBuilder
83 | | EntryExitAnimationFunction
84 | | Keyframe
85 | }
86 |
--------------------------------------------------------------------------------
/packages/moti/src/core/use-dynamic-animation/index.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | DynamicStyleProp,
3 | ExcludeFunctionKeys,
4 | UseDynamicAnimationState,
5 | } from '../types'
6 | import { useSharedValue } from 'react-native-reanimated'
7 | import { useRef } from 'react'
8 | import { ViewStyle, TextStyle, ImageStyle } from 'react-native'
9 |
10 | const fallback = () => ({})
11 |
12 | /**
13 | * A hook that acts like `useAnimationState`, except that it allows for dynamic values rather than static variants.
14 | *
15 | * This is useful when you want to update styles on the fly the way you do with `useState`.
16 | *
17 | * You can change the state by calling `state.animateTo()`, and access the current state by calling `state.current`.
18 | *
19 | * This hook has high performance, triggers no state changes, and runs fully on the native thread!
20 | *
21 | * ```js
22 | * const dynamicAnimation = useDynamicAnimation(() => ({ opacity: 0 }))
23 | *
24 | * const onPress = () => {
25 | * dynamicAnimation.animateTo({ opacity: 1 })
26 | * }
27 | *
28 | * const onMergeStyle = () => {
29 | * // or, merge your styles
30 | * // this uses the previous state, like useState from react
31 | * dynamicAnimation.animateTo((current) => ({ ...current, scale: 1 }))
32 | *
33 | * // you can also synchronously read the current value
34 | * // these two options are the same!
35 | * dynamicAnimation.animateTo({ ...dynamicAnimation.current, scale: 1 })
36 | * }
37 | * ```
38 | *
39 | * @param initialState A function that returns your initial style. Similar to `useState`'s initial style.
40 | */
41 | export default function useDynamicAnimation<
42 | _Animate = ViewStyle | TextStyle | ImageStyle,
43 | Animate = ExcludeFunctionKeys<_Animate>
44 | >(initialState: () => DynamicStyleProp = fallback) {
45 | const initializer = useRef<{ value: DynamicStyleProp }>(null as any)
46 | if (initializer.current === null) {
47 | // use a .value to be certain it's never been set
48 | initializer.current = { value: initialState() }
49 | }
50 |
51 | const __state = useSharedValue(initializer.current.value)
52 |
53 | const controller = useRef>(null)
54 |
55 | if (controller.current == null) {
56 | controller.current = {
57 | __state,
58 | // @ts-ignore
59 | get current(): DynamicStyleProp {
60 | return __state.value
61 | },
62 | animateTo(nextStateOrFunction) {
63 | 'worklet'
64 |
65 | const nextStyle =
66 | typeof nextStateOrFunction === 'function'
67 | ? nextStateOrFunction(__state.value)
68 | : nextStateOrFunction
69 |
70 | __state.value = nextStyle
71 | },
72 | }
73 | }
74 |
75 | return controller.current as UseDynamicAnimationState
76 | }
77 |
--------------------------------------------------------------------------------
/packages/moti/src/index.tsx:
--------------------------------------------------------------------------------
1 | export * from './core'
2 | export * from './components'
3 |
--------------------------------------------------------------------------------
/packages/moti/src/interactions/index.ts:
--------------------------------------------------------------------------------
1 | export * from './pressable'
2 |
--------------------------------------------------------------------------------
/packages/moti/src/interactions/pressable/context.tsx:
--------------------------------------------------------------------------------
1 | import { createContext, useContext } from 'react'
2 | import type { MotiPressableInteractionState } from './types'
3 | import type Animated from 'react-native-reanimated'
4 |
5 | export const INTERACTION_CONTAINER_ID = '__INTERACTION_CONTAINER_ID' as const
6 |
7 | export interface MotiPressableInteractionIds {
8 | id: string
9 | }
10 |
11 | export type MotiPressableContext = {
12 | containers: Record<
13 | MotiPressableInteractionIds['id'] | typeof INTERACTION_CONTAINER_ID,
14 | Animated.SharedValue
15 | >
16 | }
17 |
18 | export const MotiPressableContext = createContext({
19 | containers: {},
20 | })
21 |
22 | export const useMotiPressableContext = () => useContext(MotiPressableContext)
23 |
--------------------------------------------------------------------------------
/packages/moti/src/interactions/pressable/hoverable-context.tsx:
--------------------------------------------------------------------------------
1 | import { createContext, useContext } from 'react'
2 | import type { SharedValue } from 'react-native-reanimated'
3 |
4 | const HoveredContext = createContext({
5 | value: false,
6 | } as SharedValue)
7 |
8 | const useIsHovered = () => {
9 | return useContext(HoveredContext)
10 | }
11 |
12 | export { HoveredContext, useIsHovered }
13 |
--------------------------------------------------------------------------------
/packages/moti/src/interactions/pressable/hoverable.native.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useSharedValue } from 'react-native-reanimated'
3 | import { HoveredContext } from './hoverable-context'
4 |
5 | export function Hoverable({ children }) {
6 | return (
7 |
8 | {React.Children.only(children)}
9 |
10 | )
11 | }
12 |
--------------------------------------------------------------------------------
/packages/moti/src/interactions/pressable/index.tsx:
--------------------------------------------------------------------------------
1 | export * from './pressable'
2 | export * from './use-pressable'
3 | export * from './types'
4 | export * from './use-pressables'
5 | export { useMotiPressableContext } from './context'
6 | export * from './use-moti-pressable-animated-props'
7 | export * from './use-moti-pressable-interpolate'
8 | export * from './use-moti-pressable-transition'
9 | export * from './merge'
10 | export { Hoverable as MotiHover } from './hoverable'
11 | export { useIsHovered as useMotiHover } from './hoverable-context'
12 |
--------------------------------------------------------------------------------
/packages/moti/src/interactions/pressable/merge-refs.ts:
--------------------------------------------------------------------------------
1 | // credit: https://github.com/gregberge/react-merge-refs/blob/main/src/index.tsx
2 |
3 | export function mergeRefs(
4 | refs: Array | React.LegacyRef>
5 | ): React.RefCallback {
6 | return (value) => {
7 | refs.forEach((ref) => {
8 | if (typeof ref === 'function') {
9 | ref(value)
10 | } else if (ref != null) {
11 | ;(ref as React.MutableRefObject).current = value
12 | }
13 | })
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/moti/src/interactions/pressable/merge.ts:
--------------------------------------------------------------------------------
1 | import type { MotiPressableInteractionState, MotiPressableProp } from './types'
2 |
3 | export function mergeAnimateProp(
4 | interaction: MotiPressableInteractionState,
5 | prop?: MotiPressableProp,
6 | extra?: MotiPressableProp
7 | ) {
8 | 'worklet'
9 |
10 | let final = {}
11 | for (const animate of [prop, extra]) {
12 | if (animate) {
13 | if (typeof animate === 'function') {
14 | final = Object.assign(final, animate(interaction))
15 | } else {
16 | final = Object.assign(final, animate)
17 | }
18 | }
19 | }
20 |
21 | return final
22 | }
23 |
--------------------------------------------------------------------------------
/packages/moti/src/interactions/pressable/use-moti-pressable-animated-props.ts:
--------------------------------------------------------------------------------
1 | import type { MotiPressableInteractionState } from './types'
2 | import { useAnimatedProps } from 'react-native-reanimated'
3 | import { MotiPressableInteractionIds, useMotiPressableContext } from './context'
4 | import { useFactory } from './use-validate-factory-or-id'
5 |
6 | type Factory = (interaction: MotiPressableInteractionState) => Props
7 |
8 | type Deps = unknown[] | null | undefined
9 |
10 | /**
11 | * Replacement for `useAnimatedProps`, which receives the interaction state as the first argument.
12 | * @param factory function that receives the interaction state and returns the props
13 | */
14 | export function useMotiPressableAnimatedProps(
15 | id: MotiPressableInteractionIds['id'],
16 | factory: Factory,
17 | deps?: Deps
18 | ): Partial
19 | export function useMotiPressableAnimatedProps(
20 | factory: Factory,
21 | deps?: Deps
22 | ): Partial
23 | export function useMotiPressableAnimatedProps(
24 | factoryOrId: Factory | MotiPressableInteractionIds['id'],
25 | maybeFactoryOrDeps?: Factory | Deps,
26 | maybeDeps?: Deps
27 | ) {
28 | const context = useMotiPressableContext()
29 |
30 | const { factory, id, deps } = useFactory>(
31 | 'useMotiPressableAnimatedProps',
32 | factoryOrId,
33 | maybeFactoryOrDeps,
34 | maybeDeps
35 | )
36 |
37 | return useAnimatedProps(() => {
38 | return context ? factory(context.containers[id].value) : {}
39 | }, deps)
40 | }
41 |
--------------------------------------------------------------------------------
/packages/moti/src/interactions/pressable/use-moti-pressable-interpolate.ts:
--------------------------------------------------------------------------------
1 | import type { MotiPressableInteractionState } from './types'
2 | import { SharedValue, useDerivedValue } from 'react-native-reanimated'
3 | import { MotiPressableInteractionIds, useMotiPressableContext } from './context'
4 | import { useFactory } from './use-validate-factory-or-id'
5 |
6 | type Factory = (interaction: MotiPressableInteractionState) => Props
7 | type Deps = unknown[] | null | undefined
8 |
9 | /**
10 | * `useInterpolateMotiPressable` lets you access the pressable state, and create a reanimated derived value from it.
11 | *
12 | * You probably won't need this hook often. `useMotiPressable`, `useMotiPressables`, and `useMotiPressableAnimatedProps` should cover most use-cases
13 | *
14 | * Example:
15 | * ```tsx
16 | * import { useSharedValue } from 'react-native-reanimated'
17 | *
18 | * const mySharedValue = useSharedValue(0)
19 | * useInterpolateMotiPressable(({ pressed }) => {
20 | * 'worklet'
21 | *
22 | * mySharedValue.value = pressed ? 1 : 0
23 | * })
24 | * ```
25 | *
26 | * If you're passing a unique `id` prop to your pressable, you can also isolate this hook to that pressable.
27 | *
28 | * Say the parent pressable has `id="list"`, and you want to isolate this hook to the `list` pressable:
29 | *
30 | * ```tsx
31 | *
34 | * ```
35 | *
36 | * Then, in the `Item` component:
37 | *
38 | * ```tsx
39 | * const mySharedValue = useSharedValue(0)
40 | * useInterpolateMotiPressable("list", ({ pressed }) => {
41 | * 'worklet'
42 | *
43 | * mySharedValue.value = pressed ? 1 : 0
44 | * })
45 | * ```
46 | *
47 | * It returns an `Animated.DerivedValue`. You can also type it with a generic:
48 | *
49 | * ```tsx
50 | * const swipePosition = useSharedValue(0)
51 | * const state = useInterpolateMotiPressable<{ done: boolean }>("list", ({ pressed }) => {
52 | * 'worklet'
53 | *
54 | * return {
55 | * done: swipePosition.value > 50 && !pressed,
56 | * }
57 | * })
58 | * ```
59 | *
60 | * Just like any derived value, you can read the value it returns with `.value`:
61 | *
62 | * ```tsx
63 | * const state = useInterpolateMotiPressable<{ done: boolean }>("list", ({ pressed }) => {
64 | * 'worklet'
65 | *
66 | * return {
67 | * done: swipePosition.value > 50 && !pressed,
68 | * }
69 | * })
70 | *
71 | * // then in some worklet
72 | * const done = state.value.done
73 | */
74 | export function useInterpolateMotiPressable(
75 | id: MotiPressableInteractionIds['id'],
76 | factory: Factory,
77 | deps?: Deps
78 | ): Readonly>
79 | export function useInterpolateMotiPressable(
80 | factory: Factory,
81 | deps?: Deps
82 | ): Readonly>
83 | export function useInterpolateMotiPressable(
84 | factoryOrId: Factory | MotiPressableInteractionIds['id'],
85 | maybeFactoryOrDeps?: Factory | Deps,
86 | maybeDeps?: Deps
87 | ): Readonly> {
88 | const context = useMotiPressableContext()
89 |
90 | const { factory, id, deps } = useFactory>(
91 | 'useMotiPressableAnimatedProps',
92 | factoryOrId,
93 | maybeFactoryOrDeps,
94 | maybeDeps
95 | )
96 |
97 | return useDerivedValue(() => {
98 | return context && factory(context.containers[id].value)
99 | }, [...(deps || []), context.containers[id]])
100 | }
101 |
--------------------------------------------------------------------------------
/packages/moti/src/interactions/pressable/use-moti-pressable-transition.ts:
--------------------------------------------------------------------------------
1 | import type { MotiPressableInteractionState } from './types'
2 | import { SharedValue, useDerivedValue } from 'react-native-reanimated'
3 | import type Animated from 'react-native-reanimated'
4 | import { MotiPressableInteractionIds, useMotiPressableContext } from './context'
5 | import { useFactory } from './use-validate-factory-or-id'
6 | import type { MotiTransition } from '../../core'
7 |
8 | type Factory = (interaction: MotiPressableInteractionState) => Props
9 | type Deps = unknown[] | null | undefined
10 | /**
11 | * `useMotiPressableTransition` lets you access the pressable state, and create a custom moti transition from it.
12 | *
13 | * You probably won't need this hook. A normal `transition` prop on components should suffice. However, if you want to transition differently based on whether you are or aren't interacting with something (such as for a tooltip), then this hook comes in handy.
14 | *
15 | * Please refer to the Moti `transition` options to see what this hook should return.
16 | *
17 | * Example:
18 | * ```tsx
19 | * const transition = useMotiPressableTransition(({ pressed, hovered }) => {
20 | * 'worklet'
21 | *
22 | * if (pressed) {
23 | * return {
24 | * type: 'timing'
25 | * }
26 | * }
27 | *
28 | * return {
29 | * type: 'spring',
30 | * delay: 50
31 | * }
32 | * })
33 | *
34 | * return
35 | * ```
36 | *
37 | * If you're passing a unique `id` prop to your pressable, you can also isolate this hook to that pressable.
38 | *
39 | * Say the parent pressable has `id="list"`, and you want to isolate this hook to the `list` pressable:
40 | *
41 | * ```tsx
42 | *
45 | * ```
46 | *
47 | * Then, in the `Item` component:
48 | *
49 | * ```tsx
50 | * const transition = useMotiPressableTransition("list", ({ pressed }) => {
51 | * 'worklet'
52 | *
53 | * if (pressed) {
54 | * return {
55 | * type: 'timing'
56 | * }
57 | * }
58 | *
59 | * return {
60 | * type: 'spring',
61 | * delay: 50
62 | * }
63 | * })
64 | *
65 | * return
66 | * ```
67 | */
68 | export function useMotiPressableTransition(
69 | id: MotiPressableInteractionIds['id'],
70 | factory: Factory,
71 | deps?: Deps
72 | ): Readonly>
73 | export function useMotiPressableTransition(
74 | factory: Factory,
75 | deps?: Deps
76 | ): Readonly>
77 | export function useMotiPressableTransition(
78 | factoryOrId: Factory | MotiPressableInteractionIds['id'],
79 | maybeFactoryOrDeps?: Factory | Deps,
80 | maybeDeps?: Deps
81 | ): Readonly> {
82 | const context = useMotiPressableContext()
83 |
84 | const { factory, id, deps = [] } = useFactory>(
85 | 'useMotiPressableAnimatedProps',
86 | factoryOrId,
87 | maybeFactoryOrDeps,
88 | maybeDeps
89 | )
90 |
91 | return useDerivedValue(() => {
92 | return context && factory(context.containers[id].value)
93 | }, [context.containers[id], ...(deps || [])])
94 | }
95 |
--------------------------------------------------------------------------------
/packages/moti/src/interactions/pressable/use-pressable.ts:
--------------------------------------------------------------------------------
1 | import { MotiPressableInteractionIds, useMotiPressableContext } from './context'
2 | import type { MotiPressableInteractionProp } from './types'
3 | import { useDerivedValue } from 'react-native-reanimated'
4 | import type { MotiProps } from '../../core'
5 | import { useMemo } from 'react'
6 | import { useFactory } from './use-validate-factory-or-id'
7 |
8 | type Id = MotiPressableInteractionIds['id']
9 | type Deps = unknown[] | null | undefined
10 | /**
11 | * `useMotiPressable` lets you access the interaction state of a parent `MotiPressable` component.
12 | *
13 | * (If you need to access the interaction state of multiple `MotiPressable` parents, use `useMotiPressables` instead.)
14 | *
15 | * ```tsx
16 | *
17 | *
18 | *
19 | * ```
20 | *
21 | * Then, in the `Item` component:
22 | *
23 | * ```tsx
24 | * const state = useMotiPressable(({ pressed }) => {
25 | * 'worklet'
26 | *
27 | * return {
28 | * opactiy: pressed ? 0.5 : 1,
29 | * }
30 | * })
31 | *
32 | * return
33 | * ```
34 | *
35 | * You can also access a pressable via unique ID:
36 | *
37 | * ```tsx
38 | *
39 | *
40 | *
41 | * ```
42 | *
43 | * Then, in the `Item` component, add `list` as the first argument of `useMotiPressable`:
44 | *
45 | * ```tsx
46 | * const state = useMotiPressable('list', ({ pressed }) => {
47 | * 'worklet'
48 | *
49 | * return {
50 | * opactiy: pressed ? 0.5 : 1,
51 | * }
52 | * })
53 | *
54 | * return
55 | * ```
56 | *
57 | * Similar to `useMemo`, you can also pass in a dependency array as the last argument:
58 | *
59 | * ```tsx
60 | * const state = useMotiPressable('list', ({ pressed, hovered }) => {
61 | * 'worklet'
62 | *
63 | * return {
64 | * opactiy: pressed && !loading ? 0.5 : 1,
65 | * }
66 | * }, [loading])
67 | */
68 | function useMotiPressable(
69 | /**
70 | * Function that receives the interaction state from the closest parent container and returns a style object.
71 | * @worklet
72 | */
73 | factory: MotiPressableInteractionProp,
74 | maybeDeps?: Deps
75 | ): MotiProps['state']
76 | function useMotiPressable(
77 | /**
78 | * Optional: the unique `id` prop of the parent `MotiPressable` component. Useful if you want to access a unique component's interaction state without.
79 | */
80 | id: Id,
81 | /**
82 | * Function that receives the interaction state from the parent whose `id` prop matches the first argument of `useMotiPressable`. Returns a style object.
83 | * @worklet
84 | */
85 | factory: MotiPressableInteractionProp,
86 | maybeDeps?: Deps
87 | ): MotiProps['state']
88 | function useMotiPressable(
89 | factoryOrId: MotiPressableInteractionProp | Id,
90 | maybeFactoryOrDeps?: MotiPressableInteractionProp | Deps,
91 | maybeDeps?: Deps
92 | ): MotiProps['state'] {
93 | const context = useMotiPressableContext()
94 |
95 | const { factory, id, deps } = useFactory(
96 | 'useMotiPressable',
97 | factoryOrId,
98 | maybeFactoryOrDeps,
99 | maybeDeps
100 | )
101 |
102 | const __state = useDerivedValue(() => {
103 | const interaction = context.containers[id]
104 |
105 | return interaction && factory(interaction.value)
106 | }, [context, id, context.containers[id], ...(deps || [])])
107 |
108 | return useMemo(
109 | () => ({
110 | __state,
111 | }),
112 | [__state]
113 | )
114 | }
115 |
116 | export { useMotiPressable }
117 |
--------------------------------------------------------------------------------
/packages/moti/src/interactions/pressable/use-pressables.ts:
--------------------------------------------------------------------------------
1 | import { MotiPressableContext, useMotiPressableContext } from './context'
2 | import type { MotiPressableInteractionProp } from './types'
3 | import { useDerivedValue } from 'react-native-reanimated'
4 | import { useMemo } from 'react'
5 | import { Platform } from 'react-native'
6 |
7 | type Factory = (
8 | containers: MotiPressableContext['containers']
9 | ) => ReturnType
10 |
11 | /**
12 | * `useMotiPressables` lets you access the interaction state of *all* parent `MotiPressable` components.
13 | *
14 | * This offers more complex use-cases over `useMotiPressable`, which only lets you access the interaction state of a single parent `MotiPressable` at a time.
15 | *
16 | * Say you have a parent pressable, with a list of items:
17 | *
18 | * ```tsx
19 | *
20 | * {items.map(({ id }) => (
21 | *
22 | *
23 | *
24 | * )}
25 | *
26 | * ```
27 | *
28 | * Then, in your component, you can access each unique `MotiPressable`'s interaction state:
29 | *
30 | * ```tsx
31 | * const Item = ({ id }) => {
32 | * const state = useMotiPressables((containers) => {
33 | * 'worklet'
34 | *
35 | * const list = containers.list.value
36 | * const item = containers.['item-' + id].value
37 | *
38 | * // when hovering a list,
39 | * // fade out all items except the one actually hovered
40 | *
41 | * let opacity = 1
42 | * if (item.hovered || item.pressed) {
43 | * opacity = 1
44 | * } else if (list.hovered) {
45 | * opacity = 0.7
46 | * }
47 | *
48 | * return {
49 | * opacity,
50 | * }
51 | * })
52 | *
53 | * return
54 | * }
55 | * ```
56 | *
57 | * Example shown [here](https://twitter.com/FernandoTheRojo/status/1430717474778066944)
58 | *
59 | *
60 | */
61 | export function useMotiPressables(
62 | /**
63 | * Function that receives the interaction state from all parent containers and returns a style object/
64 | * @worklet
65 | */
66 | factory: Factory,
67 | deps: readonly any[] = []
68 | ) {
69 | const context = useMotiPressableContext()
70 |
71 | if (!deps) {
72 | console.warn(
73 | '[moti/interactions] useMotiPressables is missing a dependency array as the second argument. https://moti.fyi/interactions/use-pressables. You can use this hook to your ESLint plugin for hooks using the additionalHooks field: https://www.npmjs.com/package/eslint-plugin-react-hooks'
74 | )
75 | }
76 |
77 | const __state = useDerivedValue(() => {
78 | const animatedResult = factory(context.containers)
79 |
80 | return animatedResult
81 | // eslint-disable-next-line react-hooks/exhaustive-deps
82 | }, [context.containers, Platform.select({ web: factory }), ...deps])
83 |
84 | const state = useMemo(() => ({ __state }), [__state])
85 |
86 | return state
87 | }
88 |
--------------------------------------------------------------------------------
/packages/moti/src/interactions/pressable/use-validate-factory-or-id.ts:
--------------------------------------------------------------------------------
1 | import {
2 | INTERACTION_CONTAINER_ID,
3 | MotiPressableInteractionIds,
4 | useMotiPressableContext,
5 | } from './context'
6 |
7 | type Id = MotiPressableInteractionIds['id']
8 |
9 | type Deps = unknown[] | null | undefined
10 | type Returns = {
11 | id: Id
12 | factory: Factory
13 | deps?: Deps
14 | }
15 |
16 | type HookName =
17 | | 'useMotiPressableAnimatedProps'
18 | | 'useMotiPressable'
19 | | 'useMotiPressableTransition'
20 |
21 | export function useFactory any>(
22 | hookName: HookName,
23 | factoryOrId: Factory | MotiPressableInteractionIds['id'],
24 | maybeFactoryOrDeps?: Factory | Deps,
25 | maybeDeps?: Deps
26 | ): Returns {
27 | const context = useMotiPressableContext()
28 | const missingIdError = `
29 |
30 | If you're using a container ID, it should look like this:
31 | ${hookName}("${factoryOrId}", ({ pressed, hovered }) => {
32 | 'worklet'
33 |
34 | return {
35 | opacity: pressed ? 1 : 0
36 | }
37 | })
38 |
39 | Otherwise, you could ignore the id and style relative to the closest parent pressable.
40 |
41 | ${hookName}(({ pressed, hovered }) => {
42 | 'worklet'
43 |
44 | return {
45 | opacity: pressed ? 1 : 0
46 | }
47 | })
48 | `
49 |
50 | let factory: Factory
51 | let id: Id = INTERACTION_CONTAINER_ID
52 | let deps: Deps
53 |
54 | if (typeof factoryOrId === 'function') {
55 | factory = factoryOrId
56 | deps = maybeFactoryOrDeps as Deps
57 | } else if (typeof maybeFactoryOrDeps === 'function') {
58 | id = factoryOrId
59 | factory = maybeFactoryOrDeps
60 | deps = maybeDeps
61 | } else {
62 | throw new Error(
63 | `[${hookName}] Invalid arguments. If the first argument is a unique ID string, the second must be a worklet function. Alternatively, the first argument can be a function, without an ID argument. However, received ${factoryOrId} as first argument, and ${maybeFactoryOrDeps} as the second one.` +
64 | missingIdError
65 | )
66 | }
67 |
68 | if (!context) {
69 | console.error(
70 | `[${hookName}] Missing context. Are you sure this component is the child of a component?`
71 | )
72 | } else if (!context.containers[id]) {
73 | // this error will only happen if you set a unique ID
74 | // why? because if there is indeed a context, and there's no unique ID
75 | // ...then we fall back to the default INTERACTION_CONTAINER_ID, which exists if (context) {}
76 | let error = `[${hookName}] Received id "${id}", but there isn't a component wrapping it. This will result in nothing happening.`
77 |
78 | const containerKeys = Object.keys(context.containers)
79 | if (containerKeys.length) {
80 | if (
81 | containerKeys.length === 1 &&
82 | containerKeys[0] === INTERACTION_CONTAINER_ID
83 | ) {
84 | error += ` There is a component as the parent of this hook, but it doesn't have id="${id}" set as a prop. You should either add that prop to the parent , or remove the first argument of ${hookName}().`
85 | } else {
86 | const possibleIds = containerKeys.filter(
87 | (key) => key !== INTERACTION_CONTAINER_ID
88 | )
89 | if (possibleIds.length) {
90 | error += ` Did you mean to use one of: ${possibleIds.join(', ')}`
91 | }
92 | }
93 | }
94 |
95 | error = error + missingIdError
96 | console.error(error)
97 | }
98 |
99 | return {
100 | factory,
101 | id,
102 | deps,
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/packages/moti/src/skeleton/expo.tsx:
--------------------------------------------------------------------------------
1 | import { LinearGradient } from 'expo-linear-gradient'
2 |
3 | import SkeletonNative from './skeleton-new'
4 | import { MotiSkeletonProps } from './types'
5 |
6 | export default function SkeletonExpo(
7 | props: Omit
8 | ) {
9 | return
10 | }
11 |
12 | SkeletonExpo.Group = SkeletonNative.Group
13 |
--------------------------------------------------------------------------------
/packages/moti/src/skeleton/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Skeleton } from './expo'
2 |
--------------------------------------------------------------------------------
/packages/moti/src/skeleton/native.tsx:
--------------------------------------------------------------------------------
1 | import LinearGradient from 'react-native-linear-gradient'
2 |
3 | import SkeletonNative from './skeleton-new'
4 | import { MotiSkeletonProps } from './types'
5 |
6 | export function Skeleton(props: Omit) {
7 | return
8 | }
9 |
10 | Skeleton.Group = SkeletonNative.Group
11 |
--------------------------------------------------------------------------------
/packages/moti/src/skeleton/shared.ts:
--------------------------------------------------------------------------------
1 | export const DEFAULT_SKELETON_SIZE = 32
2 |
3 | export const baseColors = {
4 | dark: { primary: 'rgb(17, 17, 17)', secondary: 'rgb(51, 51, 51)' },
5 | light: {
6 | primary: 'rgb(250, 250, 250)',
7 | secondary: 'rgb(205, 205, 205)',
8 | },
9 | } as const
10 |
11 | const makeColors = (mode: keyof typeof baseColors) => [
12 | baseColors[mode].primary,
13 | baseColors[mode].secondary,
14 | baseColors[mode].secondary,
15 | baseColors[mode].primary,
16 | baseColors[mode].secondary,
17 | baseColors[mode].primary,
18 | ]
19 |
20 | export let defaultDarkColors = makeColors('dark')
21 |
22 | export let defaultLightColors = makeColors('light')
23 |
24 | for (let i = 0; i++; i < 3) {
25 | defaultDarkColors = [...defaultDarkColors, ...defaultDarkColors]
26 | defaultLightColors = [...defaultLightColors, ...defaultLightColors]
27 | }
28 |
--------------------------------------------------------------------------------
/packages/moti/src/skeleton/types.ts:
--------------------------------------------------------------------------------
1 | import { DimensionValue } from 'react-native'
2 | import { MotiTransitionProp } from '../core'
3 | import { baseColors } from './shared'
4 |
5 | type Size = number | DimensionValue
6 |
7 | export type MotiSkeletonProps = {
8 | /**
9 | * Optional height of the container of the skeleton. If set, it will give a fixed height to the container.
10 | *
11 | * If not set, the container will stretch to the children.
12 | */
13 | boxHeight?: Size
14 | /**
15 | * Optional height of the skeleton. Defaults to a `minHeight` of `32`
16 | */
17 | height?: Size
18 | children?: React.ReactElement | null
19 | /**
20 | * `boolean` specifying whether the skeleton should be visible. By default, it shows if there are no children. This way, you can conditionally display children, and automatically hide the skeleton when they exist.
21 | *
22 | * ```tsx
23 | * // skeleton will hide when data exists
24 | *
25 | * {data ? : null}
26 | *
27 | * ```
28 | *
29 | * // skeleton will always show
30 | *
31 | * {data ? : null}
32 | *
33 | *
34 | * // skeleton will always hide
35 | *
36 | * {data ? : null}
37 | *
38 | *
39 | * If you have multiple skeletons, you can use the ` as a parent rather than use this prop directly.
40 | */
41 | show?: boolean
42 | /**
43 | * Width of the skeleton. Defaults to `32` as the `minWidth`. Sets the container's `minWidth` to this value if defined, falling back to 32.
44 | */
45 | width?: Size
46 | /**
47 | * Border radius. Can be `square`, `round`, or a number. `round` makes it a circle. Defaults to `8`.
48 | */
49 | radius?: number | 'square' | 'round'
50 | /**
51 | * Background of the box that contains the skeleton. Should match the main `colors` prop color.
52 | *
53 | * Default: `'rgb(51, 51, 51, 50)'`
54 | */
55 | backgroundColor?: string
56 | /**
57 | * Gradient colors. Defaults to grayish black.
58 | */
59 | colors?: string[]
60 | /**
61 | * Default: `6`. Similar to `600%` for CSS `background-size`. Determines how much the gradient stretches.
62 | */
63 | backgroundSize?: number
64 | /**
65 | * `light` or `dark`. Default: `dark`.
66 | */
67 | colorMode?: keyof typeof baseColors
68 | disableExitAnimation?: boolean
69 | transition?: MotiTransitionProp
70 | Gradient: React.ComponentType<{
71 | colors: Array
72 | start?: { x: number; y: number }
73 | end?: { x: number; y: number }
74 | style?: any
75 | }>
76 | }
77 |
--------------------------------------------------------------------------------
/packages/moti/src/svg/index.ts:
--------------------------------------------------------------------------------
1 | export * from './motify-svg'
2 |
--------------------------------------------------------------------------------
/packages/moti/src/svg/motify-svg.tsx:
--------------------------------------------------------------------------------
1 | import React, { forwardRef } from 'react'
2 | import Animated from 'react-native-reanimated'
3 |
4 | import { ExcludeFunctionKeys, MotiProps } from '../core/types'
5 | import { useMotify } from '../core/use-motify'
6 |
7 | type AdditionalProps = {
8 | children?: React.ReactNode
9 | /**
10 | * Animated props are not allowed with a Moti SVG component, since they will be overridden.
11 | *
12 | * Please use the `animate` prop instead. You can pass a derived value if needed:
13 | *
14 | * ```tsx
15 | * const MotiRect = motifySvg(Rect)()
16 | *
17 | * export const Example = () => {
18 | * const animate = useDerivedValue(() => {
19 | * return {
20 | * width: 100,
21 | * height: 100,
22 | * }
23 | * })
24 | * return
25 | * }
26 | * ```
27 | */
28 | animatedProps?: never
29 | }
30 |
31 | export function motifySvg<
32 | C extends React.ComponentClass,
33 | Props = React.ComponentPropsWithoutRef,
34 | Animate = ExcludeFunctionKeys>
35 | >(ComponentWithoutAnimation: C) {
36 | const withAnimations = () => {
37 | const AnimatedComponent = Animated.createAnimatedComponent(
38 | ComponentWithoutAnimation
39 | )
40 | const Motified = forwardRef<
41 | React.RefAttributes>,
42 | Props & MotiProps & AdditionalProps
43 | >(function Moti(props, ref) {
44 | const animated = useMotify(props)
45 |
46 | if (props.animatedProps) {
47 | console.warn(
48 | `Moti: You passed animatedProps to a Moti SVG component. This will do nothing. You should use the animate prop directly. This will have no effect.`
49 | )
50 | }
51 |
52 | return (
53 |
59 | )
60 | })
61 |
62 | Motified.displayName = `MotiSvg.${
63 | ComponentWithoutAnimation.displayName ||
64 | ComponentWithoutAnimation.name ||
65 | 'NoName'
66 | }`
67 |
68 | return Motified
69 | }
70 |
71 | return withAnimations
72 | }
73 |
--------------------------------------------------------------------------------
/packages/moti/svg/index.d.ts:
--------------------------------------------------------------------------------
1 | export * from '../build/svg'
2 |
--------------------------------------------------------------------------------
/packages/moti/svg/index.js:
--------------------------------------------------------------------------------
1 | export * from '../build/svg'
2 |
--------------------------------------------------------------------------------
/packages/moti/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "expo-module-scripts/tsconfig.base",
3 | "compilerOptions": {
4 | "jsx": "react-jsx",
5 | "outDir": "./build",
6 | "types": []
7 | },
8 | "include": ["./src", "./src/interactions", "./src/author"],
9 | "exclude": ["**/__mocks__/*", "**/__tests__/*", "**/*/node_modules/*"]
10 | }
11 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "paths": {
5 | "@motify/*": ["./packages/*/src"],
6 | "moti": ["./packages/moti/src"]
7 | },
8 | "composite": true,
9 | "allowUnreachableCode": false,
10 | "allowUnusedLabels": false,
11 | "esModuleInterop": true,
12 | "forceConsistentCasingInFileNames": true,
13 | "jsx": "react-jsx",
14 | "lib": ["esnext", "dom"],
15 | "module": "esnext",
16 | "moduleResolution": "node",
17 | "noFallthroughCasesInSwitch": true,
18 | "noImplicitReturns": true,
19 | "noImplicitUseStrict": false,
20 | "noStrictGenericChecks": false,
21 | "noUnusedLocals": true,
22 | "noUnusedParameters": true,
23 | "resolveJsonModule": true,
24 | "skipLibCheck": true,
25 | "strict": true,
26 | "target": "esnext",
27 | "noImplicitAny": false
28 | },
29 | "exclude": ["./examples"]
30 | }
31 |
--------------------------------------------------------------------------------