@manhphi1309/dialog 0.1.2 → 0.2.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/README.md +241 -12
- package/dist/index.cjs +354 -2
- package/dist/index.d.cts +303 -13
- package/dist/index.d.mts +303 -13
- package/dist/index.mjs +346 -3
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
# @manhphi1309/dialog
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A fully-accessible dialog package built on top of [Radix UI Dialog](https://www.radix-ui.com/primitives/docs/components/dialog). Ships two complete component families:
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
- **`Dialog*`** — standard modal dialog, always renders as a centered overlay
|
|
6
|
+
- **`ResponsiveDialog*`** — adaptive wrapper that switches to a bottom `Drawer` on mobile screens (< 768 px)
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
## Dependencies
|
|
10
|
-
|
|
11
|
-
- `@manhphi1309/utils`
|
|
12
|
-
- `class-variance-authority`
|
|
8
|
+
---
|
|
13
9
|
|
|
14
10
|
## Installation
|
|
15
11
|
|
|
@@ -17,12 +13,245 @@ A custom dialog component for the shadcn-custom monorepo.
|
|
|
17
13
|
npm install @manhphi1309/dialog
|
|
18
14
|
```
|
|
19
15
|
|
|
20
|
-
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Dependencies
|
|
19
|
+
|
|
20
|
+
| Package | Role |
|
|
21
|
+
| --------------------- | --------------------------------------------- |
|
|
22
|
+
| `@manhphi1309/button` | Close / action buttons inside dialogs |
|
|
23
|
+
| `@manhphi1309/drawer` | Mobile fallback used by `ResponsiveDialog*` |
|
|
24
|
+
| `@manhphi1309/hooks` | `useIsMobile()` for the responsive breakpoint |
|
|
25
|
+
| `@manhphi1309/utils` | `cn()` class-name helper |
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Dialog Family
|
|
30
|
+
|
|
31
|
+
A set of composable primitives that always render as a centred modal overlay, regardless of screen size.
|
|
32
|
+
|
|
33
|
+
### `Dialog`
|
|
34
|
+
|
|
35
|
+
The **root** component. Controls open/close state and provides context to all children. Thin wrapper around `Radix Dialog.Root`.
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
<Dialog open={open} onOpenChange={setOpen}>
|
|
39
|
+
...
|
|
40
|
+
</Dialog>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
| Prop | Type | Notes |
|
|
44
|
+
| -------------- | ------------------------- | -------------------------------------------------------------- |
|
|
45
|
+
| `open` | `boolean` | Controlled open state |
|
|
46
|
+
| `onOpenChange` | `(open: boolean) => void` | Called when open state changes |
|
|
47
|
+
| `defaultOpen` | `boolean` | Uncontrolled initial state |
|
|
48
|
+
| `modal` | `boolean` | Default `true` — traps focus and blocks background interaction |
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
### `DialogTrigger`
|
|
53
|
+
|
|
54
|
+
Wraps the element that **opens** the dialog when clicked. Passes through all props to the underlying `Radix Dialog.Trigger`. Use `asChild` to compose with your own element.
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
<DialogTrigger asChild>
|
|
58
|
+
<Button>Open</Button>
|
|
59
|
+
</DialogTrigger>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
### `DialogPortal`
|
|
65
|
+
|
|
66
|
+
Renders its children into a **portal** outside the current DOM tree (appended to `document.body`). Ensures the dialog appears above all other content and avoids z-index / overflow issues.
|
|
67
|
+
|
|
68
|
+
> Usually you do not use this directly — `DialogContent` includes it automatically.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
### `DialogOverlay`
|
|
73
|
+
|
|
74
|
+
The **backdrop** behind the dialog content. Renders a fixed, full-screen semi-transparent layer (`bg-black/10`) with a backdrop blur on supported browsers.
|
|
75
|
+
|
|
76
|
+
**Animations:**
|
|
77
|
+
|
|
78
|
+
- Open → `fade-in-0`
|
|
79
|
+
- Close → `fade-out-0`
|
|
80
|
+
|
|
81
|
+
> Usually you do not use this directly — `DialogContent` includes it automatically.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
### `DialogContent`
|
|
86
|
+
|
|
87
|
+
The **visible panel** of the dialog. Renders centred on screen (`top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2`), inside a portal with an overlay underneath. Comes with a ✕ ghost button in the top-right corner by default.
|
|
88
|
+
|
|
89
|
+
**Animations:**
|
|
90
|
+
|
|
91
|
+
- Open → `fade-in-0 + zoom-in-95`
|
|
92
|
+
- Close → `fade-out-0 + zoom-out-95`
|
|
93
|
+
|
|
94
|
+
| Prop | Type | Default | Notes |
|
|
95
|
+
| ----------------- | --------- | ------- | ------------------------------------------------------- |
|
|
96
|
+
| `showCloseButton` | `boolean` | `true` | Show/hide the built-in ✕ button in the top-right corner |
|
|
97
|
+
| `className` | `string` | — | Extra classes merged on the content panel |
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
<DialogContent showCloseButton={false}>...</DialogContent>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
### `DialogHeader`
|
|
106
|
+
|
|
107
|
+
A **layout wrapper** for the top section of a dialog. Stacks children vertically with `gap-2`. Typically holds `DialogTitle` and `DialogDescription`.
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
<DialogHeader>
|
|
111
|
+
<DialogTitle>Title</DialogTitle>
|
|
112
|
+
<DialogDescription>Optional description.</DialogDescription>
|
|
113
|
+
</DialogHeader>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
### `DialogTitle`
|
|
119
|
+
|
|
120
|
+
The **accessible title** of the dialog. Rendered as a Radix `Dialog.Title` (connected to `aria-labelledby` on the content). Styled with `text-base font-medium leading-none` using the heading font.
|
|
121
|
+
|
|
122
|
+
> Required for accessibility — screen readers announce this when the dialog opens.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
### `DialogDescription`
|
|
127
|
+
|
|
128
|
+
The **accessible description** of the dialog. Rendered as a Radix `Dialog.Description` (connected to `aria-describedby` on the content). Styled as small muted text. Links inside it are automatically underlined.
|
|
129
|
+
|
|
130
|
+
> Optional but recommended for accessibility.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
### `DialogFooter`
|
|
135
|
+
|
|
136
|
+
A **layout wrapper** for the bottom action area. Bleeds to the edges of the content panel (`-mx-4 -mb-4`), adds a top border and `bg-muted/50` tint. Actions stack vertically on mobile, align right horizontally on `sm+`.
|
|
137
|
+
|
|
138
|
+
| Prop | Type | Default | Notes |
|
|
139
|
+
| ----------------- | --------- | ------- | -------------------------------------------------------- |
|
|
140
|
+
| `showCloseButton` | `boolean` | `false` | Appends a `Close` outline button wired to `Dialog.Close` |
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
<DialogFooter showCloseButton>
|
|
144
|
+
<Button type="submit">Save</Button>
|
|
145
|
+
</DialogFooter>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
### `DialogClose`
|
|
151
|
+
|
|
152
|
+
A bare **close trigger** primitive. Closes the dialog when activated. Use `asChild` to compose with a custom element.
|
|
153
|
+
|
|
154
|
+
```tsx
|
|
155
|
+
<DialogClose asChild>
|
|
156
|
+
<Button variant="ghost">Cancel</Button>
|
|
157
|
+
</DialogClose>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## ResponsiveDialog Family
|
|
163
|
+
|
|
164
|
+
Drop-in replacements for the `Dialog*` components. Each one checks `useIsMobile()` (breakpoint: `768 px`) and renders the appropriate primitive:
|
|
165
|
+
|
|
166
|
+
| Component | Desktop (≥ 768 px) | Mobile (< 768 px) |
|
|
167
|
+
| ----------------------------- | ------------------- | ------------------- |
|
|
168
|
+
| `ResponsiveDialog` | `Dialog` | `Drawer` |
|
|
169
|
+
| `ResponsiveDialogTrigger` | `DialogTrigger` | `DrawerTrigger` |
|
|
170
|
+
| `ResponsiveDialogClose` | `DialogClose` | `DrawerClose` |
|
|
171
|
+
| `ResponsiveDialogContent` | `DialogContent` | `DrawerContent` |
|
|
172
|
+
| `ResponsiveDialogHeader` | `DialogHeader` | `DrawerHeader` |
|
|
173
|
+
| `ResponsiveDialogFooter` | `DialogFooter` | `DrawerFooter` |
|
|
174
|
+
| `ResponsiveDialogTitle` | `DialogTitle` | `DrawerTitle` |
|
|
175
|
+
| `ResponsiveDialogDescription` | `DialogDescription` | `DrawerDescription` |
|
|
176
|
+
|
|
177
|
+
All props are forwarded to the underlying component unchanged. `showCloseButton` on `ResponsiveDialogContent` and `ResponsiveDialogFooter` works the same way as on their dialog counterparts, including the correct close primitive for the active variant.
|
|
178
|
+
|
|
179
|
+
> **Note:** `ResponsiveDialogContent` does **not** forward `showCloseButton` to `DrawerContent` (the Drawer handles close via the handle bar and swipe gesture). The prop is only meaningful on desktop.
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Usage
|
|
184
|
+
|
|
185
|
+
### Standard Dialog
|
|
186
|
+
|
|
187
|
+
```tsx
|
|
188
|
+
import { Button } from "@manhphi1309/button"
|
|
189
|
+
import {
|
|
190
|
+
Dialog,
|
|
191
|
+
DialogContent,
|
|
192
|
+
DialogDescription,
|
|
193
|
+
DialogFooter,
|
|
194
|
+
DialogHeader,
|
|
195
|
+
DialogTitle,
|
|
196
|
+
DialogTrigger,
|
|
197
|
+
} from "@manhphi1309/dialog"
|
|
198
|
+
|
|
199
|
+
export default function DeleteConfirm() {
|
|
200
|
+
return (
|
|
201
|
+
<Dialog>
|
|
202
|
+
<DialogTrigger asChild>
|
|
203
|
+
<Button variant="destructive">Delete account</Button>
|
|
204
|
+
</DialogTrigger>
|
|
205
|
+
<DialogContent>
|
|
206
|
+
<DialogHeader>
|
|
207
|
+
<DialogTitle>Are you absolutely sure?</DialogTitle>
|
|
208
|
+
<DialogDescription>
|
|
209
|
+
This action cannot be undone. Your account will be permanently
|
|
210
|
+
deleted.
|
|
211
|
+
</DialogDescription>
|
|
212
|
+
</DialogHeader>
|
|
213
|
+
<DialogFooter showCloseButton>
|
|
214
|
+
<Button variant="destructive">Delete</Button>
|
|
215
|
+
</DialogFooter>
|
|
216
|
+
</DialogContent>
|
|
217
|
+
</Dialog>
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Responsive Dialog (Dialog on desktop, Drawer on mobile)
|
|
21
223
|
|
|
22
224
|
```tsx
|
|
23
|
-
import {
|
|
225
|
+
import { Button } from "@manhphi1309/button"
|
|
226
|
+
import {
|
|
227
|
+
ResponsiveDialog,
|
|
228
|
+
ResponsiveDialogContent,
|
|
229
|
+
ResponsiveDialogDescription,
|
|
230
|
+
ResponsiveDialogFooter,
|
|
231
|
+
ResponsiveDialogHeader,
|
|
232
|
+
ResponsiveDialogTitle,
|
|
233
|
+
ResponsiveDialogTrigger,
|
|
234
|
+
} from "@manhphi1309/dialog"
|
|
24
235
|
|
|
25
|
-
export default function
|
|
26
|
-
return
|
|
236
|
+
export default function EditProfile() {
|
|
237
|
+
return (
|
|
238
|
+
<ResponsiveDialog>
|
|
239
|
+
<ResponsiveDialogTrigger asChild>
|
|
240
|
+
<Button>Edit Profile</Button>
|
|
241
|
+
</ResponsiveDialogTrigger>
|
|
242
|
+
<ResponsiveDialogContent>
|
|
243
|
+
<ResponsiveDialogHeader>
|
|
244
|
+
<ResponsiveDialogTitle>Edit profile</ResponsiveDialogTitle>
|
|
245
|
+
<ResponsiveDialogDescription>
|
|
246
|
+
Make changes to your profile here.
|
|
247
|
+
</ResponsiveDialogDescription>
|
|
248
|
+
</ResponsiveDialogHeader>
|
|
249
|
+
{/* form content */}
|
|
250
|
+
<ResponsiveDialogFooter>
|
|
251
|
+
<Button type="submit">Save changes</Button>
|
|
252
|
+
</ResponsiveDialogFooter>
|
|
253
|
+
</ResponsiveDialogContent>
|
|
254
|
+
</ResponsiveDialog>
|
|
255
|
+
)
|
|
27
256
|
}
|
|
28
257
|
```
|
package/dist/index.cjs
CHANGED
|
@@ -1,35 +1,102 @@
|
|
|
1
|
-
"use client";
|
|
2
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#endregion
|
|
3
|
+
require("react");
|
|
3
4
|
let lucide_react = require("lucide-react");
|
|
4
5
|
let radix_ui = require("radix-ui");
|
|
5
6
|
let _manhphi1309_button = require("@manhphi1309/button");
|
|
6
7
|
let _manhphi1309_utils = require("@manhphi1309/utils");
|
|
7
8
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
8
|
-
|
|
9
|
+
let _manhphi1309_drawer = require("@manhphi1309/drawer");
|
|
10
|
+
let _manhphi1309_hooks = require("@manhphi1309/hooks");
|
|
11
|
+
//#region src/dialog.tsx
|
|
12
|
+
/**
|
|
13
|
+
* Root dialog component. Controls open/close state and provides context
|
|
14
|
+
* to all child primitives via Radix `Dialog.Root`.
|
|
15
|
+
*
|
|
16
|
+
* Can be used as **uncontrolled** (via `defaultOpen`) or **controlled**
|
|
17
|
+
* (via `open` + `onOpenChange`).
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* // Uncontrolled
|
|
21
|
+
* <Dialog>
|
|
22
|
+
* <DialogTrigger>Open</DialogTrigger>
|
|
23
|
+
* <DialogContent>...</DialogContent>
|
|
24
|
+
* </Dialog>
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* // Controlled
|
|
28
|
+
* const [open, setOpen] = useState(false)
|
|
29
|
+
* <Dialog open={open} onOpenChange={setOpen}>
|
|
30
|
+
* <DialogContent>...</DialogContent>
|
|
31
|
+
* </Dialog>
|
|
32
|
+
*/
|
|
9
33
|
function Dialog({ ...props }) {
|
|
10
34
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(radix_ui.Dialog.Root, {
|
|
11
35
|
"data-slot": "dialog",
|
|
12
36
|
...props
|
|
13
37
|
});
|
|
14
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* The element that opens the dialog when activated (clicked / keyboard).
|
|
41
|
+
* Thin wrapper around Radix `Dialog.Trigger`.
|
|
42
|
+
*
|
|
43
|
+
* Use `asChild` to compose with your own element instead of the default button.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* <DialogTrigger asChild>
|
|
47
|
+
* <Button variant="outline">Open</Button>
|
|
48
|
+
* </DialogTrigger>
|
|
49
|
+
*/
|
|
15
50
|
function DialogTrigger({ ...props }) {
|
|
16
51
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(radix_ui.Dialog.Trigger, {
|
|
17
52
|
"data-slot": "dialog-trigger",
|
|
18
53
|
...props
|
|
19
54
|
});
|
|
20
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Renders its children into a **portal** appended to `document.body`,
|
|
58
|
+
* outside the current DOM tree. This ensures the dialog always appears
|
|
59
|
+
* above other content and is never clipped by parent `overflow` or `z-index`.
|
|
60
|
+
*
|
|
61
|
+
* You rarely need to use this directly — `DialogContent` already wraps its
|
|
62
|
+
* output in a portal automatically.
|
|
63
|
+
*/
|
|
21
64
|
function DialogPortal({ ...props }) {
|
|
22
65
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(radix_ui.Dialog.Portal, {
|
|
23
66
|
"data-slot": "dialog-portal",
|
|
24
67
|
...props
|
|
25
68
|
});
|
|
26
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* A primitive close trigger. Closes the dialog when activated.
|
|
72
|
+
* Use `asChild` to compose with a custom element.
|
|
73
|
+
*
|
|
74
|
+
* Prefer the built-in `showCloseButton` prop on `DialogContent` or
|
|
75
|
+
* `DialogFooter` for standard close affordances. Use this component
|
|
76
|
+
* when you need a completely custom close action inside the dialog body.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* <DialogClose asChild>
|
|
80
|
+
* <Button variant="ghost">Cancel</Button>
|
|
81
|
+
* </DialogClose>
|
|
82
|
+
*/
|
|
27
83
|
function DialogClose({ ...props }) {
|
|
28
84
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(radix_ui.Dialog.Close, {
|
|
29
85
|
"data-slot": "dialog-close",
|
|
30
86
|
...props
|
|
31
87
|
});
|
|
32
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* The semi-transparent **backdrop** rendered behind the dialog panel.
|
|
91
|
+
* Covers the full viewport (`fixed inset-0`) with a slight blur and dark tint.
|
|
92
|
+
*
|
|
93
|
+
* Animates:
|
|
94
|
+
* - **Open** → `fade-in-0`
|
|
95
|
+
* - **Close** → `fade-out-0`
|
|
96
|
+
*
|
|
97
|
+
* You rarely need to use this directly — `DialogContent` renders it
|
|
98
|
+
* automatically via `DialogPortal`.
|
|
99
|
+
*/
|
|
33
100
|
function DialogOverlay({ className, ...props }) {
|
|
34
101
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(radix_ui.Dialog.Overlay, {
|
|
35
102
|
"data-slot": "dialog-overlay",
|
|
@@ -37,6 +104,29 @@ function DialogOverlay({ className, ...props }) {
|
|
|
37
104
|
...props
|
|
38
105
|
});
|
|
39
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* The **visible panel** of the dialog. Rendered centred on screen via
|
|
109
|
+
* `position: fixed; top: 50%; left: 50%; translate(-50%, -50%)`.
|
|
110
|
+
* Automatically wraps itself in `DialogPortal` and renders `DialogOverlay`
|
|
111
|
+
* behind it.
|
|
112
|
+
*
|
|
113
|
+
* By default a ghost ✕ button is positioned in the top-right corner.
|
|
114
|
+
* Pass `showCloseButton={false}` to remove it when you want to control
|
|
115
|
+
* close behaviour exclusively from the footer.
|
|
116
|
+
*
|
|
117
|
+
* Animates:
|
|
118
|
+
* - **Open** → `fade-in-0 + zoom-in-95`
|
|
119
|
+
* - **Close** → `fade-out-0 + zoom-out-95`
|
|
120
|
+
*
|
|
121
|
+
* @param showCloseButton - Whether to render the ✕ ghost button in the
|
|
122
|
+
* top-right corner. Defaults to `true`.
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* <DialogContent showCloseButton={false}>
|
|
126
|
+
* <DialogHeader>...</DialogHeader>
|
|
127
|
+
* <DialogFooter showCloseButton>...</DialogFooter>
|
|
128
|
+
* </DialogContent>
|
|
129
|
+
*/
|
|
40
130
|
function DialogContent({ className, children, showCloseButton = true, ...props }) {
|
|
41
131
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DialogPortal, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DialogOverlay, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(radix_ui.Dialog.Content, {
|
|
42
132
|
"data-slot": "dialog-content",
|
|
@@ -58,6 +148,17 @@ function DialogContent({ className, children, showCloseButton = true, ...props }
|
|
|
58
148
|
})]
|
|
59
149
|
})] });
|
|
60
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Layout wrapper for the **top section** of a dialog.
|
|
153
|
+
* Stacks children vertically with `gap-2`. Typically contains
|
|
154
|
+
* `DialogTitle` and optionally `DialogDescription`.
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* <DialogHeader>
|
|
158
|
+
* <DialogTitle>Confirm deletion</DialogTitle>
|
|
159
|
+
* <DialogDescription>This action is irreversible.</DialogDescription>
|
|
160
|
+
* </DialogHeader>
|
|
161
|
+
*/
|
|
61
162
|
function DialogHeader({ className, ...props }) {
|
|
62
163
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
63
164
|
"data-slot": "dialog-header",
|
|
@@ -65,6 +166,20 @@ function DialogHeader({ className, ...props }) {
|
|
|
65
166
|
...props
|
|
66
167
|
});
|
|
67
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* Layout wrapper for the **bottom action area** of a dialog.
|
|
171
|
+
* Bleeds to the content panel edges (`-mx-4 -mb-4`), adds a top border
|
|
172
|
+
* and a subtle muted background tint. Actions stack vertically on mobile
|
|
173
|
+
* and align to the right on `sm+` screens.
|
|
174
|
+
*
|
|
175
|
+
* @param showCloseButton - When `true`, appends an outline `Close` button
|
|
176
|
+
* wired to `DialogClose` after any provided `children`. Defaults to `false`.
|
|
177
|
+
*
|
|
178
|
+
* @example
|
|
179
|
+
* <DialogFooter showCloseButton>
|
|
180
|
+
* <Button type="submit">Save</Button>
|
|
181
|
+
* </DialogFooter>
|
|
182
|
+
*/
|
|
68
183
|
function DialogFooter({ className, showCloseButton = false, children, ...props }) {
|
|
69
184
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
70
185
|
"data-slot": "dialog-footer",
|
|
@@ -79,6 +194,16 @@ function DialogFooter({ className, showCloseButton = false, children, ...props }
|
|
|
79
194
|
})]
|
|
80
195
|
});
|
|
81
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* The **accessible title** of the dialog. Rendered as a Radix `Dialog.Title`,
|
|
199
|
+
* which is automatically linked to the dialog content via `aria-labelledby`.
|
|
200
|
+
* Screen readers announce this text when the dialog opens.
|
|
201
|
+
*
|
|
202
|
+
* Styled with the heading font token at `text-base font-medium`.
|
|
203
|
+
*
|
|
204
|
+
* @remarks Required for accessibility. Every dialog should have a title,
|
|
205
|
+
* even if visually hidden with `className="sr-only"`.
|
|
206
|
+
*/
|
|
82
207
|
function DialogTitle({ className, ...props }) {
|
|
83
208
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(radix_ui.Dialog.Title, {
|
|
84
209
|
"data-slot": "dialog-title",
|
|
@@ -86,6 +211,17 @@ function DialogTitle({ className, ...props }) {
|
|
|
86
211
|
...props
|
|
87
212
|
});
|
|
88
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* The **accessible description** of the dialog. Rendered as a Radix
|
|
216
|
+
* `Dialog.Description`, which is linked to the dialog content via
|
|
217
|
+
* `aria-describedby`. Screen readers read this after the title.
|
|
218
|
+
*
|
|
219
|
+
* Styled as small muted text. Anchor tags (`<a>`) inside the description
|
|
220
|
+
* are automatically underlined and change colour on hover.
|
|
221
|
+
*
|
|
222
|
+
* @remarks Optional but recommended. Omit only if the dialog content itself
|
|
223
|
+
* is sufficiently self-descriptive.
|
|
224
|
+
*/
|
|
89
225
|
function DialogDescription({ className, ...props }) {
|
|
90
226
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(radix_ui.Dialog.Description, {
|
|
91
227
|
"data-slot": "dialog-description",
|
|
@@ -94,6 +230,214 @@ function DialogDescription({ className, ...props }) {
|
|
|
94
230
|
});
|
|
95
231
|
}
|
|
96
232
|
//#endregion
|
|
233
|
+
//#region src/responsive-dialog.tsx
|
|
234
|
+
/**
|
|
235
|
+
* Adaptive root component that switches between `Dialog` and `Drawer`
|
|
236
|
+
* depending on screen width:
|
|
237
|
+
* - **Desktop (≥ 768 px)** → renders `Dialog` (centred modal overlay)
|
|
238
|
+
* - **Mobile (< 768 px)** → renders `Drawer` (bottom-sheet panel)
|
|
239
|
+
*
|
|
240
|
+
* All props are forwarded unchanged to the active primitive, so `open`,
|
|
241
|
+
* `defaultOpen`, `onOpenChange`, and `modal` all behave identically to
|
|
242
|
+
* the standard `Dialog` root.
|
|
243
|
+
*
|
|
244
|
+
* @example
|
|
245
|
+
* <ResponsiveDialog>
|
|
246
|
+
* <ResponsiveDialogTrigger asChild>
|
|
247
|
+
* <Button>Open</Button>
|
|
248
|
+
* </ResponsiveDialogTrigger>
|
|
249
|
+
* <ResponsiveDialogContent>
|
|
250
|
+
* <ResponsiveDialogHeader>
|
|
251
|
+
* <ResponsiveDialogTitle>Title</ResponsiveDialogTitle>
|
|
252
|
+
* </ResponsiveDialogHeader>
|
|
253
|
+
* </ResponsiveDialogContent>
|
|
254
|
+
* </ResponsiveDialog>
|
|
255
|
+
*/
|
|
256
|
+
function ResponsiveDialog({ children, ...props }) {
|
|
257
|
+
if ((0, _manhphi1309_hooks.useIsMobile)()) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_manhphi1309_drawer.Drawer, {
|
|
258
|
+
...props,
|
|
259
|
+
children
|
|
260
|
+
});
|
|
261
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Dialog, {
|
|
262
|
+
...props,
|
|
263
|
+
children
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* The element that opens the responsive dialog/drawer when activated.
|
|
268
|
+
*
|
|
269
|
+
* - **Desktop** → `DialogTrigger` (Radix `Dialog.Trigger`)
|
|
270
|
+
* - **Mobile** → `DrawerTrigger` (Vaul `Drawer.Trigger`)
|
|
271
|
+
*
|
|
272
|
+
* Use `asChild` to render your own element as the trigger instead of
|
|
273
|
+
* the default button wrapper.
|
|
274
|
+
*
|
|
275
|
+
* @example
|
|
276
|
+
* <ResponsiveDialogTrigger asChild>
|
|
277
|
+
* <Button variant="outline">Open</Button>
|
|
278
|
+
* </ResponsiveDialogTrigger>
|
|
279
|
+
*/
|
|
280
|
+
function ResponsiveDialogTrigger({ children, ...props }) {
|
|
281
|
+
if ((0, _manhphi1309_hooks.useIsMobile)()) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_manhphi1309_drawer.DrawerTrigger, {
|
|
282
|
+
...props,
|
|
283
|
+
children
|
|
284
|
+
});
|
|
285
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DialogTrigger, {
|
|
286
|
+
...props,
|
|
287
|
+
children
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* A close trigger that targets the correct primitive for the current
|
|
292
|
+
* viewport:
|
|
293
|
+
* - **Desktop** → `DialogClose` (Radix `Dialog.Close`)
|
|
294
|
+
* - **Mobile** → `DrawerClose` (Vaul `Drawer.Close`)
|
|
295
|
+
*
|
|
296
|
+
* Use `asChild` to wrap a custom element.
|
|
297
|
+
*
|
|
298
|
+
* @example
|
|
299
|
+
* <ResponsiveDialogClose asChild>
|
|
300
|
+
* <Button variant="ghost">Cancel</Button>
|
|
301
|
+
* </ResponsiveDialogClose>
|
|
302
|
+
*/
|
|
303
|
+
function ResponsiveDialogClose({ children, ...props }) {
|
|
304
|
+
if ((0, _manhphi1309_hooks.useIsMobile)()) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_manhphi1309_drawer.DrawerClose, {
|
|
305
|
+
...props,
|
|
306
|
+
children
|
|
307
|
+
});
|
|
308
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DialogClose, {
|
|
309
|
+
...props,
|
|
310
|
+
children
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* The visible panel of the responsive dialog. Delegates to the
|
|
315
|
+
* appropriate primitive:
|
|
316
|
+
* - **Desktop** → `DialogContent` — centred modal with fade + zoom animations.
|
|
317
|
+
* The ✕ close button is shown by default (`showCloseButton={true}`).
|
|
318
|
+
* - **Mobile** → `DrawerContent` — bottom-sheet panel with a drag handle.
|
|
319
|
+
* `showCloseButton` has **no effect** on mobile; the drawer is closed
|
|
320
|
+
* via swipe gesture or the drag handle.
|
|
321
|
+
*
|
|
322
|
+
* @param showCloseButton - Show/hide the ✕ button in the top-right corner
|
|
323
|
+
* on desktop. Has no effect on mobile. Defaults to `true`.
|
|
324
|
+
*
|
|
325
|
+
* @example
|
|
326
|
+
* // Hide the ✕ button and force close via footer only (desktop)
|
|
327
|
+
* <ResponsiveDialogContent showCloseButton={false}>
|
|
328
|
+
* ...
|
|
329
|
+
* </ResponsiveDialogContent>
|
|
330
|
+
*/
|
|
331
|
+
function ResponsiveDialogContent({ className, children, showCloseButton = true, ...props }) {
|
|
332
|
+
if ((0, _manhphi1309_hooks.useIsMobile)()) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_manhphi1309_drawer.DrawerContent, {
|
|
333
|
+
className,
|
|
334
|
+
children
|
|
335
|
+
});
|
|
336
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DialogContent, {
|
|
337
|
+
className,
|
|
338
|
+
showCloseButton,
|
|
339
|
+
...props,
|
|
340
|
+
children
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Layout wrapper for the **top section** of the responsive dialog/drawer.
|
|
345
|
+
*
|
|
346
|
+
* - **Desktop** → `DialogHeader` — vertical stack with `gap-2`
|
|
347
|
+
* - **Mobile** → `DrawerHeader` — vertically centred text for bottom/top
|
|
348
|
+
* drawers, left-aligned for side drawers
|
|
349
|
+
*
|
|
350
|
+
* Typically contains `ResponsiveDialogTitle` and optionally
|
|
351
|
+
* `ResponsiveDialogDescription`.
|
|
352
|
+
*/
|
|
353
|
+
function ResponsiveDialogHeader({ className, ...props }) {
|
|
354
|
+
if ((0, _manhphi1309_hooks.useIsMobile)()) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_manhphi1309_drawer.DrawerHeader, {
|
|
355
|
+
className,
|
|
356
|
+
...props
|
|
357
|
+
});
|
|
358
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DialogHeader, {
|
|
359
|
+
className,
|
|
360
|
+
...props
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Layout wrapper for the **bottom action area** of the responsive
|
|
365
|
+
* dialog/drawer.
|
|
366
|
+
*
|
|
367
|
+
* - **Desktop** → `DialogFooter` — bleeds to panel edges, top border,
|
|
368
|
+
* actions aligned right on `sm+`
|
|
369
|
+
* - **Mobile** → `DrawerFooter` — `mt-auto` stacked column at the bottom
|
|
370
|
+
* of the drawer
|
|
371
|
+
*
|
|
372
|
+
* @param showCloseButton - When `true`, appends a close button after
|
|
373
|
+
* `children`. On **desktop** this is a `DialogClose`-wrapped outline
|
|
374
|
+
* button; on **mobile** this is a `DrawerClose`-wrapped outline button.
|
|
375
|
+
* Defaults to `false`.
|
|
376
|
+
*
|
|
377
|
+
* @example
|
|
378
|
+
* <ResponsiveDialogFooter showCloseButton>
|
|
379
|
+
* <Button type="submit">Save</Button>
|
|
380
|
+
* </ResponsiveDialogFooter>
|
|
381
|
+
*/
|
|
382
|
+
function ResponsiveDialogFooter({ className, children, showCloseButton = false, ...props }) {
|
|
383
|
+
if ((0, _manhphi1309_hooks.useIsMobile)()) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_manhphi1309_drawer.DrawerFooter, {
|
|
384
|
+
className,
|
|
385
|
+
...props,
|
|
386
|
+
children: [children, showCloseButton && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_manhphi1309_drawer.DrawerClose, {
|
|
387
|
+
asChild: true,
|
|
388
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_manhphi1309_button.Button, {
|
|
389
|
+
variant: "outline",
|
|
390
|
+
children: "Close"
|
|
391
|
+
})
|
|
392
|
+
})]
|
|
393
|
+
});
|
|
394
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DialogFooter, {
|
|
395
|
+
className,
|
|
396
|
+
showCloseButton,
|
|
397
|
+
...props,
|
|
398
|
+
children
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* The **accessible title** of the responsive dialog/drawer.
|
|
403
|
+
*
|
|
404
|
+
* - **Desktop** → `DialogTitle` (linked via `aria-labelledby`)
|
|
405
|
+
* - **Mobile** → `DrawerTitle` (linked via `aria-labelledby`)
|
|
406
|
+
*
|
|
407
|
+
* Screen readers announce this text when the panel opens.
|
|
408
|
+
*
|
|
409
|
+
* @remarks Required for accessibility on both desktop and mobile.
|
|
410
|
+
*/
|
|
411
|
+
function ResponsiveDialogTitle({ className, ...props }) {
|
|
412
|
+
if ((0, _manhphi1309_hooks.useIsMobile)()) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_manhphi1309_drawer.DrawerTitle, {
|
|
413
|
+
className,
|
|
414
|
+
...props
|
|
415
|
+
});
|
|
416
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DialogTitle, {
|
|
417
|
+
className,
|
|
418
|
+
...props
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* The **accessible description** of the responsive dialog/drawer.
|
|
423
|
+
*
|
|
424
|
+
* - **Desktop** → `DialogDescription` (linked via `aria-describedby`)
|
|
425
|
+
* - **Mobile** → `DrawerDescription` (linked via `aria-describedby`)
|
|
426
|
+
*
|
|
427
|
+
* @remarks Optional but recommended. Provides supplementary context for
|
|
428
|
+
* screen reader users after the title is announced.
|
|
429
|
+
*/
|
|
430
|
+
function ResponsiveDialogDescription({ className, ...props }) {
|
|
431
|
+
if ((0, _manhphi1309_hooks.useIsMobile)()) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_manhphi1309_drawer.DrawerDescription, {
|
|
432
|
+
className,
|
|
433
|
+
...props
|
|
434
|
+
});
|
|
435
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DialogDescription, {
|
|
436
|
+
className,
|
|
437
|
+
...props
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
//#endregion
|
|
97
441
|
exports.Dialog = Dialog;
|
|
98
442
|
exports.DialogClose = DialogClose;
|
|
99
443
|
exports.DialogContent = DialogContent;
|
|
@@ -104,3 +448,11 @@ exports.DialogOverlay = DialogOverlay;
|
|
|
104
448
|
exports.DialogPortal = DialogPortal;
|
|
105
449
|
exports.DialogTitle = DialogTitle;
|
|
106
450
|
exports.DialogTrigger = DialogTrigger;
|
|
451
|
+
exports.ResponsiveDialog = ResponsiveDialog;
|
|
452
|
+
exports.ResponsiveDialogClose = ResponsiveDialogClose;
|
|
453
|
+
exports.ResponsiveDialogContent = ResponsiveDialogContent;
|
|
454
|
+
exports.ResponsiveDialogDescription = ResponsiveDialogDescription;
|
|
455
|
+
exports.ResponsiveDialogFooter = ResponsiveDialogFooter;
|
|
456
|
+
exports.ResponsiveDialogHeader = ResponsiveDialogHeader;
|
|
457
|
+
exports.ResponsiveDialogTitle = ResponsiveDialogTitle;
|
|
458
|
+
exports.ResponsiveDialogTrigger = ResponsiveDialogTrigger;
|