Skip to content

Commit

Permalink
feat: move skeleton into moti import
Browse files Browse the repository at this point in the history
  • Loading branch information
nandorojo committed Dec 15, 2021
1 parent be6618e commit dab7a4f
Show file tree
Hide file tree
Showing 11 changed files with 361 additions and 19 deletions.
14 changes: 8 additions & 6 deletions docs/docs/skeleton.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Moti's skeleton component is great for showing animated loading states. It works

```tsx
import React from 'react'
import { Skeleton } from '@motify/skeleton'
import { Skeleton } from 'moti/skeleton'

const Loader = ({ children }) => <Skeleton>{children}</Skeleton>

Expand All @@ -21,11 +21,13 @@ export default Loader

## Install

The skeleton ships as its own package, since it has dependencies outside of `moti`:
Prior to version `0.17.0`, the skeleton was installed as its own package. This is no longer the case.

```bash npm2yarn
npm install @motify/skeleton
```
If you have `@motify/skeleton` in your `package.json`, be sure to delete it and upgrade `moti`.

[See the PR](https://github.com/nandorojo/moti/pull/136).

### Peer dependency

You'll also want to install `expo-linear-gradient`.

Expand Down Expand Up @@ -158,7 +160,7 @@ Here's the code from the video above:
import React, { useReducer } from 'react'
import { StyleSheet, Pressable } from 'react-native'
import { MotiView } from 'moti'
import { Skeleton } from '@motify/skeleton'
import { Skeleton } from 'moti/skeleton'

const Spacer = ({ height = 16 }) => <MotiView style={{ height }} />

Expand Down
2 changes: 2 additions & 0 deletions packages/interactions/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@ console.warn(`@motify/interactions is deprecated. It has moved to 'moti/interact
yarn remove @motify/interactions
yarn add moti
Finally, find and replace all your imports.
For more info: https://github.com/nandorojo/moti/pull/136
`)
9 changes: 5 additions & 4 deletions packages/moti/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
"src",
"build",
"!**/__tests__",
"interactions"
"interactions",
"skeleton"
],
"sideEffects": false,
"publishConfig": {
Expand All @@ -46,11 +47,11 @@
"@motify/components": "^0.16.2-alpha.6+a87dd85",
"@motify/core": "^0.16.2-alpha.6+a87dd85"
},
"peerDependencies": {
"react-native-reanimated": "*"
},
"peerDependencies": {},
"devDependencies": {
"expo-linear-gradient": "^10.0.3",
"expo-module-scripts": "^2.0.0",
"react-native-reanimated": "*",
"typescript": "^4.0.3"
},
"react-native-builder-bob": {
Expand Down
1 change: 1 addition & 0 deletions packages/moti/skeleton/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from '../build/skeleton'
2 changes: 2 additions & 0 deletions packages/moti/skeleton/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions packages/moti/skeleton/index.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/moti/src/skeleton/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as Skeleton } from './skeleton'
265 changes: 265 additions & 0 deletions packages/moti/src/skeleton/skeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
import React, { useState } from 'react'
import { View as MotiView } from '@motify/components'
import { View, StyleSheet } from 'react-native'

import { LinearGradient } from 'expo-linear-gradient'
import { AnimatePresence, MotiTransitionProp } from '@motify/core'

type Props = {
/**
* Optional height of the container of the skeleton. If set, it will give a fixed height to the container.
*
* If not set, the container will stretch to the children.
*/
boxHeight?: number | string
/**
* Optional height of the skeleton. Defauls to a `minHeight` of `32`
*/
height?: number | string
children?: React.ReactChild
/**
* `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.
*
* ```tsx
* // skeleton will hide when data exists
* <Skeleton>
* {data ? <Data /> : null}
* </Skeleton>
* ```
*
* // skeleton will always show
* <Skeleton show>
* {data ? <Data /> : null}
* </Skeleton>
*
* // skeleton will always hide
* <Skeleton show={false}>
* {data ? <Data /> : null}
* </Skeleton>
*/
show?: boolean
/**
* Width of the skeleton. Defaults to `32` as the `minWidth`. Sets the container's `minWidth` to this value if defined, falling back to 32.
*/
width?: string | number
/**
* Border radius. Can be `square`, `round`, or a number. `round` makes it a circle. Defaults to `8`.
*/
radius?: number | 'square' | 'round'
/**
* Background of the box that contains the skeleton. Should match the main `colors` prop color.
*
* Default: `'rgb(51, 51, 51, 50)'`
*/
backgroundColor?: string
/**
* Gradient colors. Defaults to grayish black.
*/
colors?: string[]
/**
* Default: `6`. Similar to `600%` for CSS `background-size`. Determines how much the gradient stretches.
*/
backgroundSize?: number
/**
* `light` or `dark`. Default: `dark`.
*/
colorMode?: keyof typeof baseColors
disableExitAnimation?: boolean
transition?: MotiTransitionProp
}

const DEFAULT_SIZE = 32

const baseColors = {
dark: { primary: 'rgb(17, 17, 17)', secondary: 'rgb(51, 51, 51)' },
light: {
primary: 'rgb(250, 250, 250)',
secondary: 'rgb(205, 205, 205)',
},
} as const

const makeColors = (mode: keyof typeof baseColors) => [
baseColors[mode].primary,
baseColors[mode].secondary,
baseColors[mode].secondary,
baseColors[mode].primary,
baseColors[mode].secondary,
baseColors[mode].primary,
]

let defaultDarkColors = makeColors('dark')

let defaultLightColors = makeColors('light')

for (let i = 0; i++; i < 3) {
defaultDarkColors = [...defaultDarkColors, ...defaultDarkColors]
defaultLightColors = [...defaultLightColors, ...defaultLightColors]
}

export default function Skeleton(props: Props) {
const {
radius = 8,
children,
show = !children,
width,
height = children ? undefined : DEFAULT_SIZE,
boxHeight,
colorMode = 'dark',
colors = colorMode === 'dark' ? defaultDarkColors : defaultLightColors,
backgroundColor = colors[0] ??
colors[1] ??
baseColors[colorMode]?.secondary,
backgroundSize = 6,
disableExitAnimation,
transition,
} = props

const [measuredWidth, setMeasuredWidth] = useState(0)

const getBorderRadius = () => {
if (radius === 'square') {
return 0
}
if (radius === 'round') {
return 99999
}
return radius
}

const borderRadius = getBorderRadius()

const getOuterHeight = () => {
if (boxHeight != null) return boxHeight
if (show && !children) {
return height
}
return undefined
}

const outerHeight = getOuterHeight()

return (
<View
style={{
height: outerHeight,
minHeight: height,
minWidth: width ?? (children ? undefined : DEFAULT_SIZE),
}}
>
{children}
<AnimatePresence>
{show && (
<MotiView
style={{
position: 'absolute',
top: 0,
left: 0,
borderRadius,
width: width ?? (children ? '100%' : DEFAULT_SIZE),
height: height ?? '100%',
overflow: 'hidden',
}}
animate={{
backgroundColor,
opacity: 1,
}}
transition={{
type: 'timing',
}}
exit={
!disableExitAnimation && {
opacity: 0,
}
}
onLayout={({ nativeEvent }) => {
if (measuredWidth === nativeEvent.layout.width) return

setMeasuredWidth(nativeEvent.layout.width)
}}
pointerEvents="none"
>
<AnimatedGradient
// force a key change to make the loop animation re-mount
key={`${JSON.stringify(colors)}-${measuredWidth}-${JSON.stringify(
transition || null
)}`}
colors={colors}
backgroundSize={backgroundSize}
measuredWidth={measuredWidth}
transition={transition}
/>
</MotiView>
)}
</AnimatePresence>
</View>
)
}

const AnimatedGradient = React.memo(
function AnimatedGradient({
measuredWidth,
colors,
backgroundSize,
transition = {},
}: {
measuredWidth: number
colors: string[]
backgroundSize: number,
transition?: MotiTransitionProp
}) {

return (
<MotiView
style={StyleSheet.absoluteFillObject}
from={{ opacity: 0 }}
transition={{
type: 'timing',
duration: 200,
}}
animate={
measuredWidth
? {
opacity: 1,
}
: undefined
}
>
<MotiView
style={[
StyleSheet.absoluteFillObject,
{
width: measuredWidth * backgroundSize,
},
]}
from={{
translateX: 0,
}}
animate={
measuredWidth
? {
translateX: -measuredWidth * (backgroundSize - 1),
}
: undefined
}
transition={{
loop: true,
delay: 200,
type: 'timing',
duration: 3000,
...(transition as any),
}}
>
<LinearGradient
colors={colors}
start={[0.1, 1]}
end={[1, 1]}
style={StyleSheet.absoluteFillObject}
/>
</MotiView>
</MotiView>
)
},
function propsAreEqual(prev, next) {
return JSON.stringify(prev) === JSON.stringify(next)
}
)
6 changes: 0 additions & 6 deletions packages/skeleton/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,6 @@

First, install and setup `moti`.

Next, install `@motify/skeleton`:

```sh
yarn add @motify/skeleton
```

Finally, install peer dependencies:

```
Expand Down
10 changes: 10 additions & 0 deletions packages/skeleton/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,11 @@
export { default as Skeleton } from './skeleton'

console.warn(`@motify/skeleton is deprecated. It has moved to 'moti/skeleton'. Please uninstall @motify/skeleton, upgrade "moti", and update your imports.
yarn remove @motify/skeleton
yarn add moti
Finally, find and replace all your imports.
For more info: https://github.com/nandorojo/moti/pull/136
`)
Loading

0 comments on commit dab7a4f

Please sign in to comment.