ionbase-ui 0.10.0 → 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.
@@ -0,0 +1,49 @@
1
+ import React from 'react';
2
+ import type { AriaModalOverlayProps } from 'react-aria';
3
+ export type ModalSize = 'sm' | 'md' | 'lg' | 'fullscreen';
4
+ export type ModalAlign = 'left' | 'center';
5
+ export interface ModalProps extends AriaModalOverlayProps {
6
+ /** Whether the modal is shown. */
7
+ isOpen?: boolean;
8
+ /** Called with the next open state — `false` when the modal asks to close. */
9
+ onOpenChange?: (isOpen: boolean) => void;
10
+ /** Matches the Figma `Size` variant. */
11
+ size?: ModalSize;
12
+ /** Matches the Figma `Align` variant. */
13
+ align?: ModalAlign;
14
+ /** Required: the dialog's accessible name, wired via `aria-labelledby`. */
15
+ title: React.ReactNode;
16
+ /** Supporting copy under the title. */
17
+ description?: React.ReactNode;
18
+ /**
19
+ * The featured slot above the title — an icon or an illustration. Rendered
20
+ * inside a circular container that hugs it.
21
+ */
22
+ media?: React.ReactNode;
23
+ /** Action row. Right-aligned when `align="left"`, centred when `"center"`. */
24
+ footer?: React.ReactNode;
25
+ /** Show the close button. Defaults to `true`. */
26
+ showClose?: boolean;
27
+ /** Accessible name for the close button. */
28
+ closeLabel?: string;
29
+ /** Body content. */
30
+ children?: React.ReactNode;
31
+ className?: string;
32
+ }
33
+ /**
34
+ * Modal — Figma `Modal` (792:1537).
35
+ *
36
+ * `Size` x `Align`, with the sections as props rather than variants. The panel
37
+ * hugs its content at every size except `fullscreen`, which fills the viewport
38
+ * and pushes the actions to the bottom edge.
39
+ *
40
+ * RENDERS NOTHING WHEN CLOSED, and mounts into a portal when open. Both come
41
+ * from React Aria's `Overlay`, and both matter: a modal left in the tree while
42
+ * closed is still focusable by keyboard, and one rendered inline inherits any
43
+ * `overflow: hidden` or stacking context from wherever it was written.
44
+ *
45
+ * The scrim is part of this component in code, unlike in Figma — see the note
46
+ * in modal.css.
47
+ */
48
+ export declare const Modal: React.ForwardRefExoticComponent<ModalProps & React.RefAttributes<HTMLDivElement>>;
49
+ //# sourceMappingURL=Modal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Modal.d.ts","sourceRoot":"","sources":["../../src/components/Modal.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAkD,MAAM,OAAO,CAAC;AASvE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAExD,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,YAAY,CAAC;AAC1D,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,QAAQ,CAAC;AAkB3C,MAAM,WAAW,UAAW,SAAQ,qBAAqB;IACvD,kCAAkC;IAClC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,8EAA8E;IAC9E,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IACzC,wCAAwC;IACxC,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,yCAAyC;IACzC,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,2EAA2E;IAC3E,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,uCAAuC;IACvC,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC9B;;;OAGG;IACH,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,8EAA8E;IAC9E,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,iDAAiD;IACjD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oBAAoB;IACpB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA0FD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,KAAK,mFAUhB,CAAC"}
@@ -0,0 +1,59 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { forwardRef, useRef, useImperativeHandle } from 'react';
4
+ import { Overlay, useModalOverlay, useDialog, useButton, mergeProps, } from 'react-aria';
5
+ import { useOverlayTriggerState } from 'react-stately';
6
+ /** The close glyph from Figma's `Close` slot, inlined for the same reason
7
+ * Select's and PhoneInput's chevrons are: it is part of the component, not a
8
+ * slot a caller fills, and `ionbase-ui` deliberately does not depend on
9
+ * `ionbase-icons`. */
10
+ const CloseIcon = () => (_jsx("svg", { viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", focusable: "false", children: _jsx("path", { d: "M18 6 6 18M6 6l12 12", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }));
11
+ function ModalDialog(props, forwardedRef) {
12
+ const { state, size = 'md', align = 'left', title, description, media, footer, showClose = true, closeLabel = 'Close dialog', children, className, } = props;
13
+ const panelRef = useRef(null);
14
+ useImperativeHandle(forwardedRef, () => panelRef.current);
15
+ /*
16
+ * `useModalOverlay` supplies what makes this a modal rather than a styled
17
+ * box: it traps focus inside the panel, marks everything outside it
18
+ * `aria-hidden`, prevents the page behind from scrolling, and closes on
19
+ * Escape and on an outside click. `useDialog` adds the dialog role and
20
+ * moves focus in on open.
21
+ */
22
+ const { modalProps, underlayProps } = useModalOverlay(props, state, panelRef);
23
+ const { dialogProps, titleProps } = useDialog({ role: 'dialog' }, panelRef);
24
+ const closeRef = useRef(null);
25
+ const { buttonProps: closeButtonProps } = useButton({ onPress: () => state.close(), 'aria-label': closeLabel }, closeRef);
26
+ const panelClassNames = [
27
+ 'ion-modal',
28
+ `ion-modal--${size}`,
29
+ `ion-modal--${align}`,
30
+ className || '',
31
+ ]
32
+ .filter(Boolean)
33
+ .join(' ');
34
+ return (_jsx("div", { ...underlayProps, className: "ion-modal__scrim", children: _jsxs("div", { ...mergeProps(modalProps, dialogProps), ref: panelRef, className: panelClassNames, children: [_jsxs("div", { className: "ion-modal__header", children: [_jsxs("div", { className: "ion-modal__heading", children: [media && (_jsx("div", { className: "ion-modal__media", "aria-hidden": "true", children: media })), _jsx("h2", { ...titleProps, className: "ion-modal__title", children: title }), description && (_jsx("p", { className: "ion-modal__description", children: description }))] }), showClose && (_jsx("button", { ...closeButtonProps, ref: closeRef, type: "button", className: "ion-modal__close", children: _jsx(CloseIcon, {}) }))] }), children && _jsx("div", { className: "ion-modal__body", children: children }), footer && _jsx("div", { className: "ion-modal__footer", children: footer })] }) }));
35
+ }
36
+ const ModalDialogWithRef = forwardRef(ModalDialog);
37
+ /**
38
+ * Modal — Figma `Modal` (792:1537).
39
+ *
40
+ * `Size` x `Align`, with the sections as props rather than variants. The panel
41
+ * hugs its content at every size except `fullscreen`, which fills the viewport
42
+ * and pushes the actions to the bottom edge.
43
+ *
44
+ * RENDERS NOTHING WHEN CLOSED, and mounts into a portal when open. Both come
45
+ * from React Aria's `Overlay`, and both matter: a modal left in the tree while
46
+ * closed is still focusable by keyboard, and one rendered inline inherits any
47
+ * `overflow: hidden` or stacking context from wherever it was written.
48
+ *
49
+ * The scrim is part of this component in code, unlike in Figma — see the note
50
+ * in modal.css.
51
+ */
52
+ export const Modal = forwardRef((props, ref) => {
53
+ const state = useOverlayTriggerState(props);
54
+ if (!state.isOpen)
55
+ return null;
56
+ return (_jsx(Overlay, { children: _jsx(ModalDialogWithRef, { ...props, state: state, ref: ref }) }));
57
+ });
58
+ Modal.displayName = 'Modal';
59
+ //# sourceMappingURL=Modal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Modal.js","sourceRoot":"","sources":["../../src/components/Modal.tsx"],"names":[],"mappings":"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"}
@@ -26,6 +26,8 @@ export { Logo, LogoMark } from './Logo.js';
26
26
  export type { LogoProps, LogoMarkProps, LogoSize, LogoWordmark, } from './Logo.js';
27
27
  export { Divider } from './Divider.js';
28
28
  export type { DividerProps, DividerOrientation } from './Divider.js';
29
+ export { Modal } from './Modal.js';
30
+ export type { ModalProps, ModalSize, ModalAlign } from './Modal.js';
29
31
  export { Link } from './Link.js';
30
32
  export type { LinkProps, LinkVariant } from './Link.js';
31
33
  export { NavItem } from './NavItem.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,YAAY,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EACV,aAAa,EACb,YAAY,EACZ,cAAc,GACf,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC/C,YAAY,EACV,UAAU,EACV,eAAe,EACf,SAAS,EACT,WAAW,GACZ,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC3C,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAClD,YAAY,EACV,WAAW,EACX,gBAAgB,EAChB,UAAU,EACV,WAAW,GACZ,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC3C,YAAY,EACV,SAAS,EACT,aAAa,EACb,QAAQ,EACR,YAAY,GACb,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,YAAY,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACrE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC9E,YAAY,EACV,UAAU,EACV,YAAY,EACZ,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,cAAc,GACf,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,YAAY,EACV,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EACV,aAAa,EACb,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"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"}
@@ -12,6 +12,7 @@ export { Avatar, AvatarGroup } from './Avatar.js';
12
12
  export { Header } from './Header.js';
13
13
  export { Logo, LogoMark } from './Logo.js';
14
14
  export { Divider } from './Divider.js';
15
+ export { Modal } from './Modal.js';
15
16
  export { Link } from './Link.js';
16
17
  export { NavItem } from './NavItem.js';
17
18
  export { Table, TableHead, TableBody, TableRow, TableCell } from './Table.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE1C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAMzC,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAO/C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAE3C,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAOlD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAO3C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAU9E,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAKrD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE1C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAMzC,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAO/C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAE3C,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAOlD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAO3C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAU9E,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAKrD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC"}
@@ -43,6 +43,7 @@
43
43
  @import url('./table.css');
44
44
  @import url('./divider.css');
45
45
  @import url('./link.css');
46
+ @import url('./modal.css');
46
47
  @import url('./nav-item.css');
47
48
  @import url('./scroll-progress.css');
48
49
  @import url('./full-card.css');
@@ -0,0 +1,235 @@
1
+ /*
2
+ * Modal
3
+ *
4
+ * Measured from Figma `Modal` (792:1537). Four sizes x two alignments.
5
+ *
6
+ * THE SCRIM IS IN THIS COMPONENT, AND IN FIGMA IT IS NOT
7
+ *
8
+ * Figma models the panel alone so it can be dropped into any layout, and binds
9
+ * `surface/scrim` on a separate `Overlay — usage` frame instead. In code that
10
+ * split would be useless: nothing can render a modal without also rendering
11
+ * what sits behind it, so the scrim lives here.
12
+ *
13
+ * The consequence is that `bindings.json` records the panel's tokens but not
14
+ * the scrim, because the export only walks components and the scrim is on a
15
+ * frame. `surface/scrim` therefore still reads as unbound in any "which roles
16
+ * have no consumer" sweep. It has one — this file.
17
+ */
18
+ .ion-modal__scrim {
19
+ position: fixed;
20
+ inset: 0;
21
+ z-index: 1000;
22
+ display: flex;
23
+ align-items: center;
24
+ justify-content: center;
25
+ padding: var(--spacing-24);
26
+ background-color: var(--surface-scrim);
27
+ }
28
+
29
+ .ion-modal {
30
+ /*
31
+ * Size is a set of slots defaulting to Medium, the same shape Button and
32
+ * Input use — a size modifier reassigns the width rather than restating the
33
+ * whole box.
34
+ */
35
+ --ion-modal-width: 560px;
36
+
37
+ display: flex;
38
+ flex-direction: column;
39
+ gap: var(--spacing-20);
40
+ width: var(--ion-modal-width);
41
+
42
+ /*
43
+ * Never taller than the viewport it sits in. Figma's panels hug their
44
+ * content because a Figma frame has no viewport to overflow; a real one
45
+ * does, and a dialog taller than the screen becomes unreachable at both
46
+ * ends. The scrim's padding is subtracted so the panel never touches the
47
+ * edges.
48
+ */
49
+ max-width: 100%;
50
+ max-height: 100%;
51
+ padding: var(--spacing-24);
52
+ background-color: var(--surface-overlay);
53
+ border: var(--border-width-default) solid var(--border-subtle);
54
+ border-radius: var(--radius-xl);
55
+ box-shadow: var(--ion-shadow-shadow-xl);
56
+ }
57
+
58
+ .ion-modal--sm {
59
+ --ion-modal-width: 400px;
60
+ }
61
+
62
+ .ion-modal--lg {
63
+ --ion-modal-width: 720px;
64
+ }
65
+
66
+ /*
67
+ * Fullscreen is a different layout, not another width.
68
+ *
69
+ * It meets the viewport edges, so radius, border and shadow are removed rather
70
+ * than reduced — there is nothing for them to sit against. The scrim's padding
71
+ * goes too, or the "fullscreen" panel would float in a 24px frame.
72
+ */
73
+ .ion-modal--fullscreen {
74
+ --ion-modal-width: 100%;
75
+
76
+ height: 100%;
77
+ border: none;
78
+ border-radius: 0;
79
+ box-shadow: none;
80
+ }
81
+
82
+ .ion-modal__scrim:has(.ion-modal--fullscreen) {
83
+ padding: 0;
84
+ }
85
+
86
+ .ion-modal__header {
87
+ display: flex;
88
+ align-items: flex-start;
89
+ gap: var(--spacing-16);
90
+ }
91
+
92
+ .ion-modal__heading {
93
+ display: flex;
94
+ flex: 1 1 auto;
95
+ min-width: 0;
96
+ flex-direction: column;
97
+ gap: var(--spacing-8);
98
+ }
99
+
100
+ .ion-modal__title {
101
+ margin: 0;
102
+ color: var(--text-default);
103
+ font-family: var(--font-family-sans);
104
+ font-size: var(--type-h5);
105
+ line-height: var(--type-h5-line-height);
106
+ font-weight: var(--font-weight-semibold);
107
+ }
108
+
109
+ .ion-modal__description {
110
+ margin: 0;
111
+ color: var(--text-secondary);
112
+ font-family: var(--font-family-sans);
113
+ font-size: var(--type-body);
114
+ line-height: var(--type-body-line-height);
115
+ font-weight: var(--font-weight-regular);
116
+ }
117
+
118
+ /*
119
+ * The featured slot. Circular, hugging whatever is put in it, so an icon and a
120
+ * spot illustration both sit correctly without a second variant.
121
+ */
122
+ .ion-modal__media {
123
+ display: inline-flex;
124
+ align-items: center;
125
+ justify-content: center;
126
+ flex-shrink: 0;
127
+ width: var(--spacing-48);
128
+ height: var(--spacing-48);
129
+ background-color: var(--surface-primary-subtle);
130
+ border-radius: var(--radius-full);
131
+ color: var(--icon-primary);
132
+ }
133
+
134
+ .ion-modal__media svg {
135
+ width: var(--icon-size-lg);
136
+ height: var(--icon-size-lg);
137
+ }
138
+
139
+ /*
140
+ * 32px, not the 20px glyph it contains. WCAG 2.2 SC 2.5.8 asks for a 24x24
141
+ * minimum pointer target and the icon alone would fail it. Do not shrink this
142
+ * to fit the icon.
143
+ */
144
+ .ion-modal__close {
145
+ display: inline-flex;
146
+ align-items: center;
147
+ justify-content: center;
148
+ flex-shrink: 0;
149
+ width: var(--spacing-32);
150
+ height: var(--spacing-32);
151
+ padding: 0;
152
+ background: none;
153
+ border: none;
154
+ border-radius: var(--radius-sm);
155
+ color: var(--icon-secondary);
156
+ cursor: pointer;
157
+ transition: background-color var(--ion-duration-base) var(--ion-ease-out);
158
+ }
159
+
160
+ .ion-modal__close svg {
161
+ width: var(--icon-size-md);
162
+ height: var(--icon-size-md);
163
+ }
164
+
165
+ @media (hover: hover) {
166
+ .ion-modal__close:hover {
167
+ background-color: var(--surface-hover);
168
+ }
169
+ }
170
+
171
+ .ion-modal__close:focus-visible {
172
+ outline: var(--border-width-thick) solid var(--ring-focus);
173
+ outline-offset: 2px;
174
+ }
175
+
176
+ /*
177
+ * The body is the only part that scrolls. Capping the panel and letting this
178
+ * flex-shrink keeps the header and actions in place while long content moves
179
+ * underneath them — which is the whole reason a dialog has a header at all.
180
+ */
181
+ .ion-modal__body {
182
+ flex: 1 1 auto;
183
+ min-height: 0;
184
+ overflow-y: auto;
185
+ color: var(--text-secondary);
186
+ font-family: var(--font-family-sans);
187
+ font-size: var(--type-body);
188
+ line-height: var(--type-body-line-height);
189
+ }
190
+
191
+ .ion-modal__footer {
192
+ display: flex;
193
+ align-items: center;
194
+ justify-content: flex-end;
195
+ gap: var(--spacing-12);
196
+ flex-shrink: 0;
197
+ }
198
+
199
+ /*
200
+ * Centred alignment.
201
+ *
202
+ * THE SPACER IS REQUIRED HERE TOO, and assuming otherwise was wrong.
203
+ *
204
+ * The heading is a flex item beside the close button, so it does not span the
205
+ * panel — centring inside it lands the title and media 24px left of the
206
+ * panel's true centre, which is half the close button plus its gap. Exactly
207
+ * the same 24px Figma showed, for exactly the same reason, and the story
208
+ * `CentredContentIsActuallyCentred` measured it.
209
+ *
210
+ * A `::before` rather than a real element, and gated on `:has()` so it exists
211
+ * only when the close button does — mirroring Figma, where the spacer is bound
212
+ * to the same `Show Close` property. A spacer that outlived the button would
213
+ * push the content 24px the other way: the same bug mirrored.
214
+ *
215
+ * The actions centre too. Right-aligned buttons under centred copy read as
216
+ * broken.
217
+ */
218
+ .ion-modal--center .ion-modal__header:has(.ion-modal__close)::before {
219
+ content: '';
220
+ flex-shrink: 0;
221
+ width: var(--spacing-32);
222
+ }
223
+
224
+ .ion-modal--center .ion-modal__heading {
225
+ align-items: center;
226
+ text-align: center;
227
+ }
228
+
229
+ .ion-modal--center .ion-modal__body {
230
+ text-align: center;
231
+ }
232
+
233
+ .ion-modal--center .ion-modal__footer {
234
+ justify-content: center;
235
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ionbase-ui",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "IonBase Design System — accessible React components, styles and design tokens in one package",
5
5
  "license": "MIT",
6
6
  "type": "module",