@manhphi1309/dialog 0.1.2 → 0.3.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 +264 -12
- package/dist/index.cjs +419 -2
- package/dist/index.d.cts +324 -13
- package/dist/index.d.mts +324 -13
- package/dist/index.mjs +389 -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,268 @@ 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
|
+
### Drawer Snap Points (Mobile Only)
|
|
182
|
+
|
|
183
|
+
Because `ResponsiveDialog` forwards properties directly to the underlying Vaul `<Drawer>` on mobile devices, you can pass drawer-specific props like `snapPoints` directly to the `<ResponsiveDialog>` root component. The desktop `<Dialog>` will safely ignore them.
|
|
184
|
+
|
|
185
|
+
```tsx
|
|
186
|
+
// Using React state to properly control the active snap point
|
|
187
|
+
const [snap, setSnap] = React.useState<number | string | null>("500px")
|
|
188
|
+
|
|
189
|
+
<ResponsiveDialog
|
|
190
|
+
snapPoints={["300px", "500px", 1]}
|
|
191
|
+
activeSnapPoint={snap}
|
|
192
|
+
setActiveSnapPoint={setSnap}
|
|
193
|
+
fadeFromIndex={1}
|
|
194
|
+
>
|
|
195
|
+
...
|
|
196
|
+
</ResponsiveDialog>
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
> [!WARNING]
|
|
200
|
+
> **Important Vaul Requirements:**
|
|
201
|
+
> 1. **Strict Types:** If you provide `snapPoints`, you are strictly required by Vaul to provide `fadeFromIndex` as a number.
|
|
202
|
+
> 2. **Controlled State:** If you hardcode `activeSnapPoint` as a string/number prop *without* passing a state setter to `setActiveSnapPoint`, the Drawer will glitch and snap back down when the user tries to drag it, because React will force it back to the hardcoded prop value on the next render. Always use a state hook (like `useState`) for the active point.
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Usage
|
|
207
|
+
|
|
208
|
+
### Standard Dialog
|
|
209
|
+
|
|
210
|
+
```tsx
|
|
211
|
+
import { Button } from "@manhphi1309/button"
|
|
212
|
+
import {
|
|
213
|
+
Dialog,
|
|
214
|
+
DialogContent,
|
|
215
|
+
DialogDescription,
|
|
216
|
+
DialogFooter,
|
|
217
|
+
DialogHeader,
|
|
218
|
+
DialogTitle,
|
|
219
|
+
DialogTrigger,
|
|
220
|
+
} from "@manhphi1309/dialog"
|
|
221
|
+
|
|
222
|
+
export default function DeleteConfirm() {
|
|
223
|
+
return (
|
|
224
|
+
<Dialog>
|
|
225
|
+
<DialogTrigger asChild>
|
|
226
|
+
<Button variant="destructive">Delete account</Button>
|
|
227
|
+
</DialogTrigger>
|
|
228
|
+
<DialogContent>
|
|
229
|
+
<DialogHeader>
|
|
230
|
+
<DialogTitle>Are you absolutely sure?</DialogTitle>
|
|
231
|
+
<DialogDescription>
|
|
232
|
+
This action cannot be undone. Your account will be permanently
|
|
233
|
+
deleted.
|
|
234
|
+
</DialogDescription>
|
|
235
|
+
</DialogHeader>
|
|
236
|
+
<DialogFooter showCloseButton>
|
|
237
|
+
<Button variant="destructive">Delete</Button>
|
|
238
|
+
</DialogFooter>
|
|
239
|
+
</DialogContent>
|
|
240
|
+
</Dialog>
|
|
241
|
+
)
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Responsive Dialog (Dialog on desktop, Drawer on mobile)
|
|
21
246
|
|
|
22
247
|
```tsx
|
|
23
|
-
import {
|
|
248
|
+
import { Button } from "@manhphi1309/button"
|
|
249
|
+
import {
|
|
250
|
+
ResponsiveDialog,
|
|
251
|
+
ResponsiveDialogContent,
|
|
252
|
+
ResponsiveDialogDescription,
|
|
253
|
+
ResponsiveDialogFooter,
|
|
254
|
+
ResponsiveDialogHeader,
|
|
255
|
+
ResponsiveDialogTitle,
|
|
256
|
+
ResponsiveDialogTrigger,
|
|
257
|
+
} from "@manhphi1309/dialog"
|
|
24
258
|
|
|
25
|
-
export default function
|
|
26
|
-
return
|
|
259
|
+
export default function EditProfile() {
|
|
260
|
+
return (
|
|
261
|
+
<ResponsiveDialog>
|
|
262
|
+
<ResponsiveDialogTrigger asChild>
|
|
263
|
+
<Button>Edit Profile</Button>
|
|
264
|
+
</ResponsiveDialogTrigger>
|
|
265
|
+
<ResponsiveDialogContent>
|
|
266
|
+
<ResponsiveDialogHeader>
|
|
267
|
+
<ResponsiveDialogTitle>Edit profile</ResponsiveDialogTitle>
|
|
268
|
+
<ResponsiveDialogDescription>
|
|
269
|
+
Make changes to your profile here.
|
|
270
|
+
</ResponsiveDialogDescription>
|
|
271
|
+
</ResponsiveDialogHeader>
|
|
272
|
+
{/* form content */}
|
|
273
|
+
<ResponsiveDialogFooter>
|
|
274
|
+
<Button type="submit">Save changes</Button>
|
|
275
|
+
</ResponsiveDialogFooter>
|
|
276
|
+
</ResponsiveDialogContent>
|
|
277
|
+
</ResponsiveDialog>
|
|
278
|
+
)
|
|
27
279
|
}
|
|
28
280
|
```
|