@sproutsocial/seeds-react-panel 0.3.7

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.
@@ -0,0 +1,376 @@
1
+ import React from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+ import { Box } from "@sproutsocial/seeds-react-box";
4
+ import { Button } from "@sproutsocial/seeds-react-button";
5
+ import { Text } from "@sproutsocial/seeds-react-text";
6
+ import Panel, { PanelProvider, usePanelContext } from "./index";
7
+
8
+ const ToggleButton = ({ label = "Toggle Panel" }: { label?: string }) => {
9
+ const { togglePanel } = usePanelContext();
10
+ return (
11
+ <Button appearance="primary" onClick={togglePanel}>
12
+ {label}
13
+ </Button>
14
+ );
15
+ };
16
+
17
+ const LayoutFrame = ({ children }: { children: React.ReactNode }) => (
18
+ <Box display="flex" height="100vh" width="100%" bg="neutral.900">
19
+ {children}
20
+ </Box>
21
+ );
22
+
23
+ const MainContentWrapper = ({ children }: { children: React.ReactNode }) => (
24
+ <Box flex="1 1 auto" overflow="auto" bg="neutral.100">
25
+ {children}
26
+ </Box>
27
+ );
28
+
29
+ const MainContent = () => (
30
+ <MainContentWrapper>
31
+ <Box p={500} mr={100}>
32
+ <Text.Headline as="h1">Main content</Text.Headline>
33
+ <Text.BodyCopy as="p">
34
+ The panel renders inline at desktop widths and pushes this content.
35
+ Below the mobile breakpoint, it switches to a bottom-sheet overlay.
36
+ </Text.BodyCopy>
37
+ <ToggleButton />
38
+ </Box>
39
+ </MainContentWrapper>
40
+ );
41
+
42
+ const PanelBody = () => (
43
+ <Panel.Content>
44
+ <Text.BodyCopy as="p">
45
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam ac
46
+ consectetur tortor.
47
+ </Text.BodyCopy>
48
+ </Panel.Content>
49
+ );
50
+
51
+ const meta: Meta<typeof Panel> = {
52
+ title: "Components/Panel",
53
+ component: Panel,
54
+ parameters: { layout: "fullscreen" },
55
+ };
56
+ export default meta;
57
+
58
+ type Story = StoryObj<typeof Panel>;
59
+
60
+ export const Default: Story = {
61
+ render: () => (
62
+ <PanelProvider defaultOpen>
63
+ <LayoutFrame>
64
+ <MainContent />
65
+ <Panel
66
+ closeButtonLabel="Close"
67
+ title="Panel title"
68
+ titleId="panel-header"
69
+ aria-labelledby="panel-header"
70
+ >
71
+ <PanelBody />
72
+ </Panel>
73
+ </LayoutFrame>
74
+ </PanelProvider>
75
+ ),
76
+ };
77
+
78
+ export const LeftSide: Story = {
79
+ render: () => (
80
+ <PanelProvider defaultOpen>
81
+ <LayoutFrame>
82
+ <Panel
83
+ closeButtonLabel="Close"
84
+ direction="left"
85
+ title="Panel title"
86
+ titleId="panel-header"
87
+ aria-labelledby="panel-header"
88
+ >
89
+ <PanelBody />
90
+ </Panel>
91
+ <MainContent />
92
+ </LayoutFrame>
93
+ </PanelProvider>
94
+ ),
95
+ };
96
+
97
+ export const CustomWidth: Story = {
98
+ render: () => (
99
+ <PanelProvider defaultOpen>
100
+ <LayoutFrame>
101
+ <MainContent />
102
+ <Panel
103
+ closeButtonLabel="Close"
104
+ width={500}
105
+ title="Panel title"
106
+ titleId="panel-header"
107
+ aria-labelledby="panel-header"
108
+ >
109
+ <PanelBody />
110
+ </Panel>
111
+ </LayoutFrame>
112
+ </PanelProvider>
113
+ ),
114
+ };
115
+
116
+ export const CustomHeader: Story = {
117
+ render: () => (
118
+ <PanelProvider defaultOpen>
119
+ <LayoutFrame>
120
+ <MainContent />
121
+ <Panel
122
+ closeButtonLabel="Close"
123
+ header={
124
+ <Panel.Header>
125
+ <Text.Headline as="h2">Custom header</Text.Headline>
126
+ <Panel.CloseButton />
127
+ </Panel.Header>
128
+ }
129
+ >
130
+ <Panel.Content>
131
+ <Text.BodyCopy as="p">
132
+ When you supply a custom header via the header prop, you are
133
+ responsible for rendering Panel.CloseButton yourself.
134
+ </Text.BodyCopy>
135
+ </Panel.Content>
136
+ </Panel>
137
+ </LayoutFrame>
138
+ </PanelProvider>
139
+ ),
140
+ };
141
+
142
+ export const WithFooter: Story = {
143
+ render: () => (
144
+ <PanelProvider defaultOpen>
145
+ <LayoutFrame>
146
+ <MainContent />
147
+ <Panel
148
+ closeButtonLabel="Close"
149
+ title="Panel title"
150
+ titleId="panel-header"
151
+ aria-labelledby="panel-header"
152
+ >
153
+ <Panel.Content>
154
+ {Array.from({ length: 20 }, (_, i) => (
155
+ <Text.BodyCopy key={i} as="p">
156
+ Scrollable content line {i + 1}
157
+ </Text.BodyCopy>
158
+ ))}
159
+ </Panel.Content>
160
+ <Panel.Footer>
161
+ <Box display="flex" justifyContent="flex-end" gap={300}>
162
+ <Button appearance="secondary">Cancel</Button>
163
+ <Button appearance="primary">Save</Button>
164
+ </Box>
165
+ </Panel.Footer>
166
+ </Panel>
167
+ </LayoutFrame>
168
+ </PanelProvider>
169
+ ),
170
+ };
171
+
172
+ export const ClosedByDefault: Story = {
173
+ render: () => (
174
+ <PanelProvider>
175
+ <LayoutFrame>
176
+ <MainContent />
177
+ <Panel
178
+ closeButtonLabel="Close"
179
+ title="Panel title"
180
+ titleId="panel-header"
181
+ aria-labelledby="panel-header"
182
+ >
183
+ <PanelBody />
184
+ </Panel>
185
+ </LayoutFrame>
186
+ </PanelProvider>
187
+ ),
188
+ };
189
+
190
+ export const MobileBottomSheet: Story = {
191
+ parameters: {
192
+ viewport: { defaultViewport: "mobile1" },
193
+ },
194
+ render: () => (
195
+ <PanelProvider defaultOpen>
196
+ <LayoutFrame>
197
+ <MainContent />
198
+ <Panel
199
+ closeButtonLabel="Close"
200
+ title="Panel title"
201
+ titleId="panel-header"
202
+ aria-labelledby="panel-header"
203
+ >
204
+ <PanelBody />
205
+ </Panel>
206
+ </LayoutFrame>
207
+ </PanelProvider>
208
+ ),
209
+ };
210
+
211
+ export const MobileWithFooter: Story = {
212
+ parameters: {
213
+ viewport: { defaultViewport: "mobile1" },
214
+ },
215
+ render: () => (
216
+ <PanelProvider defaultOpen>
217
+ <LayoutFrame>
218
+ <MainContent />
219
+ <Panel
220
+ closeButtonLabel="Close"
221
+ title="Panel title"
222
+ titleId="panel-header"
223
+ aria-labelledby="panel-header"
224
+ >
225
+ <Panel.Content>
226
+ {Array.from({ length: 20 }, (_, i) => (
227
+ <Text.BodyCopy key={i} as="p">
228
+ Scrollable content line {i + 1}
229
+ </Text.BodyCopy>
230
+ ))}
231
+ </Panel.Content>
232
+ <Panel.Footer>
233
+ <Box display="flex" justifyContent="flex-end" gap={300}>
234
+ <Button appearance="secondary">Cancel</Button>
235
+ <Button appearance="primary">Save</Button>
236
+ </Box>
237
+ </Panel.Footer>
238
+ </Panel>
239
+ </LayoutFrame>
240
+ </PanelProvider>
241
+ ),
242
+ };
243
+
244
+ export const MobileWithActions: Story = {
245
+ parameters: {
246
+ viewport: { defaultViewport: "mobile1" },
247
+ },
248
+ render: () => (
249
+ <PanelProvider defaultOpen>
250
+ <LayoutFrame>
251
+ <MainContent />
252
+ <Panel
253
+ closeButtonLabel="Close"
254
+ title="Panel title"
255
+ titleId="panel-header"
256
+ aria-labelledby="panel-header"
257
+ actions={[
258
+ {
259
+ "aria-label": "Expand",
260
+ iconName: "arrows-pointing-out-outline",
261
+ },
262
+ { "aria-label": "Share", iconName: "link-outline" },
263
+ ]}
264
+ >
265
+ <PanelBody />
266
+ </Panel>
267
+ </LayoutFrame>
268
+ </PanelProvider>
269
+ ),
270
+ };
271
+
272
+ /**
273
+ * Exception: use `actionsInHeader` only when the floating rail above the
274
+ * bottom sheet would collide with surrounding UI — for example, nested sheets
275
+ * or snap-point layouts where the fully-expanded sheet leaves no room above.
276
+ */
277
+ export const MobileWithActionsInHeader: Story = {
278
+ parameters: {
279
+ viewport: { defaultViewport: "mobile1" },
280
+ },
281
+ render: () => (
282
+ <PanelProvider defaultOpen>
283
+ <LayoutFrame>
284
+ <MainContent />
285
+ <Panel
286
+ closeButtonLabel="Close"
287
+ title="Panel title"
288
+ titleId="panel-header"
289
+ aria-labelledby="panel-header"
290
+ actions={[
291
+ {
292
+ "aria-label": "Expand",
293
+ iconName: "arrows-pointing-out-outline",
294
+ },
295
+ { "aria-label": "Share", iconName: "link-outline" },
296
+ ]}
297
+ actionsInHeader
298
+ >
299
+ <PanelBody />
300
+ </Panel>
301
+ </LayoutFrame>
302
+ </PanelProvider>
303
+ ),
304
+ };
305
+
306
+ export const CustomHeaderAndFooter: Story = {
307
+ render: () => (
308
+ <PanelProvider defaultOpen>
309
+ <LayoutFrame>
310
+ <MainContent />
311
+ <Panel
312
+ closeButtonLabel="Close"
313
+ header={
314
+ <Panel.Header>
315
+ <Text.Headline as="h2">Custom header</Text.Headline>
316
+ <Panel.CloseButton />
317
+ </Panel.Header>
318
+ }
319
+ >
320
+ <Panel.Content>
321
+ {Array.from({ length: 20 }, (_, i) => (
322
+ <Text.BodyCopy key={i} as="p">
323
+ Scrollable content line {i + 1}
324
+ </Text.BodyCopy>
325
+ ))}
326
+ </Panel.Content>
327
+ <Panel.Footer>
328
+ <Box display="flex" justifyContent="flex-end" gap={300}>
329
+ <Button appearance="secondary">Cancel</Button>
330
+ <Button appearance="primary">Save</Button>
331
+ </Box>
332
+ </Panel.Footer>
333
+ </Panel>
334
+ </LayoutFrame>
335
+ </PanelProvider>
336
+ ),
337
+ };
338
+
339
+ const ControlledDemo = () => {
340
+ const [open, setOpen] = React.useState(false);
341
+ return (
342
+ <PanelProvider open={open} onOpenChange={setOpen}>
343
+ <LayoutFrame>
344
+ <MainContentWrapper>
345
+ <Box p={500}>
346
+ <Text.Headline as="h1">Controlled panel</Text.Headline>
347
+ <Text.BodyCopy as="p">
348
+ Open state is managed externally via <code>open</code> and{" "}
349
+ <code>onOpenChange</code>.
350
+ </Text.BodyCopy>
351
+ <Box display="flex" gap={300} mt={400}>
352
+ <Button appearance="primary" onClick={() => setOpen(true)}>
353
+ Open
354
+ </Button>
355
+ <Button appearance="secondary" onClick={() => setOpen(false)}>
356
+ Close
357
+ </Button>
358
+ </Box>
359
+ </Box>
360
+ </MainContentWrapper>
361
+ <Panel
362
+ closeButtonLabel="Close"
363
+ title="Controlled panel"
364
+ titleId="controlled-panel-header"
365
+ aria-labelledby="controlled-panel-header"
366
+ >
367
+ <PanelBody />
368
+ </Panel>
369
+ </LayoutFrame>
370
+ </PanelProvider>
371
+ );
372
+ };
373
+
374
+ export const Controlled: Story = {
375
+ render: () => <ControlledDemo />,
376
+ };
package/src/Panel.tsx ADDED
@@ -0,0 +1,139 @@
1
+ import * as React from "react";
2
+ import Drawer from "@sproutsocial/seeds-react-drawer";
3
+ import { useIsMobile } from "@sproutsocial/seeds-react-hooks";
4
+ import { usePanelContext, PanelContext } from "./PanelContext";
5
+ import { PanelHeader } from "./PanelHeader";
6
+ import { PanelContent } from "./PanelContent";
7
+ import { PanelCloseButton } from "./PanelCloseButton";
8
+ import { PanelFooter } from "./PanelFooter";
9
+ import { PanelMobileActionsHeader } from "./PanelMobileActionsHeader";
10
+ import { PanelContainer, PanelInner } from "./styles";
11
+ import type { TypePanelProps, TypePanelContext } from "./PanelTypes";
12
+
13
+ const PanelComponent = ({
14
+ children,
15
+ closeButtonLabel,
16
+ header,
17
+ footer,
18
+ title,
19
+ titleId,
20
+ direction = "right",
21
+ width = 384,
22
+ gap = 4,
23
+ mobileBreakpoint,
24
+ zIndex,
25
+ id,
26
+ snapPoints,
27
+ defaultSnapPoint,
28
+ snapPoint,
29
+ onSnapPointChange,
30
+ snapToSequentialPoints,
31
+ actions,
32
+ actionsInHeader,
33
+ "aria-labelledby": ariaLabelledByProp,
34
+ "aria-label": ariaLabel,
35
+ ...rest
36
+ }: TypePanelProps) => {
37
+ const panelContext = usePanelContext();
38
+ const { isPanelOpen, closePanel } = panelContext;
39
+ const isMobile = useIsMobile(mobileBreakpoint);
40
+
41
+ // Expose closeButtonLabel through context so PanelCloseButton can read it
42
+ // without prop drilling — same pattern as DrawerContext.
43
+ const enrichedValue = React.useMemo<TypePanelContext>(
44
+ () => ({ ...panelContext, closeButtonLabel }),
45
+ [panelContext, closeButtonLabel]
46
+ );
47
+
48
+ // Freeze the last-rendered children during the collapse so consumers that
49
+ // conditionally render content don't produce an empty panel shell while the
50
+ // flex-basis animation runs.
51
+ const prevChildrenRef = React.useRef<React.ReactNode>(null);
52
+ if (isPanelOpen) {
53
+ prevChildrenRef.current = children;
54
+ }
55
+ const renderedChildren = isPanelOpen ? children : prevChildrenRef.current;
56
+
57
+ // When the consumer relies on the default header, its title element renders
58
+ // with `id={titleId}`. Auto-wire `aria-labelledby` so the dialog/aside has an
59
+ // accessible name without forcing every caller to repeat the id.
60
+ const effectiveAriaLabelledBy =
61
+ ariaLabelledByProp ?? (header == null ? titleId : undefined);
62
+
63
+ if (isMobile) {
64
+ // With `actionsInHeader`, Drawer suppresses the floating rail and expects
65
+ // the consumer to render the actions inside a custom header. Auto-compose
66
+ // one (title + action pills + close pill) when the consumer hasn't
67
+ // supplied their own header; otherwise we'd drop `actions` on the floor.
68
+ const shouldRenderActionsHeader =
69
+ header == null &&
70
+ actionsInHeader === true &&
71
+ !!actions &&
72
+ actions.length > 0;
73
+
74
+ return (
75
+ <PanelContext.Provider value={enrichedValue}>
76
+ <Drawer
77
+ {...rest}
78
+ open={isPanelOpen}
79
+ onClose={closePanel}
80
+ direction="bottom"
81
+ closeButtonLabel={closeButtonLabel}
82
+ zIndex={zIndex}
83
+ snapPoints={snapPoints}
84
+ defaultSnapPoint={defaultSnapPoint}
85
+ snapPoint={snapPoint}
86
+ onSnapPointChange={onSnapPointChange}
87
+ snapToSequentialPoints={snapToSequentialPoints}
88
+ actions={actions}
89
+ actionsInHeader={actionsInHeader}
90
+ aria-labelledby={effectiveAriaLabelledBy}
91
+ aria-label={ariaLabel}
92
+ >
93
+ {header ??
94
+ (shouldRenderActionsHeader ? (
95
+ <PanelMobileActionsHeader
96
+ title={title}
97
+ titleId={titleId}
98
+ actions={actions!}
99
+ />
100
+ ) : (
101
+ <Drawer.Header title={title} id={titleId} />
102
+ ))}
103
+ {children}
104
+ {footer}
105
+ </Drawer>
106
+ </PanelContext.Provider>
107
+ );
108
+ }
109
+
110
+ return (
111
+ <PanelContext.Provider value={enrichedValue}>
112
+ <PanelContainer
113
+ {...rest}
114
+ $isOpen={isPanelOpen}
115
+ $direction={direction}
116
+ $width={width}
117
+ $gap={gap}
118
+ data-qa-panel={id}
119
+ data-qa-panel-isopen={isPanelOpen}
120
+ aria-labelledby={effectiveAriaLabelledBy}
121
+ aria-label={ariaLabel}
122
+ >
123
+ <PanelInner $direction={direction} $width={width}>
124
+ {header ?? <PanelHeader title={title} id={titleId} />}
125
+ {renderedChildren}
126
+ {footer}
127
+ </PanelInner>
128
+ </PanelContainer>
129
+ </PanelContext.Provider>
130
+ );
131
+ };
132
+
133
+ PanelComponent.Header = PanelHeader;
134
+ PanelComponent.Content = PanelContent;
135
+ PanelComponent.CloseButton = PanelCloseButton;
136
+ PanelComponent.Footer = PanelFooter;
137
+
138
+ export const Panel = PanelComponent;
139
+ export default PanelComponent;
@@ -0,0 +1,26 @@
1
+ import * as React from "react";
2
+ import Button from "@sproutsocial/seeds-react-button";
3
+ import Icon from "@sproutsocial/seeds-react-icon";
4
+ import { usePanelContext } from "./PanelContext";
5
+ import type { TypePanelCloseButtonProps } from "./PanelTypes";
6
+
7
+ export const PanelCloseButton = (props: TypePanelCloseButtonProps) => {
8
+ const panelContext = usePanelContext();
9
+
10
+ if (props.render) {
11
+ return props.render(panelContext) ?? null;
12
+ }
13
+
14
+ return (
15
+ <Button
16
+ appearance="pill"
17
+ aria-label={panelContext.closeButtonLabel}
18
+ onClick={panelContext.closePanel}
19
+ {...props}
20
+ >
21
+ {props.children || <Icon aria-hidden name="x-outline" />}
22
+ </Button>
23
+ );
24
+ };
25
+
26
+ PanelCloseButton.displayName = "Panel.CloseButton";
@@ -0,0 +1,18 @@
1
+ import * as React from "react";
2
+ import { Content } from "./styles";
3
+ import type { TypePanelContentProps } from "./PanelTypes";
4
+
5
+ export const PanelContent = ({ children, ...rest }: TypePanelContentProps) => (
6
+ <Content
7
+ flex="1 1 auto"
8
+ minHeight="0"
9
+ p={450}
10
+ color="text.body"
11
+ data-panel-content=""
12
+ {...rest}
13
+ >
14
+ {children}
15
+ </Content>
16
+ );
17
+
18
+ PanelContent.displayName = "Panel.Content";
@@ -0,0 +1,14 @@
1
+ import * as React from "react";
2
+ import type { TypePanelContext } from "./PanelTypes";
3
+
4
+ export const PanelContext = React.createContext<TypePanelContext | null>(null);
5
+
6
+ export const usePanelContext = (): TypePanelContext => {
7
+ const ctx = React.useContext(PanelContext);
8
+ if (!ctx) {
9
+ throw new Error(
10
+ "usePanelContext must be used within a <PanelProvider>. Wrap the consuming tree in <PanelProvider> to provide isPanelOpen / togglePanel."
11
+ );
12
+ }
13
+ return ctx;
14
+ };
@@ -0,0 +1,11 @@
1
+ import * as React from "react";
2
+ import { Footer } from "./styles";
3
+ import type { TypePanelFooterProps } from "./PanelTypes";
4
+
5
+ export const PanelFooter = ({ children, ...rest }: TypePanelFooterProps) => (
6
+ <Footer p={450} data-panel-footer="" {...rest}>
7
+ {children}
8
+ </Footer>
9
+ );
10
+
11
+ PanelFooter.displayName = "Panel.Footer";
@@ -0,0 +1,41 @@
1
+ import Box from "@sproutsocial/seeds-react-box";
2
+ import Text from "@sproutsocial/seeds-react-text";
3
+ import { usePanelContext } from "./PanelContext";
4
+ import { PanelCloseButton } from "./PanelCloseButton";
5
+ import type { TypePanelHeaderProps } from "./PanelTypes";
6
+
7
+ export const PanelHeader = ({
8
+ title = "",
9
+ id = undefined,
10
+ children,
11
+ render,
12
+ ...rest
13
+ }: TypePanelHeaderProps) => {
14
+ const panelContext = usePanelContext();
15
+
16
+ if (render) {
17
+ return render(panelContext);
18
+ }
19
+
20
+ return (
21
+ <Box
22
+ display="flex"
23
+ flex="0 0 auto"
24
+ justifyContent="space-between"
25
+ alignItems="center"
26
+ p={400}
27
+ borderBottom="1px solid"
28
+ borderColor="container.border.base"
29
+ {...rest}
30
+ >
31
+ {children || (
32
+ <>
33
+ <Text.Headline id={id}>{title}</Text.Headline>
34
+ <PanelCloseButton />
35
+ </>
36
+ )}
37
+ </Box>
38
+ );
39
+ };
40
+
41
+ PanelHeader.displayName = "Panel.Header";
@@ -0,0 +1,73 @@
1
+ import * as React from "react";
2
+ import Box from "@sproutsocial/seeds-react-box";
3
+ import Button from "@sproutsocial/seeds-react-button";
4
+ import Drawer from "@sproutsocial/seeds-react-drawer";
5
+ import Icon from "@sproutsocial/seeds-react-icon";
6
+ // eslint-disable-next-line import/no-deprecated
7
+ import Text from "@sproutsocial/seeds-react-text";
8
+ import type { TypeDrawerActionProps } from "@sproutsocial/seeds-react-drawer";
9
+
10
+ interface TypePanelMobileActionsHeaderProps {
11
+ title?: string;
12
+ titleId?: string;
13
+ actions: TypeDrawerActionProps[];
14
+ }
15
+
16
+ /**
17
+ * Mobile bottom-sheet header used by Panel when `actionsInHeader` is set and
18
+ * no custom `header` is provided. Renders the title on the left and the
19
+ * forwarded actions plus a close button as pills on the right — mirrors the
20
+ * pattern in `seeds-react-peek-in/PeekIn.tsx`. The Drawer's floating rail is
21
+ * suppressed in this combination (`actionsInHeader` opts out), so without this
22
+ * header the actions would render nowhere.
23
+ */
24
+ export const PanelMobileActionsHeader = ({
25
+ title,
26
+ titleId,
27
+ actions,
28
+ }: TypePanelMobileActionsHeaderProps) => (
29
+ <Drawer.Header
30
+ render={(ctx) => (
31
+ <Box
32
+ display="flex"
33
+ alignItems="center"
34
+ justifyContent="space-between"
35
+ pt={400}
36
+ px={450}
37
+ flex="0 0 auto"
38
+ >
39
+ <Text
40
+ as="h2"
41
+ fontSize={400}
42
+ fontWeight="semibold"
43
+ color="text.headline"
44
+ id={titleId}
45
+ >
46
+ {title}
47
+ </Text>
48
+ <Box display="flex" alignItems="center" gap={200}>
49
+ {actions.map((action, i) => (
50
+ <Button
51
+ key={i}
52
+ appearance="pill"
53
+ aria-label={action["aria-label"]}
54
+ onClick={action.onClick}
55
+ disabled={action.disabled}
56
+ >
57
+ {action.iconName && <Icon aria-hidden name={action.iconName} />}
58
+ </Button>
59
+ ))}
60
+ <Button
61
+ appearance="pill"
62
+ aria-label={ctx.closeButtonLabel}
63
+ onClick={ctx.onClose}
64
+ >
65
+ <Icon aria-hidden name="x-outline" />
66
+ </Button>
67
+ </Box>
68
+ </Box>
69
+ )}
70
+ />
71
+ );
72
+
73
+ PanelMobileActionsHeader.displayName = "PanelMobileActionsHeader";