@edux-design/popovers 1.0.6 → 1.1.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.mjs CHANGED
@@ -2,7 +2,14 @@
2
2
  import React, { forwardRef } from "react";
3
3
  import * as PopoverPrimitive from "@radix-ui/react-popover";
4
4
  import { cx } from "@edux-design/utils";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
5
6
  var POPOVER_Z_INDEX = 2147483646;
7
+ var POPOVER_POSITIONING_CLASSES = {
8
+ top: "-translate-y-(--motion-distance-xs)",
9
+ right: "translate-x-(--motion-distance-xs)",
10
+ bottom: "translate-y-(--motion-distance-xs)",
11
+ left: "-translate-x-(--motion-distance-xs)"
12
+ };
6
13
  var Popover = PopoverPrimitive.Root;
7
14
  var PopoverTrigger = PopoverPrimitive.Trigger;
8
15
  var PopoverAnchor = PopoverPrimitive.Anchor;
@@ -24,14 +31,8 @@ var PopoverContent = forwardRef(
24
31
  contentProps = {},
25
32
  ...restProps
26
33
  }, ref) => {
27
- const base = "bg-fg-base text-fg-invert text-sm font-normal rounded-xl shadow-lg px-12 py-8 animate-fade-in";
28
- const positioning = {
29
- top: "translate-y-[-4px]",
30
- right: "translate-x-[4px]",
31
- bottom: "translate-y-[4px]",
32
- left: "translate-x-[-4px]"
33
- }[side];
34
- return /* @__PURE__ */ React.createElement(PopoverPortal, { container, ...portalProps }, /* @__PURE__ */ React.createElement(
34
+ const positioning = POPOVER_POSITIONING_CLASSES[side];
35
+ return /* @__PURE__ */ jsx(PopoverPortal, { container, ...portalProps, children: /* @__PURE__ */ jsxs(
35
36
  PopoverPrimitive.Content,
36
37
  {
37
38
  ref,
@@ -44,23 +45,167 @@ var PopoverContent = forwardRef(
44
45
  className: cx(positioning, className, "relative"),
45
46
  style: { zIndex: POPOVER_Z_INDEX, ...style },
46
47
  ...contentProps,
47
- ...restProps
48
- },
49
- children,
50
- showArrow && /* @__PURE__ */ React.createElement(
51
- PopoverPrimitive.Arrow,
52
- {
53
- className: "fill-fg-base",
54
- width: 12,
55
- height: 8
56
- }
57
- )
58
- ));
48
+ ...restProps,
49
+ children: [
50
+ children,
51
+ showArrow && /* @__PURE__ */ jsx(
52
+ PopoverPrimitive.Arrow,
53
+ {
54
+ className: "fill-fg-base",
55
+ width: 12,
56
+ height: 8
57
+ }
58
+ )
59
+ ]
60
+ }
61
+ ) });
59
62
  }
60
63
  );
61
64
  PopoverContent.displayName = "PopoverContent";
62
65
  var PopoverClose = PopoverPrimitive.Close;
66
+
67
+ // src/elements/ConfirmationPopover.tsx
68
+ import { forwardRef as forwardRef2, useId, useRef, useState } from "react";
69
+ import { Button } from "@edux-design/buttons";
70
+ import { cx as cx2 } from "@edux-design/utils";
71
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
72
+ var ConfirmationPopover = forwardRef2(function ConfirmationPopover2({
73
+ open,
74
+ defaultOpen = false,
75
+ onOpenChange,
76
+ title,
77
+ description,
78
+ children,
79
+ trigger,
80
+ onConfirm,
81
+ onCancel,
82
+ confirmLabel = "Confirm",
83
+ cancelLabel = "Cancel",
84
+ pendingLabel = "Working\u2026",
85
+ pending = false,
86
+ error,
87
+ confirmDisabled = false,
88
+ cancelDisabled = false,
89
+ dismissible = true,
90
+ returnFocusRef,
91
+ isDanger = false,
92
+ allowDismissWhilePending = false,
93
+ modal = true,
94
+ anchor,
95
+ contentProps = {},
96
+ portalProps,
97
+ confirmButtonProps = {},
98
+ cancelButtonProps = {}
99
+ }, ref) {
100
+ const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);
101
+ const resolvedOpen = open ?? uncontrolledOpen;
102
+ const cancelRef = useRef(null);
103
+ const titleId = useId();
104
+ const descriptionId = useId();
105
+ const locked = !dismissible || pending && !allowDismissWhilePending;
106
+ const {
107
+ onClick: onCancelClick,
108
+ ref: actionRef,
109
+ ...cancelProps
110
+ } = cancelButtonProps;
111
+ const { onClick: onConfirmClick, ...confirmProps } = confirmButtonProps;
112
+ const requestOpen = (next) => {
113
+ if (!next && locked) return;
114
+ if (open === void 0) setUncontrolledOpen(next);
115
+ onOpenChange?.(next);
116
+ };
117
+ return /* @__PURE__ */ jsxs2(Popover, { open: resolvedOpen, onOpenChange: requestOpen, modal, children: [
118
+ anchor && /* @__PURE__ */ jsx2(PopoverAnchor, { asChild: true, children: anchor }),
119
+ /* @__PURE__ */ jsx2(PopoverTrigger, { asChild: true, children: trigger }),
120
+ /* @__PURE__ */ jsxs2(
121
+ PopoverContent,
122
+ {
123
+ side: "bottom",
124
+ align: "end",
125
+ ...contentProps,
126
+ ref,
127
+ portalProps,
128
+ "aria-labelledby": contentProps["aria-labelledby"] ?? titleId,
129
+ "aria-describedby": contentProps["aria-describedby"] ?? (description ? descriptionId : void 0),
130
+ "aria-busy": pending || void 0,
131
+ className: cx2(
132
+ "w-[min(320px,calc(100vw-32px))] max-h-[var(--radix-popover-content-available-height)] overflow-auto rounded-md border-2 border-border-subtle bg-bg-base p-16 text-sm text-fg-base shadow-lg motion-reduce:animate-none",
133
+ contentProps.className
134
+ ),
135
+ onOpenAutoFocus: (event) => {
136
+ contentProps.onOpenAutoFocus?.(event);
137
+ if (!event.defaultPrevented) {
138
+ event.preventDefault();
139
+ if (!locked && !cancelDisabled) cancelRef.current?.focus();
140
+ else if (event.target instanceof HTMLElement) event.target.focus();
141
+ }
142
+ },
143
+ onCloseAutoFocus: (event) => {
144
+ contentProps.onCloseAutoFocus?.(event);
145
+ if (!event.defaultPrevented && returnFocusRef?.current?.isConnected) {
146
+ event.preventDefault();
147
+ returnFocusRef.current.focus();
148
+ }
149
+ },
150
+ onEscapeKeyDown: (event) => {
151
+ contentProps.onEscapeKeyDown?.(event);
152
+ if (locked) event.preventDefault();
153
+ },
154
+ onInteractOutside: (event) => {
155
+ contentProps.onInteractOutside?.(event);
156
+ if (locked) event.preventDefault();
157
+ },
158
+ children: [
159
+ /* @__PURE__ */ jsx2("h2", { id: titleId, className: "text-base font-semibold text-fg-base", children: title }),
160
+ description && /* @__PURE__ */ jsx2("p", { id: descriptionId, className: "mt-8 text-sm text-fg-subtle", children: description }),
161
+ children && /* @__PURE__ */ jsx2("div", { className: "mt-12", children }),
162
+ error && /* @__PURE__ */ jsx2("div", { role: "alert", className: "mt-8 text-fg-danger-emphasis", children: error }),
163
+ pending && /* @__PURE__ */ jsx2("p", { role: "status", className: "sr-only", children: pendingLabel }),
164
+ /* @__PURE__ */ jsxs2("div", { className: "mt-16 flex flex-wrap justify-end gap-8", children: [
165
+ /* @__PURE__ */ jsx2(
166
+ Button,
167
+ {
168
+ ...cancelProps,
169
+ ref: (node) => {
170
+ cancelRef.current = node;
171
+ if (typeof actionRef === "function") actionRef(node);
172
+ else if (actionRef) actionRef.current = node;
173
+ },
174
+ type: "button",
175
+ size: "small",
176
+ isSecondary: true,
177
+ disabled: locked || cancelDisabled,
178
+ onClick: (event) => {
179
+ onCancelClick?.(event);
180
+ if (!event.defaultPrevented) onCancel?.(event);
181
+ if (!event.defaultPrevented) requestOpen(false);
182
+ },
183
+ children: cancelLabel
184
+ }
185
+ ),
186
+ /* @__PURE__ */ jsx2(
187
+ Button,
188
+ {
189
+ ...confirmProps,
190
+ type: "button",
191
+ size: "small",
192
+ isDanger,
193
+ disabled: pending || confirmDisabled,
194
+ onClick: (event) => {
195
+ onConfirmClick?.(event);
196
+ if (!event.defaultPrevented) onConfirm(event);
197
+ },
198
+ children: pending ? pendingLabel : confirmLabel
199
+ }
200
+ )
201
+ ] })
202
+ ]
203
+ }
204
+ )
205
+ ] });
206
+ });
63
207
  export {
208
+ ConfirmationPopover,
64
209
  Popover,
65
210
  PopoverAnchor,
66
211
  PopoverClose,
@@ -0,0 +1,118 @@
1
+ # ConfirmationPopover
2
+
3
+ Import `ConfirmationPopover` and `ConfirmationPopoverProps` from `@edux-design/popovers`.
4
+ It composes the existing Popover root/trigger/anchor/content so it retains portalling,
5
+ placement, offsets, collision handling and public ref/event behaviour.
6
+
7
+ ```tsx
8
+ <ConfirmationPopover
9
+ open={activeConfirmation === row.id}
10
+ onOpenChange={(next) => setActiveConfirmation(next ? row.id : null)}
11
+ title="Delete this question?"
12
+ description="The question will be removed from this group."
13
+ trigger={
14
+ <IconButton
15
+ aria-label="Delete question"
16
+ icon={<Trash />}
17
+ isBare
18
+ isDanger
19
+ size="small"
20
+ onMouseDown={(event) => event.preventDefault()}
21
+ />
22
+ }
23
+ confirmLabel="Delete question"
24
+ isDanger
25
+ onConfirm={handleDelete}
26
+ confirmButtonProps={{
27
+ onMouseDown: (event) => event.preventDefault(),
28
+ onClick: (event) => event.stopPropagation(),
29
+ }}
30
+ cancelButtonProps={{
31
+ onMouseDown: (event) => event.preventDefault(),
32
+ onClick: (event) => event.stopPropagation(),
33
+ }}
34
+ contentProps={{ side: "bottom", align: "end", collisionPadding: 16 }}
35
+ />
36
+ ```
37
+
38
+ `trigger` is required: pass one ref-forwarding element; `PopoverTrigger asChild`
39
+ preserves its original handlers/ref. `anchor` optionally supplies another
40
+ ref-forwarding positioning element. `modal` defaults to `true`, containing keyboard
41
+ focus and blocking outside pointer interaction using the underlying Radix root.
42
+ Set `modal={false}` only when a non-modal contextual workflow is intended; it permits
43
+ focus to leave. Outside focus dismisses that non-modal presentation unless locked
44
+ or vetoed. Idle Escape and Cancel close in both modes. The trigger normally receives
45
+ focus after modal closure; `returnFocusRef` and preventable `onCloseAutoFocus` cover
46
+ removed rows and explicit host editor focus.
47
+
48
+ Use `contentProps` for `side`, `align`, offsets, collision settings and native event
49
+ hooks; defaults are bottom/end with the existing Popover gap. `portalProps` forwards
50
+ the existing Radix Portal options. The responsive surface is at most 320px wide,
51
+ leaves 16px at viewport edges, and scrolls within available Popover height.
52
+ The generic composition adds no editor selection suppression or row drag policy;
53
+ pass native trigger/action handlers when an adapter needs those behaviours.
54
+
55
+ Both confirmations leave mutation policy in the host. They never await, interpret,
56
+ catch, or auto-close after `onConfirm`: synchronous commands, promises, false results,
57
+ blocking document errors and retriable service errors can keep their own lifecycle.
58
+ An async handler must catch its own rejection and update its own `pending`/`error`.
59
+ Nothing schedules asynchronous component state after the callback.
60
+
61
+ `open` is authoritative when supplied. Omit it and use `defaultOpen` for an
62
+ uncontrolled presentation; `onOpenChange(next)` reports user requests in either mode.
63
+ Confirm does not close even in uncontrolled mode: use controlled state when success
64
+ must close the presentation. `onCancel(event)` only reports the Cancel button;
65
+ Escape and outside dismissal report through `onOpenChange`, without pretending to
66
+ be a Cancel activation.
67
+
68
+ Actions use Edux `Button`, default to `type="button"`, and wrap on narrow surfaces.
69
+ The default confirm action is neutral. Set `isDanger` for destructive actions.
70
+ `confirmDisabled` and `cancelDisabled` independently disable their respective
71
+ buttons. An error renders a `role="alert"`; an error alone does not disable retry.
72
+ For a stale editor/document error, the host also sets `confirmDisabled`.
73
+
74
+ While `pending`, both actions are disabled, the surface has `aria-busy`, and
75
+ `pendingLabel` (default “Working…”) replaces the confirm text and is exposed as a
76
+ status. Escape, outside pointer/focus dismissal, Cancel, and trigger toggle requests
77
+ cannot close the presentation. `allowDismissWhilePending` explicitly unlocks user
78
+ cancellation while confirm remains disabled; use it only when the host permits that
79
+ policy. `dismissible={false}` locks user dismissal regardless of pending. A controlled
80
+ host can always set `open={false}`, for example after confirmed success. There is no
81
+ implicit header X action to bypass these rules.
82
+
83
+ Safe initial focus goes to Cancel. If Cancel is unavailable, focus stays on the
84
+ surface rather than choosing the destructive action. Native `contentProps`
85
+ `onOpenAutoFocus`/`onCloseAutoFocus` can call `event.preventDefault()` to replace the
86
+ focus policy; these hooks run first. `returnFocusRef` supplies an explicit return
87
+ target when the originating row might disappear. For an editor command that places
88
+ its own selection/focus, prevent close autofocus so automatic restoration cannot
89
+ undo that command. A missing or disconnected return target is not focused.
90
+
91
+ `contentProps` forwards native attributes, style/className, keyboard/pointer events,
92
+ and Radix focus/dismissal hooks. The surface ref is an `HTMLDivElement`.
93
+ `confirmButtonProps`/`cancelButtonProps` forward native button attributes, refs and
94
+ mouse/keyboard handlers. Their click handlers run before `onConfirm`/`onCancel`;
95
+ `preventDefault()` vetoes the corresponding action, and `stopPropagation()` remains
96
+ available for editable/sortable row adapters. Forced action type, disabled state and
97
+ pending guards take precedence. Caller dismissal hooks run first, then the internal
98
+ lock always prevents dismissal while locked; a handler cannot accidentally undo it.
99
+
100
+ Each surface is a labelled `dialog`, with a meaningful description when provided.
101
+ Confirmation is a decision, so it uses ordinary buttons rather than menu navigation.
102
+ It does not automatically claim urgent `alertdialog` semantics. A caller may set
103
+ `contentProps.role="alertdialog"` when that semantic is appropriate. Error messages
104
+ have alert semantics separately. IDs remain unique across instances.
105
+
106
+ Use current semantic surface/foreground/border/action/danger tokens. There are no
107
+ new tokens or package-owned palettes, and theme switching does not change geometry.
108
+ Pass `portalProps.container` when a brand/mode scope is below the document root;
109
+ put `brand--blue`/`brand--teal` and `theme--light`/`theme--dark` on that same scope.
110
+ Default portals inherit the application root theme. Native reduced-motion rules
111
+ apply; the compositions also disable their surface animation for reduced motion.
112
+ The primitives keep collision/viewport bounds, content scrolls, and footer actions
113
+ wrap. Domain checks, permissions, editor resolver registration, persistence, source
114
+ warnings, server mutation and cancellation remain local.
115
+
116
+ Storybook: `Popovers/ConfirmationPopover` includes the shared confirmation cases and
117
+ an editable/sortable-style row proving action propagation stays under host control.
118
+ Component tests use real Radix interaction. Public type/runtime fixtures live under `test/`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edux-design/popovers",
3
- "version": "1.0.6",
3
+ "version": "1.1.0",
4
4
  "private": false,
5
5
  "sideEffects": [
6
6
  "**/*.css"
@@ -13,25 +13,21 @@
13
13
  "require": "./dist/index.js"
14
14
  }
15
15
  },
16
- "scripts": {
17
- "lint": "eslint . --max-warnings 0",
18
- "build": "tsup src/index.js --format esm,cjs --external react,react-dom,@radix-ui/react-popover",
19
- "dev": "tsup src/index.js --format esm,cjs --external react,react-dom,@radix-ui/react-popover",
20
- "generate:component": "turbo gen react-component",
21
- "check-types": "tsc --noEmit"
22
- },
23
16
  "peerDependencies": {
24
17
  "react": ">=18",
25
18
  "react-dom": ">=18",
26
19
  "@edux-design/utils": "*",
27
20
  "@radix-ui/react-popover": "1.1.15"
28
21
  },
29
- "devDependencies": {
30
- "typescript": "^5.9.2",
31
- "tailwindcss": "^4.1.12",
32
- "postcss": "^8.5.6",
33
- "tsup": "^8.5.0",
34
- "@tailwindcss/cli": "^4.1.12",
35
- "@tailwindcss/postcss": "^4.1.12"
22
+ "files": [
23
+ "dist",
24
+ "docs",
25
+ "README.md",
26
+ "CHANGELOG.md",
27
+ "LICENSE",
28
+ "LICENSE.md"
29
+ ],
30
+ "dependencies": {
31
+ "@edux-design/buttons": "*"
36
32
  }
37
33
  }
@@ -1,248 +0,0 @@
1
- import React, { useState } from "react";
2
- import {
3
- Popover,
4
- PopoverTrigger,
5
- PopoverContent,
6
- PopoverClose,
7
- } from "./../index";
8
- import { Button } from "@edux-design/buttons";
9
- import { cx } from "@edux-design/utils";
10
-
11
- const meta = {
12
- title: "Components/Popover",
13
- component: Popover,
14
- parameters: {
15
- layout: "centered",
16
- docs: {
17
- description: {
18
- component: `
19
- Radix-based **Popover** component.
20
-
21
- Use for contextual overlays triggered by a click, such as menus, mini-editors, or form helpers.
22
-
23
- Built with [@radix-ui/react-popover](https://www.radix-ui.com/primitives/docs/components/popover).
24
- `,
25
- },
26
- },
27
- },
28
- argTypes: {
29
- side: {
30
- control: "select",
31
- options: ["top", "right", "bottom", "left"],
32
- description: "Placement of the popover relative to its trigger.",
33
- table: { defaultValue: { summary: "bottom" } },
34
- },
35
- align: {
36
- control: "select",
37
- options: ["start", "center", "end"],
38
- description: "Alignment of the popover along the cross axis.",
39
- table: { defaultValue: { summary: "center" } },
40
- },
41
- sideOffset: {
42
- control: { type: "number", min: 0, step: 1 },
43
- description: "Gap between the trigger and popover.",
44
- table: { defaultValue: { summary: 8 } },
45
- },
46
- },
47
- };
48
- export default meta;
49
- /**
50
- * ───────────────────────────────────────────────
51
- * Basic Popover
52
- * ───────────────────────────────────────────────
53
- */
54
- export const Basic = {
55
- render: (args) => (
56
- <Popover>
57
- <PopoverTrigger asChild>
58
- <Button variant="primary">Open Popover</Button>
59
- </PopoverTrigger>
60
-
61
- <PopoverContent {...args}>
62
- <div className="space-y-4 w-[200px]">
63
- <p>This is a simple popover content.</p>
64
- <PopoverClose asChild>
65
- <Button variant="secondary" size="sm">
66
- Close
67
- </Button>
68
- </PopoverClose>
69
- </div>
70
- </PopoverContent>
71
- </Popover>
72
- ),
73
- args: {
74
- side: "bottom",
75
- align: "center",
76
- sideOffset: 8,
77
- },
78
- parameters: {
79
- docs: {
80
- description: {
81
- story: "A basic popover triggered by a button click.",
82
- },
83
- },
84
- },
85
- };
86
-
87
- /**
88
- * ───────────────────────────────────────────────
89
- * Custom Content
90
- * ───────────────────────────────────────────────
91
- */
92
- export const CustomContent = {
93
- render: (args) => (
94
- <Popover>
95
- <PopoverTrigger asChild>
96
- <Button variant="primary">Show Custom Popover</Button>
97
- </PopoverTrigger>
98
- <PopoverContent
99
- {...args}
100
- className={cx(
101
- "rounded-2xl border border-fg-subtle bg-fg-base p-16 w-[240px]",
102
- "shadow-lg"
103
- )}
104
- >
105
- <h3 className="font-semibold text-base mb-4">Popover title</h3>
106
- <p className="text-sm text-fg-subtle">
107
- You can render **anything** inside here — forms, menus, etc.
108
- </p>
109
- <div className="mt-8 flex justify-end">
110
- <PopoverClose asChild>
111
- <Button variant="bare" size="sm">
112
- Dismiss
113
- </Button>
114
- </PopoverClose>
115
- </div>
116
- </PopoverContent>
117
- </Popover>
118
- ),
119
- args: {
120
- side: "top",
121
- },
122
- parameters: {
123
- docs: {
124
- description: {
125
- story:
126
- "Demonstrates custom content styling, with title, text, and custom buttons.",
127
- },
128
- },
129
- },
130
- };
131
-
132
- /**
133
- * ───────────────────────────────────────────────
134
- * Controlled Popover
135
- * ───────────────────────────────────────────────
136
- */
137
- export const Controlled = {
138
- render: (args) => {
139
- const [open, setOpen] = useState(false);
140
- return (
141
- <Popover open={open} onOpenChange={setOpen}>
142
- <PopoverTrigger asChild>
143
- <Button variant="primary">
144
- {open ? "Close Popover" : "Open Controlled Popover"}
145
- </Button>
146
- </PopoverTrigger>
147
- <PopoverContent {...args}>
148
- <div className="w-[220px] text-sm">
149
- <p>This popover is **controlled** externally using React state.</p>
150
- <div className="mt-6 flex justify-end">
151
- <Button variant="bare" size="sm" onClick={() => setOpen(false)}>
152
- Close
153
- </Button>
154
- </div>
155
- </div>
156
- </PopoverContent>
157
- </Popover>
158
- );
159
- },
160
- parameters: {
161
- docs: {
162
- description: {
163
- story:
164
- "Shows how to control the popover’s open state externally via React state.",
165
- },
166
- },
167
- },
168
- };
169
-
170
- /**
171
- * ───────────────────────────────────────────────
172
- * All Sides Example
173
- * ───────────────────────────────────────────────
174
- */
175
- export const Sides = {
176
- render: () => (
177
- <div className="grid grid-cols-2 gap-16">
178
- {["top", "right", "bottom", "left"].map((side) => (
179
- <Popover key={side}>
180
- <PopoverTrigger asChild>
181
- <Button variant="secondary">Side: {side}</Button>
182
- </PopoverTrigger>
183
- <PopoverContent side={side}>
184
- <p className="w-[140px] text-center">
185
- Popover on <strong>{side}</strong>
186
- </p>
187
- </PopoverContent>
188
- </Popover>
189
- ))}
190
- </div>
191
- ),
192
- parameters: {
193
- docs: {
194
- description: {
195
- story: "Showcases popovers appearing on all sides of the trigger.",
196
- },
197
- },
198
- },
199
- };
200
-
201
- /**
202
- * ───────────────────────────────────────────────
203
- * No Auto Focus Example
204
- * ───────────────────────────────────────────────
205
- */
206
- export const NoAutoFocus = {
207
- render: (args) => (
208
- <Popover>
209
- <PopoverTrigger asChild>
210
- <Button variant="primary">Open Non-Focusing Popover</Button>
211
- </PopoverTrigger>
212
-
213
- <PopoverContent
214
- {...args}
215
- onOpenAutoFocus={(event) => {
216
- // Prevent Radix from automatically moving focus into the popover
217
- event.preventDefault();
218
- }}
219
- trapFocus={false}
220
- disableOutsidePointerEvents={false}
221
- className="w-[220px]"
222
- >
223
- <p className="text-sm mb-4">
224
- This popover <strong>does not steal focus</strong>. The trigger (or
225
- other input) remains focused.
226
- </p>
227
- <PopoverClose asChild>
228
- <Button variant="secondary" size="sm">
229
- Close
230
- </Button>
231
- </PopoverClose>
232
- </PopoverContent>
233
- </Popover>
234
- ),
235
- args: {
236
- side: "bottom",
237
- align: "center",
238
- sideOffset: 8,
239
- },
240
- parameters: {
241
- docs: {
242
- description: {
243
- story:
244
- "Demonstrates disabling Radix’s default auto-focus behaviour using `onOpenAutoFocus={(e) => e.preventDefault()}`. The editor or trigger element retains focus when the popover opens.",
245
- },
246
- },
247
- },
248
- };