@recursica/adapter-mantine-v8 1.3.6 → 1.4.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/dist/index.d.ts CHANGED
@@ -911,7 +911,7 @@ export declare type ModalOverlayProps = RecursicaOverStyled<ModalOverlayProps_2>
911
911
  * Native Mantine abstract properties like `size`, `radius`, and `shadow` have been stripped out
912
912
  * because they directly conflict with the non-negotiable pixel boundaries defined in the Recursica UI Kit.
913
913
  */
914
- export declare type ModalProps = RecursicaOverStyled<Omit<ModalProps_2, "size" | "radius" | "shadow"> & RecursicaModalProps>;
914
+ export declare type ModalProps = RecursicaOverStyled<Omit<ModalProps_2, "size" | "radius" | "shadow" | "title" | "onClose" | "withCloseButton" | "closeButtonProps"> & RecursicaModalProps>;
915
915
 
916
916
  declare const ModalRoot: default_2.ForwardRefExoticComponent<ModalRootProps & default_2.RefAttributes<HTMLDivElement>>;
917
917
 
@@ -1029,12 +1029,16 @@ export declare const Panel: PanelComponent;
1029
1029
  *
1030
1030
  * ```tsx
1031
1031
  * <Panel opened={opened} onClose={close} title="Panel Title" placement="right">
1032
- * <Panel.Body>
1033
- * Content goes here
1034
- * </Panel.Body>
1032
+ * Content goes here
1033
+ * <Panel.Footer>...</Panel.Footer>
1035
1034
  * </Panel>
1036
1035
  * ```
1037
1036
  *
1037
+ * The header is required and always rendered: `title` is required, and an icon-only Recursica
1038
+ * Close button (`aria-label` "Close", overridable via `closeButtonAriaLabel`) calls the required
1039
+ * `onClose`. The footer is optional. Passing `header` replaces the whole default header; avoid it,
1040
+ * because then nothing guarantees the title or the Close button.
1041
+ *
1038
1042
  * Always non-modal, with no props to change it: the page behind stays usable
1039
1043
  * (no overlay, focus trap or scroll lock), focus returns to the opener on close,
1040
1044
  * Escape always closes, and outside clicks never do.
@@ -1050,7 +1054,7 @@ export declare const Panel: PanelComponent;
1050
1054
  * - `Panel.Stack` — Stacked drawer context
1051
1055
  */
1052
1056
  declare const PanelBase: {
1053
- ({ overStyled, placement, keepMounted, wrapHeaderText, ...rest }: PanelProps): JSX.Element;
1057
+ ({ overStyled, placement, keepMounted, wrapHeaderText, title, onClose, closeButtonAriaLabel, header, children, ...rest }: PanelProps): JSX.Element;
1054
1058
  displayName: string;
1055
1059
  };
1056
1060
 
@@ -1244,7 +1248,7 @@ export declare interface RecursicaNumberInputProps extends Omit<NumberInputProps
1244
1248
  /**
1245
1249
  * Recursica Panel root props. Extends Mantine Drawer.
1246
1250
  */
1247
- export declare interface RecursicaPanelProps extends Omit<DrawerProps, "size" | "styles" | "classNames" | "style" | "position" | "opened" | "withOverlay" | "trapFocus" | "lockScroll" | "returnFocus" | "closeOnEscape" | "closeOnClickOutside">, RecursicaPanelProps_2 {
1251
+ export declare interface RecursicaPanelProps extends Omit<DrawerProps, "size" | "styles" | "classNames" | "style" | "position" | "opened" | "title" | "withCloseButton" | "closeButtonProps" | "withOverlay" | "trapFocus" | "lockScroll" | "returnFocus" | "closeOnEscape" | "closeOnClickOutside">, RecursicaPanelProps_2 {
1248
1252
  }
1249
1253
 
1250
1254
  export declare interface RecursicaRadioGroupProps extends Omit<RadioGroupProps_2, "size" | "labelProps" | "defaultValue" | "value">, Omit<RecursicaFormControlWrapperProps, "controlMaxWidth" | "controlMinWidth">, ReadOnlyControlProps, RecursicaRadioGroupProps_2 {
package/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "type": "git",
13
13
  "url": "git+https://github.com/borderux/recursica-adapter-mantine-v8.git"
14
14
  },
15
- "version": "1.3.6",
15
+ "version": "1.4.0",
16
16
  "publishConfig": {
17
17
  "access": "public"
18
18
  },
@@ -74,7 +74,7 @@
74
74
  "release": "changeset publish"
75
75
  },
76
76
  "dependencies": {
77
- "@recursica/adapter-common": "^1.2.0"
77
+ "@recursica/adapter-common": "^1.3.0"
78
78
  },
79
79
  "devDependencies": {
80
80
  "@changesets/cli": "^2.29.7",
@@ -1,6 +1,7 @@
1
1
  import React from "react";
2
2
  import type { Meta, StoryObj } from "@storybook/react";
3
3
  import { Button } from "./Button";
4
+ import { Loader } from "../Loader/Loader";
4
5
  import { Layer } from "@recursica/adapter-common";
5
6
 
6
7
  type ButtonStoryProps = React.ComponentProps<typeof Button>;
@@ -180,6 +181,17 @@ export const Loading: Story = {
180
181
  variant: "solid",
181
182
  size: "default",
182
183
  loading: true,
184
+ // Frozen spinner so the visual-regression snapshot is deterministic.
185
+ loaderProps: {
186
+ children: (
187
+ <Loader
188
+ size="default"
189
+ overStyled
190
+ color="var(--button-color)"
191
+ animate={false}
192
+ />
193
+ ),
194
+ },
183
195
  },
184
196
  parameters: {
185
197
  docs: {
@@ -30,3 +30,7 @@ Despite §1 above, `.content` never actually scaled fluidly: Mantine's own CSS s
30
30
  ### 5. Close button restyled to match Button
31
31
 
32
32
  `.close` reuses Button's text-variant/icon-only/small tokens (radius, padding, background/icon color, hover overlay, focus ring) instead of Mantine's native CloseButton look. Two overrides need `!important`/an explicit reset to win: Mantine's `CloseIcon` sizes itself via an inline `style` (`--cb-icon-size`, default `70%`), which beats any plain class rule — same category as Tree's `--level-offset` override; and Mantine's own subtle-variant `:hover` background (from `CloseButton.css`) is pinned back to the Button background token so only our `::after` overlay renders hover feedback.
33
+
34
+ ### 3. Required header with an always-present Close button (2026-10-16)
35
+
36
+ `title` and `onClose` are required (contract in `RecursicaModalProps`, `@recursica/adapter-common`), and `withCloseButton` and `closeButtonProps` are removed. `Modal.tsx` always renders `Modal.Header` > `Modal.Title` + `Modal.CloseButton` with `aria-label="Close"` unless `closeButtonAriaLabel` is passed. A `header` prop replaces the whole default header and is discouraged in its JSDoc.
@@ -26,7 +26,17 @@ import { type RecursicaModalProps } from "@recursica/adapter-common";
26
26
  * because they directly conflict with the non-negotiable pixel boundaries defined in the Recursica UI Kit.
27
27
  */
28
28
  export type ModalProps = RecursicaOverStyled<
29
- Omit<MantineModalProps, "size" | "radius" | "shadow"> & RecursicaModalProps
29
+ Omit<
30
+ MantineModalProps,
31
+ | "size"
32
+ | "radius"
33
+ | "shadow"
34
+ | "title"
35
+ | "onClose"
36
+ | "withCloseButton"
37
+ | "closeButtonProps"
38
+ > &
39
+ RecursicaModalProps
30
40
  >;
31
41
 
32
42
  /**
@@ -37,6 +47,11 @@ export type ModalProps = RecursicaOverStyled<
37
47
  * It automatically enforces Figma UI Kit geometries (`max-width: 960px`, `min-width: 304px`)
38
48
  * and handles internal body scrolling explicitly via CSS module overrides.
39
49
  *
50
+ * The header is required and always rendered: `title` is required, and an icon-only Recursica
51
+ * Close button (`aria-label` "Close", overridable via `closeButtonAriaLabel`) calls the required
52
+ * `onClose`. The footer is optional. Passing `header` replaces the whole default header; avoid it,
53
+ * because then nothing guarantees the title or the Close button.
54
+ *
40
55
  * @example
41
56
  * ```tsx
42
57
  * <Modal opened={opened} onClose={close} title="Hello World">
@@ -49,10 +64,11 @@ const ModalInner = React.forwardRef<HTMLDivElement, ModalProps>(function Modal(
49
64
  overStyled = false,
50
65
  children,
51
66
  title,
52
- withCloseButton = true,
67
+ onClose,
68
+ closeButtonAriaLabel = "Close",
69
+ header,
53
70
  overlayProps,
54
71
  withOverlay = true,
55
- closeButtonProps,
56
72
  ...rest
57
73
  },
58
74
  ref,
@@ -90,16 +106,15 @@ const ModalInner = React.forwardRef<HTMLDivElement, ModalProps>(function Modal(
90
106
  MantineModalProps,
91
107
  "size" | "radius" | "shadow"
92
108
  >)}
109
+ onClose={onClose}
93
110
  classNames={mergedClassNames}
94
111
  >
95
112
  {withOverlay && <MantineModal.Overlay {...overlayProps} />}
96
113
  <MantineModal.Content>
97
- {(title || withCloseButton) && (
114
+ {header ?? (
98
115
  <MantineModal.Header>
99
- {title && <MantineModal.Title>{title}</MantineModal.Title>}
100
- {withCloseButton && (
101
- <MantineModal.CloseButton {...closeButtonProps} />
102
- )}
116
+ <MantineModal.Title>{title}</MantineModal.Title>
117
+ <MantineModal.CloseButton aria-label={closeButtonAriaLabel} />
103
118
  </MantineModal.Header>
104
119
  )}
105
120
  <ModalBody>{children}</ModalBody>
@@ -43,6 +43,12 @@ All Recursica components in the `@recursica/adapter-mantine-v8` package adhere s
43
43
 
44
44
  ## 4. Key Integration Features & Constraints
45
45
 
46
+ ### Header: required title and Close button
47
+
48
+ `title` and `onClose` are required. The header always renders the title and an icon-only Recursica Close button with `aria-label="Close"`; pass `closeButtonAriaLabel` to translate it. `withCloseButton` has been removed: there is no way to hide the Close button.
49
+
50
+ The `header` prop replaces the whole default header. Avoid it: a custom header must supply its own title and Close button, and the adapter can no longer guarantee either.
51
+
46
52
  ## Architecture
47
53
 
48
54
  `Modal` wraps Mantine's `<Modal>` component. Native Mantine styling props (`size`, `radius`, `shadow`) are not used — appearance and sizing are fully controlled by the design system tokens instead.
@@ -129,4 +129,12 @@ Project-wide fix; see Chip's `CHIP_IMPLEMENTATION_NOTES.md` for the original dis
129
129
 
130
130
  **Decision:** Panel is never modal, and there are no props to change that.
131
131
 
132
- **Implementation:** The house rule is that the page behind a panel stays usable. `Panel.tsx` always passes `withOverlay={false}`, `trapFocus={false}`, `lockScroll={false}`, `returnFocus`, `closeOnEscape` and `closeOnClickOutside={false}` to Mantine's Drawer, after the caller's props, and omits those keys from `RecursicaPanelProps`. Escape is independent of outside clicks and always closes the panel; clicking the page behind never does. Every adapter must match: no overlay, no focus trap, no scroll lock, no `aria-modal`, no `aria-hidden` on the rest of the page.
132
+ **Implementation:** The house rule is that the page behind a panel stays usable. `Panel.tsx` composes `Drawer.Root` without a `Drawer.Overlay` and always passes `trapFocus={false}`, `lockScroll={false}`, `returnFocus`, `closeOnEscape` and `closeOnClickOutside={false}` to Mantine's Drawer, after the caller's props, and omits those keys from `RecursicaPanelProps`. Escape is independent of outside clicks and always closes the panel; clicking the page behind never does. Every adapter must match: no overlay, no focus trap, no scroll lock, no `aria-modal`, no `aria-hidden` on the rest of the page.
133
+
134
+ ---
135
+
136
+ ## 11. Required header with an always-present Close button (Matt Massey, 2026-10-16)
137
+
138
+ **Decision:** Every Panel has a header with a required `title` and a Close button wired to a required `onClose`. The footer is optional.
139
+
140
+ **Implementation:** The contract lives in `RecursicaPanelProps` in `@recursica/adapter-common`. `Panel.tsx` composes `Drawer.Root` > `Drawer.Content` > (`Drawer.Header` > `Drawer.Title` + `Drawer.CloseButton`) + `Drawer.Body`, so the header cannot be omitted. The Close button gets `aria-label="Close"` unless `closeButtonAriaLabel` is passed. `title`, `withCloseButton` and `closeButtonProps` are omitted from the Mantine props type. The `.close` class matches Button's text-variant, icon-only, small-size treatment (same as Modal), so the Close button looks like a Recursica icon-only Button. A `header` prop replaces the whole default header; it is discouraged in its JSDoc because nothing then guarantees a title or Close button.
@@ -1,3 +1,14 @@
1
+ /* Brand-layer exemptions (recursica-allow-brand) — see recursica-token-analyzer README.md.
2
+ * Close button is styled to match the Button component's text/icon-only variant, so it reuses
3
+ * the same global hover/focus state tokens Button.module.css exempts.
4
+ * recursica-allow-brand: --recursica_brand_states_focus_blur
5
+ * recursica-allow-brand: --recursica_brand_states_focus_border-size
6
+ * recursica-allow-brand: --recursica_brand_states_focus_color
7
+ * recursica-allow-brand: --recursica_brand_states_focus_margin
8
+ * recursica-allow-brand: --recursica_brand_states_hover_color
9
+ * recursica-allow-brand: --recursica_brand_states_hover_opacity
10
+ */
11
+
1
12
  /* HARDCODED VALUES:
2
13
  - border-style: solid: Structural rendering rule for the panel border.
3
14
  - .contentTop {
@@ -232,3 +243,97 @@
232
243
  /* Render over scrolling content */
233
244
  z-index: 10;
234
245
  }
246
+
247
+ /* ======================================
248
+ PANEL CLOSE BUTTON
249
+ ====================================== */
250
+
251
+ .close {
252
+ /* Matches the Button component's text-variant, icon-only, small-size visual treatment
253
+ (see Button.module.css) so the modal close control looks like a Recursica Button
254
+ rather than Mantine's native CloseButton. */
255
+ box-sizing: border-box;
256
+ display: flex;
257
+ align-items: center;
258
+ justify-content: center;
259
+ position: relative;
260
+ overflow: hidden;
261
+ transition: all 0.2s ease;
262
+
263
+ height: var(
264
+ --recursica_ui-kit_components_button_variants_sizes_small_properties_height
265
+ );
266
+ min-width: var(
267
+ --recursica_ui-kit_components_button_variants_content_icon-only_variants_sizes_small_properties_min-width
268
+ );
269
+ padding: 0
270
+ var(
271
+ --recursica_ui-kit_components_button_variants_content_icon-only_variants_sizes_small_properties_horizontal-padding
272
+ );
273
+ border-radius: var(
274
+ --recursica_ui-kit_components_button_variants_content_icon-only_variants_sizes_small_properties_border-radius
275
+ );
276
+
277
+ border-style: solid;
278
+ border-width: var(
279
+ --recursica_ui-kit_components_button_variants_styles_text_properties_border-size
280
+ );
281
+ border-color: var(
282
+ --recursica_ui-kit_components_button_variants_styles_text_properties_colors_border-color
283
+ );
284
+ background-color: var(
285
+ --recursica_ui-kit_components_button_variants_styles_text_properties_colors_background-color
286
+ );
287
+ color: var(
288
+ --recursica_ui-kit_components_button_variants_styles_text_properties_colors_icon-color
289
+ );
290
+ }
291
+
292
+ /* Mantine's CloseIcon sets width/height via an inline `style` attribute
293
+ (`--cb-icon-size`, default 70%), which beats any plain class rule regardless of specificity —
294
+ same category of override as Tree's `--level-offset` (see Tree/IMPLEMENTATION_NOTES.md).
295
+ `!important` is required here to land the Button's small icon-size token instead. */
296
+ .close svg {
297
+ position: relative;
298
+ z-index: 1;
299
+ width: var(
300
+ --recursica_ui-kit_components_button_variants_sizes_small_properties_icon
301
+ ) !important;
302
+ height: var(
303
+ --recursica_ui-kit_components_button_variants_sizes_small_properties_icon
304
+ ) !important;
305
+ }
306
+
307
+ .close::after {
308
+ content: "";
309
+ position: absolute;
310
+ inset: 0;
311
+ border-radius: inherit;
312
+ z-index: 0;
313
+ pointer-events: none;
314
+ transition: opacity 150ms ease;
315
+ opacity: 0;
316
+ background-color: var(--recursica_brand_states_hover_color);
317
+ }
318
+ .close:hover:not(:disabled)::after {
319
+ opacity: var(--recursica_brand_states_hover_opacity);
320
+ }
321
+
322
+ /* Mantine's own subtle-variant hover background (CloseButton.css) would otherwise show through
323
+ underneath the overlay above — pin it back to the Button's own background token so only the
324
+ `::after` overlay renders the hover feedback, matching Button's single-overlay treatment. */
325
+ .close:hover {
326
+ background-color: var(
327
+ --recursica_ui-kit_components_button_variants_styles_text_properties_colors_background-color
328
+ );
329
+ }
330
+
331
+ .close:focus-visible {
332
+ outline: none;
333
+ box-shadow:
334
+ 0 0 0 var(--recursica_brand_states_focus_border-size)
335
+ var(--recursica_brand_states_focus_color),
336
+ 0 0 var(--recursica_brand_states_focus_blur)
337
+ var(--recursica_brand_states_focus_margin)
338
+ var(--recursica_brand_states_focus_color);
339
+ }
@@ -21,10 +21,6 @@ const meta: Meta = {
21
21
  control: "text",
22
22
  description: "Panel title displayed in the header.",
23
23
  },
24
- withCloseButton: {
25
- control: "boolean",
26
- description: "Whether to display the close button in the header.",
27
- },
28
24
  wrapHeaderText: {
29
25
  control: "boolean",
30
26
  description:
@@ -76,7 +72,6 @@ export const Default: Story = {
76
72
  args: {
77
73
  placement: "right",
78
74
  title: "Panel Title",
79
- withCloseButton: true,
80
75
  wrapHeaderText: false,
81
76
  },
82
77
  render: ({ wrapHeaderText, ...args }: PanelStoryArgs) => {
@@ -117,7 +112,6 @@ export const LeftPlacement: Story = {
117
112
  args: {
118
113
  placement: "left",
119
114
  title: "Navigation",
120
- withCloseButton: true,
121
115
  wrapHeaderText: false,
122
116
  },
123
117
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -149,7 +143,6 @@ export const ScrollableContent: Story = {
149
143
  args: {
150
144
  placement: "right",
151
145
  title: "Scrollable Panel",
152
- withCloseButton: true,
153
146
  wrapHeaderText: false,
154
147
  },
155
148
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -191,7 +184,6 @@ export const LongTitle: Story = {
191
184
  placement: "right",
192
185
  title:
193
186
  "This is a ridiculously long panel title designed to test how the header CSS handles text overflow and whether it truncates correctly or breaks the layout",
194
- withCloseButton: true,
195
187
  wrapHeaderText: true,
196
188
  },
197
189
  render: ({ ...args }: PanelStoryArgs) => {
@@ -35,6 +35,9 @@ export interface RecursicaPanelProps
35
35
  | "style"
36
36
  | "position"
37
37
  | "opened"
38
+ | "title"
39
+ | "withCloseButton"
40
+ | "closeButtonProps"
38
41
  | "withOverlay"
39
42
  | "trapFocus"
40
43
  | "lockScroll"
@@ -56,12 +59,16 @@ export type PanelProps = RecursicaOverStyled<RecursicaPanelProps>;
56
59
  *
57
60
  * ```tsx
58
61
  * <Panel opened={opened} onClose={close} title="Panel Title" placement="right">
59
- * <Panel.Body>
60
- * Content goes here
61
- * </Panel.Body>
62
+ * Content goes here
63
+ * <Panel.Footer>...</Panel.Footer>
62
64
  * </Panel>
63
65
  * ```
64
66
  *
67
+ * The header is required and always rendered: `title` is required, and an icon-only Recursica
68
+ * Close button (`aria-label` "Close", overridable via `closeButtonAriaLabel`) calls the required
69
+ * `onClose`. The footer is optional. Passing `header` replaces the whole default header; avoid it,
70
+ * because then nothing guarantees the title or the Close button.
71
+ *
65
72
  * Always non-modal, with no props to change it: the page behind stays usable
66
73
  * (no overlay, focus trap or scroll lock), focus returns to the opener on close,
67
74
  * Escape always closes, and outside clicks never do.
@@ -81,6 +88,11 @@ const PanelBase = function Panel({
81
88
  placement = "right",
82
89
  keepMounted = true,
83
90
  wrapHeaderText = true,
91
+ title,
92
+ onClose,
93
+ closeButtonAriaLabel = "Close",
94
+ header,
95
+ children,
84
96
  ...rest
85
97
  }: PanelProps) {
86
98
  const sanitizedProps = filterStylingProps(rest, overStyled);
@@ -100,6 +112,7 @@ const PanelBase = function Panel({
100
112
  }`,
101
113
  header: styles.header,
102
114
  title: wrapHeaderText ? styles.titleTruncate : styles.title,
115
+ close: styles.close,
103
116
  body: styles.body,
104
117
  inner: styles.inner,
105
118
  },
@@ -107,20 +120,30 @@ const PanelBase = function Panel({
107
120
  );
108
121
 
109
122
  return (
110
- <MantineDrawer
111
- {...(sanitizedProps as unknown as MantineDrawerProps)}
123
+ <MantineDrawer.Root
124
+ {...(sanitizedProps as unknown as MantineDrawerRootProps)}
125
+ onClose={onClose}
112
126
  size="var(--recursica_ui-kit_components_panel_properties_max-width)"
113
127
  position={placement} /* Recursica default: right; Mantine default: left */
114
128
  keepMounted={keepMounted}
115
- /* Always non-modal: the page behind stays usable, Escape always closes */
116
- withOverlay={false}
129
+ /* Always non-modal and overlay-less: the page behind stays usable, Escape always closes */
117
130
  trapFocus={false}
118
131
  lockScroll={false}
119
132
  returnFocus
120
133
  closeOnEscape
121
134
  closeOnClickOutside={false}
122
135
  classNames={mergedClassNames}
123
- />
136
+ >
137
+ <MantineDrawer.Content>
138
+ {header ?? (
139
+ <MantineDrawer.Header>
140
+ <MantineDrawer.Title>{title}</MantineDrawer.Title>
141
+ <MantineDrawer.CloseButton aria-label={closeButtonAriaLabel} />
142
+ </MantineDrawer.Header>
143
+ )}
144
+ <MantineDrawer.Body>{children}</MantineDrawer.Body>
145
+ </MantineDrawer.Content>
146
+ </MantineDrawer.Root>
124
147
  );
125
148
  };
126
149
  PanelBase.displayName = "Panel";
@@ -15,12 +15,13 @@ import { Panel } from "@recursica/adapter-mantine-v8";
15
15
  ## 2. Basic Example
16
16
 
17
17
  ```tsx
18
- import React from "react";
18
+ import React, { useState } from "react";
19
19
  import { Panel } from "@recursica/adapter-mantine-v8";
20
20
 
21
21
  export default function Demo() {
22
+ const [opened, setOpened] = useState(false);
22
23
  return (
23
- <Panel>
24
+ <Panel opened={opened} onClose={() => setOpened(false)} title="Settings">
24
25
  <Text>This is a clean, structured visual container.</Text>
25
26
  </Panel>
26
27
  );
@@ -47,6 +48,12 @@ All Recursica components in the `@recursica/adapter-mantine-v8` package adhere s
47
48
 
48
49
  Use the `placement` prop (not `position`) to choose which edge the panel slides in from: `"left"`, `"right"`, `"top"`, or `"bottom"`. It defaults to `"right"`.
49
50
 
51
+ ### Header: required title and Close button
52
+
53
+ `title` and `onClose` are required. The header always renders the title and an icon-only Recursica Close button with `aria-label="Close"`; pass `closeButtonAriaLabel` to translate it. There is no prop to hide the Close button.
54
+
55
+ The `header` prop replaces the whole default header. Avoid it: a custom header must supply its own title and Close button, and the adapter can no longer guarantee either.
56
+
50
57
  ### Panel.Footer
51
58
 
52
59
  A `Panel.Footer` sub-component is available for footer content and action buttons.