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.
- package/dist/components/Modal.d.ts +49 -0
- package/dist/components/Modal.d.ts.map +1 -0
- package/dist/components/Modal.js +59 -0
- package/dist/components/Modal.js.map +1 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -0
- package/dist/components/index.js.map +1 -1
- package/dist/styles/index.css +1 -0
- package/dist/styles/modal.css +235 -0
- package/package.json +1 -1
|
@@ -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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"}
|
package/dist/components/index.js
CHANGED
|
@@ -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"}
|
package/dist/styles/index.css
CHANGED
|
@@ -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
|
+
}
|