@butternutbox/pawprint-native 0.10.10 → 0.11.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 +8 -8
- package/CHANGELOG.md +23 -0
- package/dist/index.cjs +284 -165
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +45 -1
- package/dist/index.d.ts +45 -1
- package/dist/index.js +285 -166
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/__mocks__/react-native-reanimated.tsx +35 -1
- package/src/__mocks__/react-native.tsx +9 -0
- package/src/components/molecules/Checkbox/Checkbox.tsx +102 -105
- package/src/components/molecules/NumberField/NumberField.tsx +5 -1
- package/src/components/molecules/StackedNotifications/StackedNotifications.stories.tsx +220 -0
- package/src/components/molecules/StackedNotifications/StackedNotifications.test.tsx +202 -0
- package/src/components/molecules/StackedNotifications/StackedNotifications.tsx +247 -0
- package/src/components/molecules/StackedNotifications/index.ts +6 -0
- package/src/components/molecules/index.ts +1 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { screen } from "@testing-library/react"
|
|
3
|
+
import userEvent from "@testing-library/user-event"
|
|
4
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
|
|
5
|
+
import { renderWithTheme } from "../../../test-utils"
|
|
6
|
+
import { StackedNotifications } from "./StackedNotifications"
|
|
7
|
+
import type { StackedNotificationItem } from "./StackedNotifications"
|
|
8
|
+
|
|
9
|
+
const A: StackedNotificationItem = {
|
|
10
|
+
id: "a",
|
|
11
|
+
type: "info",
|
|
12
|
+
message: "First notification"
|
|
13
|
+
}
|
|
14
|
+
const B: StackedNotificationItem = {
|
|
15
|
+
id: "b",
|
|
16
|
+
type: "warning",
|
|
17
|
+
message: "Second notification"
|
|
18
|
+
}
|
|
19
|
+
const C: StackedNotificationItem = {
|
|
20
|
+
id: "c",
|
|
21
|
+
type: "info",
|
|
22
|
+
message: "Third notification"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe("StackedNotifications", () => {
|
|
26
|
+
describe("ordering (LIFO, front is interactive)", () => {
|
|
27
|
+
it("shows the most recent item as the front, interactive card", () => {
|
|
28
|
+
renderWithTheme(<StackedNotifications items={[A, B]} />)
|
|
29
|
+
|
|
30
|
+
expect(screen.getByRole("alert")).toHaveTextContent("Second notification")
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it("renders exactly one interactive notification when several are queued", () => {
|
|
34
|
+
renderWithTheme(<StackedNotifications items={[A, B, C]} />)
|
|
35
|
+
|
|
36
|
+
expect(screen.getAllByRole("alert")).toHaveLength(1)
|
|
37
|
+
expect(screen.getByText("Third notification")).toBeInTheDocument()
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it("renders nothing when there are no items", () => {
|
|
41
|
+
renderWithTheme(<StackedNotifications items={[]} />)
|
|
42
|
+
|
|
43
|
+
expect(screen.queryByRole("alert")).not.toBeInTheDocument()
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
describe("peek stack (up to 2 behind the front card)", () => {
|
|
48
|
+
it("renders the front card plus one peek when 2 items are queued", () => {
|
|
49
|
+
renderWithTheme(<StackedNotifications items={[A, B]} />)
|
|
50
|
+
|
|
51
|
+
expect(screen.getByRole("alert")).toBeInTheDocument()
|
|
52
|
+
expect(screen.getByText("Second notification")).toBeInTheDocument()
|
|
53
|
+
expect(screen.getByText("First notification")).toBeInTheDocument()
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it("caps peeks at 2 regardless of how many are queued", () => {
|
|
57
|
+
const D: StackedNotificationItem = {
|
|
58
|
+
id: "d",
|
|
59
|
+
type: "warning",
|
|
60
|
+
message: "Fourth notification"
|
|
61
|
+
}
|
|
62
|
+
renderWithTheme(<StackedNotifications items={[A, B, C, D]} />)
|
|
63
|
+
|
|
64
|
+
// Front (D) + 2 peeks (C, B) are present; the oldest (A) is not rendered.
|
|
65
|
+
expect(screen.getByText("Fourth notification")).toBeInTheDocument()
|
|
66
|
+
expect(screen.getByText("Third notification")).toBeInTheDocument()
|
|
67
|
+
expect(screen.getByText("Second notification")).toBeInTheDocument()
|
|
68
|
+
expect(screen.queryByText("First notification")).not.toBeInTheDocument()
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it("only the front card is exposed as an alert; peeks are hidden from the accessibility tree", () => {
|
|
72
|
+
renderWithTheme(<StackedNotifications items={[A, B, C]} />)
|
|
73
|
+
|
|
74
|
+
expect(screen.getAllByRole("alert")).toHaveLength(1)
|
|
75
|
+
expect(screen.getByRole("alert")).toHaveTextContent("Third notification")
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it("only the front card has a close control", () => {
|
|
79
|
+
renderWithTheme(<StackedNotifications items={[A, B, C]} />)
|
|
80
|
+
|
|
81
|
+
expect(
|
|
82
|
+
screen.getAllByRole("button", { name: "Close notification" })
|
|
83
|
+
).toHaveLength(1)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it("promotes the next peek to front, revealing a new peek behind it", async () => {
|
|
87
|
+
const D: StackedNotificationItem = {
|
|
88
|
+
id: "d",
|
|
89
|
+
type: "warning",
|
|
90
|
+
message: "Fourth notification"
|
|
91
|
+
}
|
|
92
|
+
const user = userEvent.setup()
|
|
93
|
+
renderWithTheme(<StackedNotifications items={[A, B, C, D]} />)
|
|
94
|
+
|
|
95
|
+
await user.click(
|
|
96
|
+
screen.getByRole("button", { name: "Close notification" })
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
expect(screen.getByRole("alert")).toHaveTextContent("Third notification")
|
|
100
|
+
expect(screen.getByText("Second notification")).toBeInTheDocument()
|
|
101
|
+
expect(screen.getByText("First notification")).toBeInTheDocument()
|
|
102
|
+
expect(screen.queryByText("Fourth notification")).not.toBeInTheDocument()
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
describe("variants", () => {
|
|
107
|
+
it("renders the info variant with its icon", () => {
|
|
108
|
+
renderWithTheme(<StackedNotifications items={[A]} />)
|
|
109
|
+
|
|
110
|
+
expect(screen.getByLabelText("info")).toBeInTheDocument()
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it("renders the warning variant with its icon", () => {
|
|
114
|
+
renderWithTheme(<StackedNotifications items={[B]} />)
|
|
115
|
+
|
|
116
|
+
expect(screen.getByLabelText("warning")).toBeInTheDocument()
|
|
117
|
+
})
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
describe("dismissing", () => {
|
|
121
|
+
it("reveals the next item when the visible one is dismissed", async () => {
|
|
122
|
+
const user = userEvent.setup()
|
|
123
|
+
renderWithTheme(<StackedNotifications items={[A, B]} />)
|
|
124
|
+
|
|
125
|
+
await user.click(
|
|
126
|
+
screen.getByRole("button", { name: "Close notification" })
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
expect(screen.getByText("First notification")).toBeInTheDocument()
|
|
130
|
+
expect(screen.queryByText("Second notification")).not.toBeInTheDocument()
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it("calls onDismiss with the dismissed item id", async () => {
|
|
134
|
+
const user = userEvent.setup()
|
|
135
|
+
const onDismiss = vi.fn()
|
|
136
|
+
renderWithTheme(
|
|
137
|
+
<StackedNotifications items={[A, B]} onDismiss={onDismiss} />
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
await user.click(
|
|
141
|
+
screen.getByRole("button", { name: "Close notification" })
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
expect(onDismiss).toHaveBeenCalledTimes(1)
|
|
145
|
+
expect(onDismiss).toHaveBeenCalledWith("b")
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
describe("persistence (no auto-dismiss)", () => {
|
|
150
|
+
beforeEach(() => {
|
|
151
|
+
vi.useFakeTimers()
|
|
152
|
+
})
|
|
153
|
+
afterEach(() => {
|
|
154
|
+
vi.useRealTimers()
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it("does not dismiss on its own over time", () => {
|
|
158
|
+
renderWithTheme(<StackedNotifications items={[A]} />)
|
|
159
|
+
|
|
160
|
+
expect(screen.getByText("First notification")).toBeInTheDocument()
|
|
161
|
+
vi.advanceTimersByTime(60000)
|
|
162
|
+
expect(screen.getByText("First notification")).toBeInTheDocument()
|
|
163
|
+
})
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
describe("re-raise on reselect", () => {
|
|
167
|
+
it("keeps the notification until its item is removed", () => {
|
|
168
|
+
const { rerender } = renderWithTheme(
|
|
169
|
+
<StackedNotifications items={[A, B]} />
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
// Deselect B — A is revealed.
|
|
173
|
+
rerender(<StackedNotifications items={[A]} />)
|
|
174
|
+
expect(screen.getByText("First notification")).toBeInTheDocument()
|
|
175
|
+
expect(screen.queryByText("Second notification")).not.toBeInTheDocument()
|
|
176
|
+
|
|
177
|
+
// Reselect B — it re-raises to the top.
|
|
178
|
+
rerender(<StackedNotifications items={[A, B]} />)
|
|
179
|
+
expect(screen.getByText("Second notification")).toBeInTheDocument()
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
it("re-raises a previously dismissed item after deselect then reselect", async () => {
|
|
183
|
+
const user = userEvent.setup()
|
|
184
|
+
const { rerender } = renderWithTheme(
|
|
185
|
+
<StackedNotifications items={[A, B]} />
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
// Dismiss B — A shown, B hidden but still selected.
|
|
189
|
+
await user.click(
|
|
190
|
+
screen.getByRole("button", { name: "Close notification" })
|
|
191
|
+
)
|
|
192
|
+
expect(screen.getByText("First notification")).toBeInTheDocument()
|
|
193
|
+
|
|
194
|
+
// Deselect B (removed from items) then reselect it.
|
|
195
|
+
rerender(<StackedNotifications items={[A]} />)
|
|
196
|
+
rerender(<StackedNotifications items={[A, B]} />)
|
|
197
|
+
|
|
198
|
+
// The dismissed flag was cleared, so B shows again.
|
|
199
|
+
expect(screen.getByText("Second notification")).toBeInTheDocument()
|
|
200
|
+
})
|
|
201
|
+
})
|
|
202
|
+
})
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { View, ViewProps } from "react-native"
|
|
3
|
+
import Reanimated, {
|
|
4
|
+
useAnimatedStyle,
|
|
5
|
+
useSharedValue,
|
|
6
|
+
withSpring,
|
|
7
|
+
FadeInDown,
|
|
8
|
+
FadeOutDown,
|
|
9
|
+
Easing
|
|
10
|
+
} from "react-native-reanimated"
|
|
11
|
+
import styled from "@emotion/native"
|
|
12
|
+
import { useTheme } from "@emotion/react"
|
|
13
|
+
import { Notification } from "../Notification"
|
|
14
|
+
|
|
15
|
+
type StackedNotificationType = "info" | "warning"
|
|
16
|
+
|
|
17
|
+
type StackedNotificationItem = {
|
|
18
|
+
id: string
|
|
19
|
+
type: StackedNotificationType
|
|
20
|
+
message: React.ReactNode
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type StackedNotificationsOwnProps = {
|
|
24
|
+
items: StackedNotificationItem[]
|
|
25
|
+
onDismiss?: (id: string) => void
|
|
26
|
+
bottomOffset?: number
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type StackedNotificationsProps = StackedNotificationsOwnProps &
|
|
30
|
+
Omit<ViewProps, keyof StackedNotificationsOwnProps | "children">
|
|
31
|
+
|
|
32
|
+
const parseTokenValue = (value: string): number => parseFloat(value)
|
|
33
|
+
|
|
34
|
+
// Behind the front card, at most this many queued notifications peek out
|
|
35
|
+
// (à la iOS notification grouping) — regardless of how many are queued.
|
|
36
|
+
const PEEK_LAYERS = 2
|
|
37
|
+
|
|
38
|
+
// Unitless recede per depth level — the reveal distance itself comes from a
|
|
39
|
+
// spacing token (`peekStep`), this only controls the "shrink into the
|
|
40
|
+
// background" feel.
|
|
41
|
+
const SCALE_STEP = 0.06
|
|
42
|
+
const SPRING_CONFIG = { damping: 20, stiffness: 260, mass: 0.5 }
|
|
43
|
+
|
|
44
|
+
const CARD_ENTERING = FadeInDown.duration(200).easing(Easing.inOut(Easing.quad))
|
|
45
|
+
const CARD_EXITING = FadeOutDown.duration(200)
|
|
46
|
+
|
|
47
|
+
// Bottom-anchored so scaling never moves a card's bottom edge — only
|
|
48
|
+
// translateY does. That's what makes the peek reveal an exact, constant
|
|
49
|
+
// distance regardless of a card's own message length.
|
|
50
|
+
const CARD_BASE_STYLE = {
|
|
51
|
+
position: "absolute" as const,
|
|
52
|
+
bottom: 0,
|
|
53
|
+
left: 0,
|
|
54
|
+
right: 0,
|
|
55
|
+
transformOrigin: "50% 100%"
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Floats over content pinned to the bottom. `box-none` lets touches pass
|
|
59
|
+
// through to the content behind everywhere except on the notification itself,
|
|
60
|
+
// so the list is never blocked and never reflows.
|
|
61
|
+
const StyledRoot = styled(View)<{
|
|
62
|
+
rootBottom: number
|
|
63
|
+
rootPaddingH: number
|
|
64
|
+
}>(({ rootBottom, rootPaddingH }) => ({
|
|
65
|
+
position: "absolute",
|
|
66
|
+
left: 0,
|
|
67
|
+
right: 0,
|
|
68
|
+
bottom: rootBottom,
|
|
69
|
+
paddingHorizontal: rootPaddingH,
|
|
70
|
+
alignItems: "stretch"
|
|
71
|
+
}))
|
|
72
|
+
|
|
73
|
+
// Positioning context for the card layers. Deliberately left unsized — every
|
|
74
|
+
// layer is absolutely positioned and bottom-anchored, so the stack's own
|
|
75
|
+
// height stays 0 without clipping anything (RN views default to visible
|
|
76
|
+
// overflow).
|
|
77
|
+
const StyledStack = styled(View)({
|
|
78
|
+
position: "relative",
|
|
79
|
+
width: "100%"
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
type StackedCardProps = {
|
|
83
|
+
item: StackedNotificationItem
|
|
84
|
+
depth: number
|
|
85
|
+
revealStep: number
|
|
86
|
+
onDismiss?: (id: string) => void
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* One card in the stack. Depth 0 is the front (interactive); 1 and 2 peek
|
|
91
|
+
* out behind it. Stays mounted across promotions — dismissing the front card
|
|
92
|
+
* shifts every other card's `depth` down by one — so the position change
|
|
93
|
+
* springs smoothly instead of jump-cutting. Only truly joining or leaving
|
|
94
|
+
* the visible stack mounts/unmounts it.
|
|
95
|
+
*/
|
|
96
|
+
const StackedCard = ({
|
|
97
|
+
item,
|
|
98
|
+
depth,
|
|
99
|
+
revealStep,
|
|
100
|
+
onDismiss
|
|
101
|
+
}: StackedCardProps) => {
|
|
102
|
+
const depthSV = useSharedValue(depth)
|
|
103
|
+
|
|
104
|
+
React.useEffect(() => {
|
|
105
|
+
depthSV.value = withSpring(depth, SPRING_CONFIG)
|
|
106
|
+
}, [depth, depthSV])
|
|
107
|
+
|
|
108
|
+
const animatedStyle = useAnimatedStyle(() => ({
|
|
109
|
+
transform: [
|
|
110
|
+
{ translateY: depthSV.value * revealStep },
|
|
111
|
+
{ scale: 1 - depthSV.value * SCALE_STEP }
|
|
112
|
+
]
|
|
113
|
+
}))
|
|
114
|
+
|
|
115
|
+
const isFront = depth === 0
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<Reanimated.View
|
|
119
|
+
entering={CARD_ENTERING}
|
|
120
|
+
exiting={CARD_EXITING}
|
|
121
|
+
pointerEvents={isFront ? "auto" : "none"}
|
|
122
|
+
accessibilityElementsHidden={!isFront}
|
|
123
|
+
importantForAccessibility={isFront ? "auto" : "no-hide-descendants"}
|
|
124
|
+
style={[CARD_BASE_STYLE, { zIndex: -depth }, animatedStyle]}
|
|
125
|
+
>
|
|
126
|
+
<Notification
|
|
127
|
+
variant="toast"
|
|
128
|
+
type={item.type}
|
|
129
|
+
onClose={isFront ? () => onDismiss?.(item.id) : undefined}
|
|
130
|
+
>
|
|
131
|
+
{item.message}
|
|
132
|
+
</Notification>
|
|
133
|
+
</Reanimated.View>
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* A last-in-first-out stack of floating notifications pinned to the bottom of
|
|
139
|
+
* the screen. Only the most recent notification is interactive; up to two
|
|
140
|
+
* more peek out from behind it (à la iOS notification grouping) so the depth
|
|
141
|
+
* of the queue is visible, regardless of how many are actually queued.
|
|
142
|
+
* Dismissing the front one springs the next into place.
|
|
143
|
+
*
|
|
144
|
+
* There is no auto-dismiss: a notification persists until its item is removed
|
|
145
|
+
* from `items` (deselected). Removing then re-adding an item re-raises it to
|
|
146
|
+
* the top. Fully controlled — the consumer owns the set; this component owns
|
|
147
|
+
* ordering, dismissal, and enter/exit animation.
|
|
148
|
+
*
|
|
149
|
+
* @param {StackedNotificationItem[]} items - Active notifications, ordered by insertion — the last element is the most recent and sits at the front of the stack.
|
|
150
|
+
* @param {(id: string) => void} [onDismiss] - Called with the item id when its close control is pressed.
|
|
151
|
+
* @param {number} [bottomOffset] - Distance from the bottom edge in pixels. Defaults to `spacing.lg`.
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* ```tsx
|
|
155
|
+
* import { StackedNotifications } from "@butternutbox/pawprint-native"
|
|
156
|
+
*
|
|
157
|
+
* <StackedNotifications
|
|
158
|
+
* items={selected.map((a) => ({
|
|
159
|
+
* id: a.id,
|
|
160
|
+
* type: a.severe ? "warning" : "info",
|
|
161
|
+
* message: a.note
|
|
162
|
+
* }))}
|
|
163
|
+
* onDismiss={(id) => console.log("dismissed", id)}
|
|
164
|
+
* />
|
|
165
|
+
* ```
|
|
166
|
+
*/
|
|
167
|
+
export const StackedNotifications = React.forwardRef<
|
|
168
|
+
View,
|
|
169
|
+
StackedNotificationsProps
|
|
170
|
+
>(({ items, onDismiss, bottomOffset, ...rest }, ref) => {
|
|
171
|
+
const theme = useTheme()
|
|
172
|
+
const { spacing } = theme.tokens.semantics.dimensions
|
|
173
|
+
|
|
174
|
+
// Ids the user has closed with the dismiss control. Kept until the item
|
|
175
|
+
// leaves `items`, so a dismissed-but-still-selected notification stays hidden.
|
|
176
|
+
const [dismissed, setDismissed] = React.useState<Set<string>>(new Set())
|
|
177
|
+
|
|
178
|
+
const idsKey = items.map((item) => item.id).join("|")
|
|
179
|
+
|
|
180
|
+
// Prune dismissed ids that are no longer present, so deselect-then-reselect
|
|
181
|
+
// re-raises the notification. A no-op returns the same Set reference, letting
|
|
182
|
+
// React bail out of the re-render.
|
|
183
|
+
React.useEffect(() => {
|
|
184
|
+
setDismissed((prev) => {
|
|
185
|
+
if (prev.size === 0) return prev
|
|
186
|
+
const present = new Set(items.map((item) => item.id))
|
|
187
|
+
let changed = false
|
|
188
|
+
const next = new Set<string>()
|
|
189
|
+
prev.forEach((id) => {
|
|
190
|
+
if (present.has(id)) next.add(id)
|
|
191
|
+
else changed = true
|
|
192
|
+
})
|
|
193
|
+
return changed ? next : prev
|
|
194
|
+
})
|
|
195
|
+
}, [idsKey])
|
|
196
|
+
|
|
197
|
+
const activeItems = React.useMemo(
|
|
198
|
+
() => items.filter((item) => !dismissed.has(item.id)),
|
|
199
|
+
[items, dismissed]
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
// Front-most first: [front, peek₁, peek₂]. Capped at PEEK_LAYERS behind
|
|
203
|
+
// the front card regardless of how many are actually queued — the array
|
|
204
|
+
// index doubles as each card's depth.
|
|
205
|
+
const layers = React.useMemo(
|
|
206
|
+
() => activeItems.slice(-(PEEK_LAYERS + 1)).reverse(),
|
|
207
|
+
[activeItems]
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
const handleDismiss = (id: string) => {
|
|
211
|
+
setDismissed((prev) => {
|
|
212
|
+
const next = new Set(prev)
|
|
213
|
+
next.add(id)
|
|
214
|
+
return next
|
|
215
|
+
})
|
|
216
|
+
onDismiss?.(id)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const rootBottom = bottomOffset ?? parseTokenValue(spacing.lg)
|
|
220
|
+
const peekStep = parseTokenValue(spacing.xs)
|
|
221
|
+
|
|
222
|
+
return (
|
|
223
|
+
<StyledRoot
|
|
224
|
+
ref={ref}
|
|
225
|
+
pointerEvents="box-none"
|
|
226
|
+
rootBottom={rootBottom}
|
|
227
|
+
rootPaddingH={parseTokenValue(spacing.md)}
|
|
228
|
+
{...rest}
|
|
229
|
+
>
|
|
230
|
+
<StyledStack>
|
|
231
|
+
{layers.map((item, depth) => (
|
|
232
|
+
<StackedCard
|
|
233
|
+
key={item.id}
|
|
234
|
+
item={item}
|
|
235
|
+
depth={depth}
|
|
236
|
+
revealStep={peekStep}
|
|
237
|
+
onDismiss={handleDismiss}
|
|
238
|
+
/>
|
|
239
|
+
))}
|
|
240
|
+
</StyledStack>
|
|
241
|
+
</StyledRoot>
|
|
242
|
+
)
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
StackedNotifications.displayName = "StackedNotifications"
|
|
246
|
+
|
|
247
|
+
export type { StackedNotificationItem, StackedNotificationType }
|
|
@@ -16,6 +16,7 @@ export * from "./SelectField"
|
|
|
16
16
|
export * from "./NativeSelectPicker"
|
|
17
17
|
export * from "./Slider"
|
|
18
18
|
export * from "./Notification"
|
|
19
|
+
export * from "./StackedNotifications"
|
|
19
20
|
export * from "./Tooltip"
|
|
20
21
|
export * from "./MessageCard"
|
|
21
22
|
export * from "./DatePicker"
|