@charcuterie/ui 1.0.1 → 2.0.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/ColorSchemeSwitcher/ColorSchemeSwitcher.d.ts +35 -0
- package/dist/ColorSchemeSwitcher/ColorSchemeSwitcher.d.ts.map +1 -0
- package/dist/ColorSchemeSwitcher/ColorSchemeSwitcher.js +33 -0
- package/dist/ColorSchemeSwitcher/ColorSchemeSwitcher.js.map +1 -0
- package/dist/ColorSchemeToggle/ColorSchemeToggle.d.ts +54 -0
- package/dist/ColorSchemeToggle/ColorSchemeToggle.d.ts.map +1 -0
- package/dist/ColorSchemeToggle/ColorSchemeToggle.js +30 -0
- package/dist/ColorSchemeToggle/ColorSchemeToggle.js.map +1 -0
- package/dist/Combobox/Combobox.d.ts +54 -0
- package/dist/Combobox/Combobox.d.ts.map +1 -0
- package/dist/Combobox/Combobox.js +216 -0
- package/dist/Combobox/Combobox.js.map +1 -0
- package/dist/Combobox/ComboboxOption.d.ts +36 -0
- package/dist/Combobox/ComboboxOption.d.ts.map +1 -0
- package/dist/Combobox/ComboboxOption.js +32 -0
- package/dist/Combobox/ComboboxOption.js.map +1 -0
- package/dist/Dialog/Dialog.d.ts +47 -0
- package/dist/Dialog/Dialog.d.ts.map +1 -0
- package/dist/Dialog/Dialog.js +52 -0
- package/dist/Dialog/Dialog.js.map +1 -0
- package/dist/Lightbox/Lightbox.d.ts +57 -0
- package/dist/Lightbox/Lightbox.d.ts.map +1 -0
- package/dist/Lightbox/Lightbox.js +50 -0
- package/dist/Lightbox/Lightbox.js.map +1 -0
- package/dist/Listbox/Listbox.d.ts +56 -0
- package/dist/Listbox/Listbox.d.ts.map +1 -0
- package/dist/Listbox/Listbox.js +151 -0
- package/dist/Listbox/Listbox.js.map +1 -0
- package/dist/Listbox/ListboxOption.d.ts +36 -0
- package/dist/Listbox/ListboxOption.d.ts.map +1 -0
- package/dist/Listbox/ListboxOption.js +42 -0
- package/dist/Listbox/ListboxOption.js.map +1 -0
- package/dist/Menu/Menu.d.ts.map +1 -1
- package/dist/Menu/Menu.js +47 -97
- package/dist/Menu/Menu.js.map +1 -1
- package/dist/Modal/Modal.d.ts +11 -59
- package/dist/Modal/Modal.d.ts.map +1 -1
- package/dist/Modal/Modal.js +28 -101
- package/dist/Modal/Modal.js.map +1 -1
- package/dist/Overlay/OverlayPanel.d.ts +50 -0
- package/dist/Overlay/OverlayPanel.d.ts.map +1 -0
- package/dist/Overlay/OverlayPanel.js +72 -0
- package/dist/Overlay/OverlayPanel.js.map +1 -0
- package/dist/Overlay/OverlayStack.d.ts +47 -0
- package/dist/Overlay/OverlayStack.d.ts.map +1 -0
- package/dist/Overlay/OverlayStack.js +46 -0
- package/dist/Overlay/OverlayStack.js.map +1 -0
- package/dist/Overlay/SharedBackdrop.d.ts +24 -0
- package/dist/Overlay/SharedBackdrop.d.ts.map +1 -0
- package/dist/Overlay/SharedBackdrop.js +28 -0
- package/dist/Overlay/SharedBackdrop.js.map +1 -0
- package/dist/Overlay/lockScrollBehind.d.ts.map +1 -0
- package/dist/Overlay/lockScrollBehind.js.map +1 -0
- package/dist/Overlay/overlayPanelClass.d.ts +17 -0
- package/dist/Overlay/overlayPanelClass.d.ts.map +1 -0
- package/dist/Overlay/overlayPanelClass.js +17 -0
- package/dist/Overlay/overlayPanelClass.js.map +1 -0
- package/dist/Overlay/useAnchoredOverlay.d.ts +97 -0
- package/dist/Overlay/useAnchoredOverlay.d.ts.map +1 -0
- package/dist/Overlay/useAnchoredOverlay.js +81 -0
- package/dist/Overlay/useAnchoredOverlay.js.map +1 -0
- package/dist/Popover/Popover.d.ts +16 -24
- package/dist/Popover/Popover.d.ts.map +1 -1
- package/dist/Popover/Popover.js +32 -68
- package/dist/Popover/Popover.js.map +1 -1
- package/dist/Tooltip/Tooltip.d.ts.map +1 -1
- package/dist/Tooltip/Tooltip.js +25 -25
- package/dist/Tooltip/Tooltip.js.map +1 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/mountStory.testHelpers.d.ts +1 -0
- package/dist/mountStory.testHelpers.d.ts.map +1 -1
- package/dist/mountStory.testHelpers.js +21 -1
- package/dist/mountStory.testHelpers.js.map +1 -1
- package/package.json +5 -4
- package/src/ColorSchemeSwitcher/ColorSchemeSwitcher.mdx +70 -0
- package/src/ColorSchemeSwitcher/ColorSchemeSwitcher.stories.tsx +118 -0
- package/src/ColorSchemeSwitcher/ColorSchemeSwitcher.test.tsx +53 -0
- package/src/ColorSchemeSwitcher/ColorSchemeSwitcher.tsx +90 -0
- package/src/ColorSchemeToggle/ColorSchemeToggle.mdx +48 -0
- package/src/ColorSchemeToggle/ColorSchemeToggle.stories.tsx +161 -0
- package/src/ColorSchemeToggle/ColorSchemeToggle.test.tsx +80 -0
- package/src/ColorSchemeToggle/ColorSchemeToggle.tsx +107 -0
- package/src/Combobox/Combobox.mdx +71 -0
- package/src/Combobox/Combobox.stories.tsx +245 -0
- package/src/Combobox/Combobox.test.tsx +289 -0
- package/src/Combobox/Combobox.tsx +513 -0
- package/src/Combobox/ComboboxOption.tsx +88 -0
- package/src/Dialog/Dialog.mdx +87 -0
- package/src/Dialog/Dialog.stories.tsx +347 -0
- package/src/Dialog/Dialog.test.tsx +312 -0
- package/src/Dialog/Dialog.tsx +127 -0
- package/src/Field/Field.test.tsx +6 -4
- package/src/Lightbox/Lightbox.mdx +63 -0
- package/src/Lightbox/Lightbox.stories.tsx +133 -0
- package/src/Lightbox/Lightbox.test.tsx +124 -0
- package/src/Lightbox/Lightbox.tsx +143 -0
- package/src/Listbox/Listbox.mdx +68 -0
- package/src/Listbox/Listbox.stories.tsx +205 -0
- package/src/Listbox/Listbox.test.tsx +198 -0
- package/src/Listbox/Listbox.tsx +295 -0
- package/src/Listbox/ListboxOption.tsx +104 -0
- package/src/Menu/Menu.mdx +11 -13
- package/src/Menu/Menu.test.tsx +47 -38
- package/src/Menu/Menu.tsx +81 -142
- package/src/Modal/Modal.mdx +29 -82
- package/src/Modal/Modal.stories.tsx +55 -188
- package/src/Modal/Modal.test.tsx +44 -177
- package/src/Modal/Modal.tsx +54 -204
- package/src/Overlay/OverlayPanel.tsx +195 -0
- package/src/Overlay/OverlayStack.test.tsx +235 -0
- package/src/Overlay/OverlayStack.tsx +132 -0
- package/src/Overlay/SharedBackdrop.tsx +40 -0
- package/src/Overlay/overlayPanelClass.ts +17 -0
- package/src/Overlay/useAnchoredOverlay.ts +197 -0
- package/src/Popover/Popover.mdx +17 -20
- package/src/Popover/Popover.stories.tsx +33 -0
- package/src/Popover/Popover.test.tsx +68 -32
- package/src/Popover/Popover.tsx +56 -95
- package/src/Tooltip/Tooltip.mdx +12 -9
- package/src/Tooltip/Tooltip.test.tsx +17 -19
- package/src/Tooltip/Tooltip.tsx +35 -32
- package/src/icons.storyHelpers.tsx +22 -0
- package/src/index.ts +22 -4
- package/src/mountStory.testHelpers.ts +25 -1
- package/src/sourceRules.test.ts +26 -1
- package/src/storyControls.test.ts +25 -12
- package/dist/Modal/lockScrollBehind.d.ts.map +0 -1
- package/dist/Modal/lockScrollBehind.js.map +0 -1
- /package/dist/{Modal → Overlay}/lockScrollBehind.d.ts +0 -0
- /package/dist/{Modal → Overlay}/lockScrollBehind.js +0 -0
- /package/src/{Modal → Overlay}/lockScrollBehind.ts +0 -0
package/src/Modal/Modal.mdx
CHANGED
|
@@ -6,10 +6,11 @@ import * as ModalStories from "./Modal.stories";
|
|
|
6
6
|
|
|
7
7
|
# Modal
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
The **base layer**: a backdrop, a focus trap, Escape, and an outside press — and no
|
|
10
|
+
chrome. A dialog is one kind of modal; so is a confirmation, a media lightbox, and
|
|
11
|
+
anything else that wants a scrim over the page. `Modal` is that foundation, and
|
|
12
|
+
[`Dialog`](?path=/docs/components-dialog--docs) is `Modal` plus the
|
|
13
|
+
heading/Close/footer/size chrome the component shipped with before the M8 rebuild.
|
|
13
14
|
|
|
14
15
|
<Canvas of={ModalStories.Default} />
|
|
15
16
|
|
|
@@ -17,97 +18,43 @@ from the platform, correctly, in every browser the fleet runs on.
|
|
|
17
18
|
|
|
18
19
|
<Controls />
|
|
19
20
|
|
|
20
|
-
##
|
|
21
|
+
## Portalled, not the top layer
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
`Modal` renders an `OverlayPanel`, which portals to `document.body`. The old `Modal` rode
|
|
24
|
+
the platform top layer (`<dialog>.showModal()`); this one supersedes that
|
|
25
|
+
([decision](../../../docs/decisions/2026-08-03-overlays-portal-to-the-body-not-the-top-layer.md)),
|
|
26
|
+
because the top layer cannot escape an `overflow: hidden` ancestor's clipping in every
|
|
27
|
+
case the fleet hit, and a portal does. The objection a portal used to raise — that it
|
|
28
|
+
moves the node out of a scoped query — is answered by `useRole` writing `aria-controls` /
|
|
29
|
+
`aria-labelledby` across the boundary, and by stories scoping panel queries to
|
|
30
|
+
`within(document.body)`.
|
|
27
31
|
|
|
28
32
|
```tsx
|
|
29
33
|
const { hide, isVisible, show } = useVisibility()
|
|
30
34
|
|
|
31
|
-
<Button onClick={show}>
|
|
35
|
+
<Button onClick={show}>Open</Button>
|
|
32
36
|
|
|
33
|
-
<Modal
|
|
34
|
-
…
|
|
37
|
+
<Modal aria-label="Read error on title 4" isVisible={isVisible} onClose={hide}>
|
|
38
|
+
…your own markup…
|
|
35
39
|
</Modal>
|
|
36
40
|
```
|
|
37
41
|
|
|
38
|
-
`onClose` is every route out —
|
|
39
|
-
|
|
42
|
+
`onClose` is every route out — Escape, a backdrop press — so there is one place to wire
|
|
43
|
+
and no way to end up with the element shut and the state still open.
|
|
40
44
|
|
|
41
|
-
##
|
|
45
|
+
## One accessible name, required
|
|
42
46
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
tidiness:
|
|
47
|
+
A `role="dialog"` takes no name from its content, so pass exactly one of `aria-label` or
|
|
48
|
+
`aria-labelledby`. Missing both is a dev-time warning, not a throw.
|
|
46
49
|
|
|
47
|
-
|
|
48
|
-
> scoped its queries to.** `canvas.getByRole("dialog")` finds this modal. It would not find
|
|
49
|
-
> a portalled one, and every story here would have to reach for `screen` and give up its
|
|
50
|
-
> scoping.
|
|
50
|
+
<Canvas of={ModalStories.Alert} />
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
## Stacking is zero-config, until it is not
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
attribute the browser manages.
|
|
60
|
-
|
|
61
|
-
<Canvas of={ModalStories.AllVariants} />
|
|
62
|
-
|
|
63
|
-
## What a story cannot press
|
|
64
|
-
|
|
65
|
-
`play`'s `userEvent` is `@testing-library/user-event`, which dispatches **untrusted**
|
|
66
|
-
events, and a browser runs a default action only for trusted input. So a synthetic Escape
|
|
67
|
-
does not make a native `<dialog>` fire `cancel` — it does nothing at all, and a story
|
|
68
|
-
asserting "Escape did not close the non-dismissable dialog" would pass for the wrong
|
|
69
|
-
reason forever.
|
|
70
|
-
|
|
71
|
-
The stories dispatch the close request where the browser would raise it instead, which
|
|
72
|
-
splits the contract honestly: keystroke → `cancel` is the platform's and is not ours to
|
|
73
|
-
test; everything downstream of `cancel` is ours and is asserted. `AllStates` pairs it with
|
|
74
|
-
a differential — the dialog that refuses the close request is then closed by its own
|
|
75
|
-
button — because "still open" is also true of a dialog nothing can close at all.
|
|
76
|
-
|
|
77
|
-
<Canvas of={ModalStories.AllStates} />
|
|
78
|
-
|
|
79
|
-
## `isDismissable={false}` cancels, it does not ignore
|
|
80
|
-
|
|
81
|
-
Escape is `preventDefault`ed rather than dropped. Letting the browser close a dialog the
|
|
82
|
-
app said was not dismissable would leave `isVisible` true and the element shut — the exact
|
|
83
|
-
desync this component exists to prevent.
|
|
84
|
-
|
|
85
|
-
## Scroll behind it is locked, and the count is why
|
|
86
|
-
|
|
87
|
-
`showModal()` makes the page `inert` and then lets you scroll it anyway. The lock is a
|
|
88
|
-
counted module-level one rather than a hook, because nested dialogs are legal and a naive
|
|
89
|
-
lock/unlock pair has the inner dialog's cleanup unlock the page while the outer is still
|
|
90
|
-
open. The release is idempotent, because StrictMode runs an effect's cleanup twice.
|
|
91
|
-
|
|
92
|
-
## `dvh`, and the scrim
|
|
93
|
-
|
|
94
|
-
Every size clamps at `85dvh`; `full` takes the lot. `dvh` rather than `vh` because on a
|
|
95
|
-
phone the two differ by the address bar, and a dialog whose footer is under it is a dialog
|
|
96
|
-
with no buttons.
|
|
97
|
-
|
|
98
|
-
`::backdrop` reads `--color-scrim`, which is a **variant** token: `legible` dims at 0.84
|
|
99
|
-
and `daylight` at 0.66, because how hard a direction separates a dialog from the page is
|
|
100
|
-
exactly the sort of thing a visual direction decides. ePaper's is `transparent` — a panel
|
|
101
|
-
with no opacity cannot dim, so it says so instead of faking it. The `Responsive` story
|
|
102
|
-
asserts the computed `::backdrop` colour is really there, since a `var()` that failed to
|
|
103
|
-
inherit would just look like a design choice.
|
|
104
|
-
|
|
105
|
-
<Canvas of={ModalStories.Responsive} />
|
|
106
|
-
|
|
107
|
-
## A modal is not a container-query citizen
|
|
108
|
-
|
|
109
|
-
It gets no `ContainerBoard`. Its size is measured against the viewport by definition — it
|
|
110
|
-
is the thing covering the viewport — so boarding it at three container widths would be
|
|
111
|
-
measuring something with no bearing on it.
|
|
54
|
+
A lone `Modal` renders its own scrim with no setup. Only when modals **stack** — one
|
|
55
|
+
opened from inside another — does the app need an `OverlayStackProvider` at its root, so
|
|
56
|
+
the N of them share **one** scrim, keep the top live, mark the rest `inert`, and dismiss
|
|
57
|
+
top-first. The scroll lock is ref-counted through the stack, so the page frees only when
|
|
58
|
+
the last modal closes.
|
|
112
59
|
|
|
113
60
|
<Canvas of={ModalStories.Interactive} />
|
|
@@ -2,29 +2,16 @@ import { useVisibility } from "@charcuterie/logic"
|
|
|
2
2
|
import type { Meta, StoryObj } from "@storybook/react"
|
|
3
3
|
import type { ReactNode } from "react"
|
|
4
4
|
|
|
5
|
-
import { toStoryChoice } from "../argTypes.storyHelpers.ts"
|
|
6
5
|
import { Button } from "../Button/Button.tsx"
|
|
7
|
-
import { StoryRow } from "../board.storyHelpers.tsx"
|
|
8
|
-
import type { ModalSize } from "./Modal.tsx"
|
|
9
6
|
import { Modal } from "./Modal.tsx"
|
|
10
7
|
|
|
11
8
|
const meta = {
|
|
12
9
|
title: "Components/Modal",
|
|
13
10
|
component: Modal,
|
|
14
11
|
parameters: { layout: "padded" },
|
|
15
|
-
argTypes: {
|
|
16
|
-
size: toStoryChoice([
|
|
17
|
-
"sm",
|
|
18
|
-
"md",
|
|
19
|
-
"lg",
|
|
20
|
-
"xl",
|
|
21
|
-
"full",
|
|
22
|
-
] as const),
|
|
23
|
-
},
|
|
24
12
|
args: {
|
|
25
|
-
headingLevel: 2,
|
|
26
13
|
isDismissable: true,
|
|
27
|
-
|
|
14
|
+
role: "dialog",
|
|
28
15
|
},
|
|
29
16
|
} satisfies Meta<typeof Modal>
|
|
30
17
|
|
|
@@ -33,32 +20,21 @@ export default meta
|
|
|
33
20
|
type Story = StoryObj<typeof meta>
|
|
34
21
|
|
|
35
22
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* boards show instead is the real arrangement — a page, a scrim,
|
|
41
|
-
* and one dialog over both.
|
|
42
|
-
*
|
|
43
|
-
* `footer` is a function of `hide` because a dialog's own buttons
|
|
44
|
-
* are the normal way out of it, and they need the same `hide` the
|
|
45
|
-
* close button gets. One `useVisibility` per demo, no second source
|
|
46
|
-
* of truth.
|
|
23
|
+
* The base layer has no chrome, so a demo supplies its own — the
|
|
24
|
+
* point being that `Modal` is the backdrop-plus-dismiss-plus-portal
|
|
25
|
+
* foundation and the content is entirely the caller's. `Dialog` is
|
|
26
|
+
* the batteries-included version of exactly this.
|
|
47
27
|
*/
|
|
48
28
|
const ModalDemo = ({
|
|
49
29
|
buttonLabel,
|
|
50
30
|
children,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
isDismissable = true,
|
|
54
|
-
size,
|
|
31
|
+
label,
|
|
32
|
+
role,
|
|
55
33
|
}: {
|
|
56
34
|
buttonLabel: string
|
|
57
35
|
children?: ReactNode
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
isDismissable?: boolean
|
|
61
|
-
size?: ModalSize
|
|
36
|
+
label: string
|
|
37
|
+
role?: "alertdialog" | "dialog"
|
|
62
38
|
}): ReactNode => {
|
|
63
39
|
const { hide, isVisible, show } = useVisibility()
|
|
64
40
|
|
|
@@ -69,21 +45,29 @@ const ModalDemo = ({
|
|
|
69
45
|
</Button>
|
|
70
46
|
|
|
71
47
|
<Modal
|
|
72
|
-
|
|
73
|
-
heading={heading}
|
|
74
|
-
isDismissable={isDismissable}
|
|
48
|
+
aria-label={label}
|
|
75
49
|
isVisible={isVisible}
|
|
76
50
|
onClose={hide}
|
|
77
|
-
|
|
51
|
+
role={role}
|
|
78
52
|
>
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
53
|
+
<div className="flex flex-col gap-3 p-4">
|
|
54
|
+
{children ?? (
|
|
55
|
+
<p className="text-content-secondary text-sm">
|
|
56
|
+
A bare `Modal`: a scrim, a focus trap, Escape,
|
|
57
|
+
and an outside press. Everything you see
|
|
58
|
+
inside is the caller's own markup.
|
|
59
|
+
</p>
|
|
60
|
+
)}
|
|
61
|
+
|
|
62
|
+
<Button
|
|
63
|
+
appearance="soft"
|
|
64
|
+
intent="neutral"
|
|
65
|
+
onClick={hide}
|
|
66
|
+
size="sm"
|
|
67
|
+
>
|
|
68
|
+
Close
|
|
69
|
+
</Button>
|
|
70
|
+
</div>
|
|
87
71
|
</Modal>
|
|
88
72
|
</>
|
|
89
73
|
)
|
|
@@ -91,182 +75,65 @@ const ModalDemo = ({
|
|
|
91
75
|
|
|
92
76
|
export const Default: Story = {
|
|
93
77
|
args: {
|
|
94
|
-
|
|
78
|
+
"aria-label": "Read error on title 4",
|
|
79
|
+
children: null,
|
|
95
80
|
isVisible: false,
|
|
96
81
|
onClose: () => {},
|
|
97
82
|
},
|
|
98
83
|
render: () => (
|
|
99
84
|
<ModalDemo
|
|
100
|
-
buttonLabel="
|
|
101
|
-
|
|
85
|
+
buttonLabel="Open the base modal"
|
|
86
|
+
label="Read error on title 4"
|
|
102
87
|
/>
|
|
103
88
|
),
|
|
104
89
|
}
|
|
105
90
|
|
|
106
|
-
const SIZES: ModalSize[] = ["sm", "md", "lg", "xl", "full"]
|
|
107
|
-
|
|
108
|
-
export const AllVariants: Story = {
|
|
109
|
-
args: {
|
|
110
|
-
heading: "Bay 3",
|
|
111
|
-
isVisible: false,
|
|
112
|
-
onClose: () => {},
|
|
113
|
-
},
|
|
114
|
-
render: () => (
|
|
115
|
-
<StoryRow>
|
|
116
|
-
{SIZES.map((size) => (
|
|
117
|
-
<ModalDemo
|
|
118
|
-
buttonLabel={`Open ${size}`}
|
|
119
|
-
heading={`Read error — ${size}`}
|
|
120
|
-
key={size}
|
|
121
|
-
size={size}
|
|
122
|
-
/>
|
|
123
|
-
))}
|
|
124
|
-
</StoryRow>
|
|
125
|
-
),
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export const AllStates: Story = {
|
|
129
|
-
args: {
|
|
130
|
-
heading: "Bay 3",
|
|
131
|
-
isVisible: false,
|
|
132
|
-
onClose: () => {},
|
|
133
|
-
},
|
|
134
|
-
render: () => (
|
|
135
|
-
<StoryRow>
|
|
136
|
-
<ModalDemo
|
|
137
|
-
buttonLabel="With a footer"
|
|
138
|
-
footer={(hide) => (
|
|
139
|
-
<>
|
|
140
|
-
<Button
|
|
141
|
-
appearance="soft"
|
|
142
|
-
intent="neutral"
|
|
143
|
-
onClick={hide}
|
|
144
|
-
size="sm"
|
|
145
|
-
>
|
|
146
|
-
Keep going
|
|
147
|
-
</Button>
|
|
148
|
-
|
|
149
|
-
<Button intent="danger" size="sm">
|
|
150
|
-
Stop the rip
|
|
151
|
-
</Button>
|
|
152
|
-
</>
|
|
153
|
-
)}
|
|
154
|
-
heading="Stop the rip?"
|
|
155
|
-
/>
|
|
156
|
-
|
|
157
|
-
<ModalDemo
|
|
158
|
-
buttonLabel="Not dismissable"
|
|
159
|
-
footer={(hide) => (
|
|
160
|
-
<>
|
|
161
|
-
<Button
|
|
162
|
-
appearance="soft"
|
|
163
|
-
intent="neutral"
|
|
164
|
-
onClick={hide}
|
|
165
|
-
size="sm"
|
|
166
|
-
>
|
|
167
|
-
Keep the titles
|
|
168
|
-
</Button>
|
|
169
|
-
|
|
170
|
-
<Button intent="danger" size="sm">
|
|
171
|
-
Erase and re-rip
|
|
172
|
-
</Button>
|
|
173
|
-
</>
|
|
174
|
-
)}
|
|
175
|
-
heading="This erases eight completed titles"
|
|
176
|
-
isDismissable={false}
|
|
177
|
-
/>
|
|
178
|
-
|
|
179
|
-
<ModalDemo
|
|
180
|
-
buttonLabel="Long body"
|
|
181
|
-
heading="MakeMKV log"
|
|
182
|
-
>
|
|
183
|
-
<div className="flex flex-col gap-2">
|
|
184
|
-
{Array.from(
|
|
185
|
-
{ length: 24 },
|
|
186
|
-
(_, index) =>
|
|
187
|
-
`14:0${index % 10}:22 title ${
|
|
188
|
-
index + 1
|
|
189
|
-
} AACS handshake complete, reading…`,
|
|
190
|
-
).map((line) => (
|
|
191
|
-
<p
|
|
192
|
-
className="font-mono text-content-secondary text-xs"
|
|
193
|
-
key={line}
|
|
194
|
-
>
|
|
195
|
-
{line}
|
|
196
|
-
</p>
|
|
197
|
-
))}
|
|
198
|
-
</div>
|
|
199
|
-
</ModalDemo>
|
|
200
|
-
</StoryRow>
|
|
201
|
-
),
|
|
202
|
-
}
|
|
203
|
-
|
|
204
91
|
/**
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
* covering the viewport — and boarding it at three container widths
|
|
209
|
-
* would be measuring something that has no bearing on it.
|
|
210
|
-
*
|
|
211
|
-
* What does vary is the clamp: every size caps at `85dvh`, and
|
|
212
|
-
* `full` takes the lot. `dvh` rather than `vh` because on a phone
|
|
213
|
-
* the two differ by the address bar, and a dialog whose footer is
|
|
214
|
-
* underneath it is a dialog with no buttons.
|
|
92
|
+
* `role="alertdialog"` is the one configuration the base exposes that
|
|
93
|
+
* `Dialog` does not surface directly — an interruption the user has
|
|
94
|
+
* to answer, announced as an alert rather than a plain dialog.
|
|
215
95
|
*/
|
|
216
|
-
export const
|
|
96
|
+
export const Alert: Story = {
|
|
217
97
|
args: {
|
|
218
|
-
|
|
98
|
+
"aria-label": "Confirm erase",
|
|
99
|
+
children: null,
|
|
219
100
|
isVisible: false,
|
|
220
101
|
onClose: () => {},
|
|
221
102
|
},
|
|
222
103
|
render: () => (
|
|
223
104
|
<ModalDemo
|
|
224
|
-
buttonLabel="Open
|
|
225
|
-
|
|
226
|
-
|
|
105
|
+
buttonLabel="Open an alertdialog"
|
|
106
|
+
label="Confirm erase"
|
|
107
|
+
role="alertdialog"
|
|
227
108
|
>
|
|
228
109
|
<p className="text-content-secondary text-sm">
|
|
229
|
-
|
|
230
|
-
the screen, so there is nothing behind it worth
|
|
231
|
-
showing.
|
|
110
|
+
This erases eight completed titles.
|
|
232
111
|
</p>
|
|
233
112
|
</ModalDemo>
|
|
234
113
|
),
|
|
235
114
|
}
|
|
236
115
|
|
|
237
116
|
/**
|
|
238
|
-
* The keyboard contract, driven. `
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
* `onCancel` → `hide()` → the effect → `close()`, rather than the
|
|
242
|
-
* browser closing the dialog behind the app's back.
|
|
117
|
+
* The keyboard contract, driven. `OverlayPanel`'s
|
|
118
|
+
* `FloatingFocusManager` traps focus and restores it to the trigger;
|
|
119
|
+
* `useDismiss` routes Escape and an outside press through `onClose`.
|
|
243
120
|
*/
|
|
244
121
|
export const Interactive: Story = {
|
|
245
122
|
args: {
|
|
246
|
-
|
|
123
|
+
"aria-label": "Stop the rip?",
|
|
124
|
+
children: null,
|
|
247
125
|
isVisible: false,
|
|
248
126
|
onClose: () => {},
|
|
249
127
|
},
|
|
250
128
|
render: () => (
|
|
251
129
|
<ModalDemo
|
|
252
130
|
buttonLabel="Stop the rip"
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
>
|
|
261
|
-
Keep going
|
|
262
|
-
</Button>
|
|
263
|
-
|
|
264
|
-
<Button intent="danger" size="sm">
|
|
265
|
-
Stop it now
|
|
266
|
-
</Button>
|
|
267
|
-
</>
|
|
268
|
-
)}
|
|
269
|
-
heading="Stop the rip?"
|
|
270
|
-
/>
|
|
131
|
+
label="Stop the rip?"
|
|
132
|
+
>
|
|
133
|
+
<p className="text-content-secondary text-sm">
|
|
134
|
+
Escape, an outside press, or the button below all
|
|
135
|
+
land on the same `onClose`.
|
|
136
|
+
</p>
|
|
137
|
+
</ModalDemo>
|
|
271
138
|
),
|
|
272
139
|
}
|