@butternutbox/pawprint-native 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +9 -9
- package/CHANGELOG.md +12 -0
- package/dist/index.cjs +533 -2305
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -20
- package/dist/index.d.ts +9 -20
- package/dist/index.js +351 -2122
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/atoms/index.ts +0 -1
- package/src/components/molecules/Drawer/Drawer.test.tsx +49 -2
- package/src/components/molecules/Drawer/Drawer.tsx +32 -3
- package/src/components/atoms/ChefDogLoader/ChefDogLoader.stories.tsx +0 -21
- package/src/components/atoms/ChefDogLoader/ChefDogLoader.test.tsx +0 -33
- package/src/components/atoms/ChefDogLoader/ChefDogLoader.tsx +0 -115
- package/src/components/atoms/ChefDogLoader/index.ts +0 -2
package/package.json
CHANGED
|
@@ -6,12 +6,18 @@ import { Drawer } from "./Drawer"
|
|
|
6
6
|
|
|
7
7
|
const BasicDrawer = ({
|
|
8
8
|
open,
|
|
9
|
-
onOpenChange
|
|
9
|
+
onOpenChange,
|
|
10
|
+
onExitComplete
|
|
10
11
|
}: {
|
|
11
12
|
open?: boolean
|
|
12
13
|
onOpenChange?: (v: boolean) => void
|
|
14
|
+
onExitComplete?: () => void
|
|
13
15
|
}) => (
|
|
14
|
-
<Drawer.Root
|
|
16
|
+
<Drawer.Root
|
|
17
|
+
open={open}
|
|
18
|
+
onOpenChange={onOpenChange}
|
|
19
|
+
onExitComplete={onExitComplete}
|
|
20
|
+
>
|
|
15
21
|
<Drawer.Trigger>
|
|
16
22
|
<button>Open</button>
|
|
17
23
|
</Drawer.Trigger>
|
|
@@ -77,6 +83,47 @@ describe("Drawer", () => {
|
|
|
77
83
|
})
|
|
78
84
|
})
|
|
79
85
|
|
|
86
|
+
describe("onExitComplete", () => {
|
|
87
|
+
it("is not called while the drawer has never closed", () => {
|
|
88
|
+
const onExitComplete = vi.fn()
|
|
89
|
+
const { rerender } = renderWithTheme(
|
|
90
|
+
<BasicDrawer open={false} onExitComplete={onExitComplete} />
|
|
91
|
+
)
|
|
92
|
+
rerender(<BasicDrawer open onExitComplete={onExitComplete} />)
|
|
93
|
+
expect(onExitComplete).not.toHaveBeenCalled()
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it("is called once the drawer has closed and its content is removed", () => {
|
|
97
|
+
const onExitComplete = vi.fn(() => {
|
|
98
|
+
expect(screen.queryByText("Drawer title")).not.toBeInTheDocument()
|
|
99
|
+
})
|
|
100
|
+
const { rerender } = renderWithTheme(
|
|
101
|
+
<BasicDrawer open onExitComplete={onExitComplete} />
|
|
102
|
+
)
|
|
103
|
+
rerender(<BasicDrawer open={false} onExitComplete={onExitComplete} />)
|
|
104
|
+
expect(onExitComplete).toHaveBeenCalledTimes(1)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it("is called once per close", () => {
|
|
108
|
+
const onExitComplete = vi.fn()
|
|
109
|
+
const { rerender } = renderWithTheme(
|
|
110
|
+
<BasicDrawer open onExitComplete={onExitComplete} />
|
|
111
|
+
)
|
|
112
|
+
rerender(<BasicDrawer open={false} onExitComplete={onExitComplete} />)
|
|
113
|
+
rerender(<BasicDrawer open onExitComplete={onExitComplete} />)
|
|
114
|
+
rerender(<BasicDrawer open={false} onExitComplete={onExitComplete} />)
|
|
115
|
+
expect(onExitComplete).toHaveBeenCalledTimes(2)
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it("is called when an uncontrolled drawer is closed", () => {
|
|
119
|
+
const onExitComplete = vi.fn()
|
|
120
|
+
renderWithTheme(<BasicDrawer onExitComplete={onExitComplete} />)
|
|
121
|
+
fireEvent.click(screen.getByText("Open"))
|
|
122
|
+
fireEvent.click(screen.getByLabelText("Close"))
|
|
123
|
+
expect(onExitComplete).toHaveBeenCalledTimes(1)
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
|
|
80
127
|
describe("controlled mode", () => {
|
|
81
128
|
it("respects controlled open=true", () => {
|
|
82
129
|
renderWithTheme(<BasicDrawer open />)
|
|
@@ -19,6 +19,14 @@ type DrawerRootOwnProps = {
|
|
|
19
19
|
open?: boolean
|
|
20
20
|
defaultOpen?: boolean
|
|
21
21
|
onOpenChange?: (open: boolean) => void
|
|
22
|
+
/**
|
|
23
|
+
* Called once the drawer has fully closed: the exit animation has finished
|
|
24
|
+
* and the native `Modal` has been removed. Use it to chain work that can't
|
|
25
|
+
* overlap the drawer, such as opening another modal drawer (iOS won't present
|
|
26
|
+
* a second `Modal` while the first is still on screen) or unmounting the
|
|
27
|
+
* drawer's host.
|
|
28
|
+
*/
|
|
29
|
+
onExitComplete?: () => void
|
|
22
30
|
/**
|
|
23
31
|
* Whether the drawer shows a dimming backdrop and presents as a blocking
|
|
24
32
|
* modal (default `true`). Set to `false` for a backdrop-less, pass-through
|
|
@@ -70,6 +78,7 @@ const DrawerRoot = React.forwardRef<View, DrawerProps>(
|
|
|
70
78
|
open: controlledOpen,
|
|
71
79
|
defaultOpen = false,
|
|
72
80
|
onOpenChange,
|
|
81
|
+
onExitComplete,
|
|
73
82
|
backdrop = true,
|
|
74
83
|
children,
|
|
75
84
|
style,
|
|
@@ -102,10 +111,23 @@ const DrawerRoot = React.forwardRef<View, DrawerProps>(
|
|
|
102
111
|
onOpenChange?.(false)
|
|
103
112
|
}, [onOpenChange])
|
|
104
113
|
|
|
105
|
-
const
|
|
114
|
+
const handleExitComplete = useCallback(() => {
|
|
106
115
|
setModalVisible(false)
|
|
107
116
|
}, [])
|
|
108
117
|
|
|
118
|
+
const onExitCompleteRef = useRef(onExitComplete)
|
|
119
|
+
onExitCompleteRef.current = onExitComplete
|
|
120
|
+
const wasModalVisible = useRef(modalVisible)
|
|
121
|
+
|
|
122
|
+
// Fires after the commit that removed the Modal, not from the animation
|
|
123
|
+
// callback, so consumers never act while the Modal is still mounted.
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
if (wasModalVisible.current && !modalVisible) {
|
|
126
|
+
onExitCompleteRef.current?.()
|
|
127
|
+
}
|
|
128
|
+
wasModalVisible.current = modalVisible
|
|
129
|
+
}, [modalVisible])
|
|
130
|
+
|
|
109
131
|
const ctx = useMemo(
|
|
110
132
|
() => ({
|
|
111
133
|
isOpen,
|
|
@@ -113,9 +135,16 @@ const DrawerRoot = React.forwardRef<View, DrawerProps>(
|
|
|
113
135
|
backdrop,
|
|
114
136
|
openDrawer,
|
|
115
137
|
closeDrawer,
|
|
116
|
-
onExitComplete
|
|
138
|
+
onExitComplete: handleExitComplete
|
|
117
139
|
}),
|
|
118
|
-
[
|
|
140
|
+
[
|
|
141
|
+
isOpen,
|
|
142
|
+
modalVisible,
|
|
143
|
+
backdrop,
|
|
144
|
+
openDrawer,
|
|
145
|
+
closeDrawer,
|
|
146
|
+
handleExitComplete
|
|
147
|
+
]
|
|
119
148
|
)
|
|
120
149
|
|
|
121
150
|
return (
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import React from "react"
|
|
2
|
-
import { ChefDogLoader } from "./ChefDogLoader"
|
|
3
|
-
import type { ChefDogLoaderProps } from "./ChefDogLoader"
|
|
4
|
-
|
|
5
|
-
export default {
|
|
6
|
-
title: "Atoms/ChefDogLoader",
|
|
7
|
-
component: ChefDogLoader,
|
|
8
|
-
argTypes: {
|
|
9
|
-
size: {
|
|
10
|
-
control: { type: "number" },
|
|
11
|
-
description: "Width and height in points"
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export const Playground = (args: ChefDogLoaderProps) => (
|
|
17
|
-
<ChefDogLoader {...args} />
|
|
18
|
-
)
|
|
19
|
-
Playground.args = {
|
|
20
|
-
size: 240
|
|
21
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import React from "react"
|
|
2
|
-
import { View } from "react-native"
|
|
3
|
-
import { screen } from "@testing-library/react"
|
|
4
|
-
import { describe, it, expect } from "vitest"
|
|
5
|
-
import { renderWithTheme } from "../../../test-utils"
|
|
6
|
-
import { ChefDogLoader } from "./ChefDogLoader"
|
|
7
|
-
|
|
8
|
-
describe("ChefDogLoader", () => {
|
|
9
|
-
describe("when component is rendering", () => {
|
|
10
|
-
it("renders with progressbar role", () => {
|
|
11
|
-
renderWithTheme(<ChefDogLoader />)
|
|
12
|
-
expect(screen.getByRole("progressbar")).toBeInTheDocument()
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
it("has accessible loading label", () => {
|
|
16
|
-
renderWithTheme(<ChefDogLoader />)
|
|
17
|
-
expect(screen.getByLabelText("Loading")).toBeInTheDocument()
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
it("accepts a custom accessibility label", () => {
|
|
21
|
-
renderWithTheme(<ChefDogLoader accessibilityLabel="Fetching your plan" />)
|
|
22
|
-
expect(screen.getByLabelText("Fetching your plan")).toBeInTheDocument()
|
|
23
|
-
})
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
describe("when using with ref", () => {
|
|
27
|
-
it("forwards ref", () => {
|
|
28
|
-
const ref = React.createRef<View>()
|
|
29
|
-
renderWithTheme(<ChefDogLoader ref={ref} />)
|
|
30
|
-
expect(ref.current).toBeTruthy()
|
|
31
|
-
})
|
|
32
|
-
})
|
|
33
|
-
})
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import React from "react"
|
|
2
|
-
import { StyleSheet, View, ViewProps } from "react-native"
|
|
3
|
-
import Svg, { ClipPath, Defs, G, Polygon } from "react-native-svg"
|
|
4
|
-
import Reanimated, {
|
|
5
|
-
cancelAnimation,
|
|
6
|
-
Easing,
|
|
7
|
-
useAnimatedStyle,
|
|
8
|
-
useReducedMotion,
|
|
9
|
-
useSharedValue,
|
|
10
|
-
withRepeat,
|
|
11
|
-
withTiming
|
|
12
|
-
} from "react-native-reanimated"
|
|
13
|
-
import { LabradorChefsHat } from "@butternutbox/pawprint-illustrations/breeds"
|
|
14
|
-
|
|
15
|
-
type ChefDogLoaderOwnProps = {
|
|
16
|
-
size?: number
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
type ChefDogLoaderProps = ChefDogLoaderOwnProps &
|
|
20
|
-
Omit<ViewProps, keyof ChefDogLoaderOwnProps>
|
|
21
|
-
|
|
22
|
-
const DEFAULT_SIZE = 240
|
|
23
|
-
const TAIL_REGION = "0,0 15.5,0 15.5,22.4 12,26.5 0,26.5"
|
|
24
|
-
const BODY_REGION = "15.5,0 48,0 48,48 0,48 0,26.5 12,26.5 15.5,22.4"
|
|
25
|
-
const TAIL_ORIGIN = { x: 13.4 / 48 - 0.5, y: 24.6 / 48 - 0.5 }
|
|
26
|
-
const TAIL_DOWN = -6
|
|
27
|
-
const TAIL_UP = 14
|
|
28
|
-
const WAG_DURATION = 180
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* The labrador in a chef's hat wagging its tail, for indeterminate loading states.
|
|
32
|
-
*
|
|
33
|
-
* @example
|
|
34
|
-
* ```tsx
|
|
35
|
-
* import { ChefDogLoader } from "@butternutbox/pawprint-native"
|
|
36
|
-
*
|
|
37
|
-
* <ChefDogLoader />
|
|
38
|
-
* <ChefDogLoader size={160} />
|
|
39
|
-
* ```
|
|
40
|
-
*
|
|
41
|
-
* @param size - *(optional)* Width and height in points (default: 240)
|
|
42
|
-
*/
|
|
43
|
-
const ChefDogLoader = React.forwardRef<View, ChefDogLoaderProps>(
|
|
44
|
-
({ size = DEFAULT_SIZE, ...rest }, ref) => {
|
|
45
|
-
const reducedMotion = useReducedMotion()
|
|
46
|
-
const rotation = useSharedValue(TAIL_DOWN)
|
|
47
|
-
const clipId = React.useId().replace(/:/g, "")
|
|
48
|
-
|
|
49
|
-
React.useEffect(() => {
|
|
50
|
-
if (reducedMotion) return
|
|
51
|
-
rotation.value = withRepeat(
|
|
52
|
-
withTiming(TAIL_UP, {
|
|
53
|
-
duration: WAG_DURATION,
|
|
54
|
-
easing: Easing.inOut(Easing.quad)
|
|
55
|
-
}),
|
|
56
|
-
-1,
|
|
57
|
-
true
|
|
58
|
-
)
|
|
59
|
-
return () => cancelAnimation(rotation)
|
|
60
|
-
}, [reducedMotion, rotation])
|
|
61
|
-
|
|
62
|
-
const originX = TAIL_ORIGIN.x * size
|
|
63
|
-
const originY = TAIL_ORIGIN.y * size
|
|
64
|
-
|
|
65
|
-
const tailStyle = useAnimatedStyle(() => ({
|
|
66
|
-
transform: [
|
|
67
|
-
{ translateX: originX },
|
|
68
|
-
{ translateY: originY },
|
|
69
|
-
{ rotate: `${rotation.value}deg` },
|
|
70
|
-
{ translateX: -originX },
|
|
71
|
-
{ translateY: -originY }
|
|
72
|
-
]
|
|
73
|
-
}))
|
|
74
|
-
|
|
75
|
-
return (
|
|
76
|
-
<View
|
|
77
|
-
ref={ref}
|
|
78
|
-
accessibilityRole="progressbar"
|
|
79
|
-
accessibilityLabel="Loading"
|
|
80
|
-
{...rest}
|
|
81
|
-
>
|
|
82
|
-
<Svg width={size} height={size} viewBox="0 0 48 48">
|
|
83
|
-
<Defs>
|
|
84
|
-
<ClipPath id={`${clipId}-body`}>
|
|
85
|
-
<Polygon points={BODY_REGION} />
|
|
86
|
-
</ClipPath>
|
|
87
|
-
</Defs>
|
|
88
|
-
<G clipPath={`url(#${clipId}-body)`}>
|
|
89
|
-
<LabradorChefsHat />
|
|
90
|
-
</G>
|
|
91
|
-
</Svg>
|
|
92
|
-
<Reanimated.View
|
|
93
|
-
pointerEvents="none"
|
|
94
|
-
style={[StyleSheet.absoluteFill, tailStyle]}
|
|
95
|
-
>
|
|
96
|
-
<Svg width={size} height={size} viewBox="0 0 48 48">
|
|
97
|
-
<Defs>
|
|
98
|
-
<ClipPath id={`${clipId}-tail`}>
|
|
99
|
-
<Polygon points={TAIL_REGION} />
|
|
100
|
-
</ClipPath>
|
|
101
|
-
</Defs>
|
|
102
|
-
<G clipPath={`url(#${clipId}-tail)`}>
|
|
103
|
-
<LabradorChefsHat />
|
|
104
|
-
</G>
|
|
105
|
-
</Svg>
|
|
106
|
-
</Reanimated.View>
|
|
107
|
-
</View>
|
|
108
|
-
)
|
|
109
|
-
}
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
ChefDogLoader.displayName = "ChefDogLoader"
|
|
113
|
-
|
|
114
|
-
export { ChefDogLoader }
|
|
115
|
-
export type { ChefDogLoaderProps }
|