react-confirm-alert 3.1.1 → 3.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/style.css CHANGED
@@ -32,6 +32,8 @@ body.react-confirm-alert-body-element {
32
32
  border-radius: var(--rca-radius, 10px);
33
33
  box-shadow: var(--rca-shadow, 0 20px 75px rgba(0, 0, 0, 0.13));
34
34
  color: var(--rca-fg, #666);
35
+ /* confirmAlert renders message as text right in the body; keep its line breaks (#38). */
36
+ white-space: pre-line;
35
37
  }
36
38
 
37
39
  .react-confirm-alert-body > h1 {
@@ -125,3 +127,234 @@ dialog.react-confirm-alert-overlay::backdrop {
125
127
  opacity: 1;
126
128
  }
127
129
  }
130
+
131
+ /* The default look of every dialog except confirmAlert, which has no data-skin and keeps the 3.0 look above; unstyled drops the overlay class, so none of this applies.
132
+ --_* are internal defaults: a --rca-* variable set by the app always wins over them. */
133
+ .react-confirm-alert-overlay[data-skin] {
134
+ --_bg: #fff;
135
+ --_fg: #71717a;
136
+ --_title: #09090b;
137
+ --_border: #e4e4e7;
138
+ --_hover: #f4f4f5;
139
+ --_primary: #18181b;
140
+ --_primary-fg: #fff;
141
+ --_ring: #a1a1aa;
142
+ --_danger: #dc2626;
143
+ --_error: #dc2626;
144
+ box-sizing: border-box;
145
+ padding: 16px;
146
+ background: var(--rca-backdrop, rgba(0, 0, 0, 0.5));
147
+ opacity: 1;
148
+ animation: rca-in var(--rca-duration, 200ms) var(--rca-easing, cubic-bezier(0.32, 0.72, 0, 1)) both;
149
+ }
150
+ /* Dark is opt-in: the app's .dark class or data-theme="dark" on an ancestor, usually <html>. */
151
+ :is(.dark, [data-theme=dark]) .react-confirm-alert-overlay[data-skin] {
152
+ --_bg: #18181b;
153
+ --_fg: #a1a1aa;
154
+ --_title: #fafafa;
155
+ --_border: #3f3f46;
156
+ --_hover: #27272a;
157
+ --_primary: #fafafa;
158
+ --_primary-fg: #18181b;
159
+ --_ring: #71717a;
160
+ --_error: #f87171;
161
+ color-scheme: dark;
162
+ background: var(--rca-backdrop, rgba(0, 0, 0, 0.7));
163
+ }
164
+ .react-confirm-alert-overlay[data-skin] .react-confirm-alert {
165
+ max-width: 100%;
166
+ animation: rca-pop var(--rca-duration, 200ms) var(--rca-easing, cubic-bezier(0.32, 0.72, 0, 1)) both;
167
+ }
168
+ .react-confirm-alert-overlay[data-skin] .react-confirm-alert-body {
169
+ box-sizing: border-box;
170
+ width: var(--rca-width, 420px);
171
+ max-width: 100%;
172
+ padding: var(--rca-padding, 24px);
173
+ border: 1px solid var(--rca-border, var(--_border));
174
+ border-radius: var(--rca-radius, 10px);
175
+ background: var(--rca-bg, var(--_bg));
176
+ box-shadow: var(--rca-shadow, 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1));
177
+ color: var(--rca-fg, var(--_fg));
178
+ font-family: var(--rca-font, inherit);
179
+ font-size: 14px;
180
+ line-height: 1.5;
181
+ }
182
+ .react-confirm-alert-overlay[data-skin][data-state=closing] {
183
+ animation: rca-out 150ms ease-in both;
184
+ }
185
+ .react-confirm-alert-overlay[data-skin][data-state=closing] .react-confirm-alert {
186
+ animation: rca-pop-out 150ms ease-in both;
187
+ }
188
+ /* Plain elements inside a skinned dialog look like the slots. Their selectors are type selectors under
189
+ :where(), so they beat a CSS reset such as Tailwind preflight and lose to any class you add. */
190
+ .react-confirm-alert-overlay[data-skin] [data-slot=title],
191
+ :where(.react-confirm-alert-overlay[data-skin]) div :is(h1, h2, h3) {
192
+ margin: 0 0 8px;
193
+ font-size: 18px;
194
+ font-weight: 600;
195
+ line-height: 1.3;
196
+ letter-spacing: -0.01em;
197
+ color: var(--rca-title, var(--_title));
198
+ }
199
+ .react-confirm-alert-overlay[data-skin] [data-slot=field] {
200
+ margin-top: 16px;
201
+ }
202
+ .react-confirm-alert-overlay[data-skin] [data-slot=label],
203
+ :where(.react-confirm-alert-overlay[data-skin]) div label {
204
+ display: block;
205
+ margin-bottom: 6px;
206
+ font-size: 13px;
207
+ font-weight: 500;
208
+ color: var(--rca-title, var(--_title));
209
+ }
210
+ .react-confirm-alert-overlay[data-skin] [data-slot=input],
211
+ :where(.react-confirm-alert-overlay[data-skin]) div :is(input:where(:not([type=checkbox], [type=radio], [type=range], [type=file], [type=color], [type=submit], [type=button], [type=reset], [type=image])), textarea, select) {
212
+ display: block;
213
+ box-sizing: border-box;
214
+ width: 100%;
215
+ height: 36px;
216
+ padding: 0 12px;
217
+ border: 1px solid var(--rca-border, var(--_border));
218
+ border-radius: 6px;
219
+ background: transparent;
220
+ color: var(--rca-title, var(--_title));
221
+ font: inherit;
222
+ outline: none;
223
+ transition: border-color 150ms, box-shadow 150ms;
224
+ }
225
+ .react-confirm-alert-overlay[data-skin] textarea[data-slot=input],
226
+ :where(.react-confirm-alert-overlay[data-skin]) div textarea {
227
+ height: auto;
228
+ min-height: 80px;
229
+ padding: 8px 12px;
230
+ resize: vertical;
231
+ }
232
+ .react-confirm-alert-overlay[data-skin] [data-slot=input]:focus,
233
+ :where(.react-confirm-alert-overlay[data-skin]) div :is(input, textarea, select):where(:focus) {
234
+ border-color: var(--rca-ring, var(--_ring));
235
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--rca-ring, var(--_ring)) 50%, transparent);
236
+ }
237
+ .react-confirm-alert-overlay[data-skin] [data-slot=input][aria-invalid=true],
238
+ :where(.react-confirm-alert-overlay[data-skin]) div :is(input, textarea, select):where([aria-invalid=true]) {
239
+ border-color: var(--rca-danger, var(--_error));
240
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--rca-danger, var(--_error)) 20%, transparent);
241
+ }
242
+ :where(.react-confirm-alert-overlay[data-skin]) div p {
243
+ margin: 0;
244
+ }
245
+ :where(.react-confirm-alert-overlay[data-skin]) div input:where([type=checkbox], [type=radio]) {
246
+ accent-color: var(--rca-button-bg, var(--_primary));
247
+ }
248
+ :where(.react-confirm-alert-overlay[data-skin]) div * + :is(p, label, input, textarea, select) {
249
+ margin-top: 12px;
250
+ }
251
+ :where(.react-confirm-alert-overlay[data-skin]) div label + :is(input, textarea, select) {
252
+ margin-top: 0;
253
+ }
254
+ .react-confirm-alert-overlay[data-skin] [data-slot=error] {
255
+ margin: 6px 0 0;
256
+ font-size: 13px;
257
+ color: var(--rca-danger, var(--_error));
258
+ }
259
+ .react-confirm-alert-overlay[data-skin] .react-confirm-alert-button-group {
260
+ justify-content: flex-end;
261
+ gap: var(--rca-gap, 8px);
262
+ margin-top: 24px;
263
+ }
264
+ .react-confirm-alert-overlay[data-skin] .react-confirm-alert-button-group > button,
265
+ :where(.react-confirm-alert-overlay[data-skin]) div button {
266
+ display: inline-flex;
267
+ align-items: center;
268
+ justify-content: center;
269
+ gap: 8px;
270
+ height: 36px;
271
+ margin: 0;
272
+ padding: 0 16px;
273
+ border: 1px solid var(--rca-border, var(--_border));
274
+ border-radius: var(--rca-button-radius, 6px);
275
+ background: var(--rca-bg, var(--_bg));
276
+ color: var(--rca-title, var(--_title));
277
+ font-family: inherit;
278
+ font-size: 14px;
279
+ font-weight: 500;
280
+ line-height: 1;
281
+ white-space: nowrap;
282
+ transition: background-color 150ms, transform 150ms;
283
+ }
284
+ .react-confirm-alert-overlay[data-skin] .react-confirm-alert-button-group > [data-variant=primary],
285
+ :where(.react-confirm-alert-overlay[data-skin]) div button:where([data-variant=primary]) {
286
+ border-color: transparent;
287
+ background: var(--rca-button-bg, var(--_primary));
288
+ color: var(--rca-button-fg, var(--_primary-fg));
289
+ }
290
+ .react-confirm-alert-overlay[data-skin] .react-confirm-alert-button-group > [data-variant=danger],
291
+ :where(.react-confirm-alert-overlay[data-skin]) div button:where([data-variant=danger]) {
292
+ border-color: transparent;
293
+ background: var(--rca-danger, var(--_danger));
294
+ color: #fff;
295
+ }
296
+ .react-confirm-alert-overlay[data-skin] .react-confirm-alert-button-group > :not([data-variant=primary],[data-variant=danger]):enabled:hover,
297
+ :where(.react-confirm-alert-overlay[data-skin]) div button:where(:not([data-variant=primary],[data-variant=danger]):enabled:hover) {
298
+ background: var(--_hover);
299
+ }
300
+ .react-confirm-alert-overlay[data-skin] .react-confirm-alert-button-group > [data-variant=primary]:enabled:hover,
301
+ :where(.react-confirm-alert-overlay[data-skin]) div button:where([data-variant=primary]:enabled:hover) {
302
+ background: var(--rca-button-bg-hover, color-mix(in srgb, var(--rca-button-bg, var(--_primary)) 85%, #000));
303
+ }
304
+ .react-confirm-alert-overlay[data-skin] .react-confirm-alert-button-group > [data-variant=danger]:enabled:hover,
305
+ :where(.react-confirm-alert-overlay[data-skin]) div button:where([data-variant=danger]:enabled:hover) {
306
+ background: var(--rca-danger-hover, color-mix(in srgb, var(--rca-danger, var(--_danger)) 85%, #000));
307
+ }
308
+ .react-confirm-alert-overlay[data-skin] .react-confirm-alert-button-group > button:active,
309
+ :where(.react-confirm-alert-overlay[data-skin]) div button:where(:active) {
310
+ transform: scale(0.97);
311
+ }
312
+ .react-confirm-alert-overlay[data-skin] .react-confirm-alert-button-group > button:focus-visible,
313
+ :where(.react-confirm-alert-overlay[data-skin]) div button:where(:focus-visible) {
314
+ outline: var(--rca-focus-ring, 2px solid var(--_ring));
315
+ outline-offset: 2px;
316
+ }
317
+ /* Not faded: white on a faded red is below 4.5:1. */
318
+ .react-confirm-alert-overlay[data-skin] .react-confirm-alert-button-group > button:disabled:not([aria-busy=true]),
319
+ :where(.react-confirm-alert-overlay[data-skin]) div button:where(:disabled:not([aria-busy=true])) {
320
+ cursor: default;
321
+ border-color: var(--rca-border, var(--_border));
322
+ background: var(--rca-bg, var(--_bg));
323
+ color: var(--rca-fg, var(--_fg));
324
+ }
325
+ .react-confirm-alert-overlay[data-skin] .react-confirm-alert-button-group > button[aria-busy=true],
326
+ :where(.react-confirm-alert-overlay[data-skin]) div button:where([aria-busy=true]) {
327
+ cursor: progress;
328
+ }
329
+ .react-confirm-alert-overlay[data-skin] .react-confirm-alert-button-group > button[aria-busy=true]::before,
330
+ :where(.react-confirm-alert-overlay[data-skin]) div button:where([aria-busy=true])::before {
331
+ content: '';
332
+ width: 12px;
333
+ height: 12px;
334
+ border: 2px solid currentColor;
335
+ border-right-color: transparent;
336
+ border-radius: 50%;
337
+ animation: rca-spin 700ms linear infinite;
338
+ }
339
+ @media (max-width: 480px) {
340
+ .react-confirm-alert-overlay[data-skin] .react-confirm-alert-button-group {
341
+ flex-direction: column-reverse;
342
+ }
343
+ .react-confirm-alert-overlay[data-skin] .react-confirm-alert-button-group > button {
344
+ height: 40px;
345
+ }
346
+ }
347
+ @media (prefers-reduced-motion: reduce) {
348
+ .react-confirm-alert-overlay[data-skin],
349
+ .react-confirm-alert-overlay[data-skin] .react-confirm-alert {
350
+ animation: none;
351
+ }
352
+ .react-confirm-alert-overlay[data-skin] .react-confirm-alert-button-group > button {
353
+ transition: none;
354
+ }
355
+ }
356
+ @keyframes rca-in { from { opacity: 0; } }
357
+ @keyframes rca-out { to { opacity: 0; } }
358
+ @keyframes rca-pop { from { opacity: 0; transform: scale(0.95); } }
359
+ @keyframes rca-pop-out { to { opacity: 0; transform: scale(0.95); } }
360
+ @keyframes rca-spin { to { transform: rotate(360deg); } }
@@ -20,6 +20,8 @@ export interface DialogFrameProps {
20
20
  closeOnEscape: boolean;
21
21
  /** the v3 API reads Escape from document keydown and focuses the panel */
22
22
  legacy: boolean;
23
+ /** confirmAlert opted into the new default look */
24
+ skin?: boolean | undefined;
23
25
  onKeyDown?: ((event: KeyboardEvent<HTMLElement>, close: () => void) => void) | undefined;
24
26
  onClickOutside?: (() => void) | undefined;
25
27
  onDismiss: () => void;
@@ -0,0 +1,4 @@
1
+ import { type ReactElement } from 'react';
2
+ import type { DialogLayoutProps } from './types';
3
+ /** The default dialog layout for your own content: title, message, your children, then the actions. */
4
+ export declare function DialogLayout({ title, message, actions, children }: DialogLayoutProps): ReactElement;
package/typings/api.d.ts CHANGED
@@ -4,6 +4,7 @@ export declare function Body({ props, children }: {
4
4
  props: DialogRenderProps<any>;
5
5
  children: ReactNode;
6
6
  }): ReactElement;
7
+ export declare function useBusy(id: DialogId | undefined): [number | null, (index: number | null) => void];
7
8
  export declare function Buttons<R>({ props, signal: getSignal, focus }: {
8
9
  props: DialogRenderProps<R>;
9
10
  signal: () => AbortSignal;
@@ -1,4 +1,4 @@
1
- import type { DialogClassNames, DialogDefaults, DialogSlot, StyleOptions } from './types';
1
+ import type { DialogClassNames, DialogDefaults, DialogId, DialogSlot, StyleOptions } from './types';
2
2
  /**
3
3
  * Style layers from widest to narrowest. confirmAlert keeps the v3 look unless the app sets
4
4
  * confirmAlert defaults, so app-wide unstyled or classNames never reach it by surprise.
@@ -12,5 +12,11 @@ export type SlotProps = (slot: DialogSlot, extra?: string) => {
12
12
  export declare const slotsFor: (layers: readonly StyleOptions[]) => SlotProps;
13
13
  export declare const SlotContext: import("react").Context<SlotProps>;
14
14
  export declare const useSlot: () => SlotProps;
15
+ export declare const FrameContext: import("react").Context<{
16
+ id?: DialogId | undefined;
17
+ titleId?: string | undefined;
18
+ inBody?: boolean;
19
+ countTitle?: (change: (count: number) => number) => void;
20
+ }>;
15
21
  export declare function configureDialogs(defaults: DialogDefaults): void;
16
22
  export type { DialogClassNames };
@@ -35,6 +35,8 @@ export interface ReactConfirmAlertProps {
35
35
  unstyled?: boolean;
36
36
  /** classes per slot, added after the app defaults */
37
37
  classNames?: DialogClassNames;
38
+ /** use the new default look instead of the 3.0 one; customUI then sits in the dialog body */
39
+ skin?: boolean;
38
40
  }
39
41
  export type ConfirmAlertOptions = ReactConfirmAlertProps;
40
42
  export declare function confirmAlert(properties: ReactConfirmAlertProps): void;
@@ -5,5 +5,6 @@ export { DialogHost, useDialog } from './DialogHost';
5
5
  export { configureDialogs } from './classNames';
6
6
  export type { DialogHostProps } from './DialogHost';
7
7
  export { ConfirmDialog, Dialog } from './Dialog';
8
+ export { DialogLayout } from './DialogLayout';
8
9
  export type { ConfirmDialogProps, DialogProps } from './Dialog';
9
- export type { AlertOptions, BaseOptions, ButtonContext, ButtonDataAttributes, DialogClassNames, DialogDataAttributes, DialogDefaults, DialogSlot, SlotDataAttributes, StyleOptions, ConfirmOptions, CustomOptions, DialogButton, DialogHandle, DialogId, DialogKind, DialogRenderProps, PromptOptions, Strategy } from './types';
10
+ export type { AlertOptions, BaseOptions, ButtonContext, ButtonDataAttributes, DialogAction, DialogClassNames, DialogLayoutProps, DialogDataAttributes, DialogDefaults, DialogSlot, SlotDataAttributes, StyleOptions, ConfirmOptions, CustomOptions, DialogButton, DialogHandle, DialogId, DialogKind, DialogRenderProps, PromptOptions, Strategy } from './types';
@@ -1,4 +1,4 @@
1
- import type { ButtonHTMLAttributes, ComponentType, InputHTMLAttributes, KeyboardEvent, ReactNode } from 'react';
1
+ import type { ButtonHTMLAttributes, ComponentType, InputHTMLAttributes, KeyboardEvent, MouseEvent, ReactNode } from 'react';
2
2
  export type DialogId = string & {
3
3
  readonly __brand: 'DialogId';
4
4
  };
@@ -22,6 +22,7 @@ export interface DialogRenderProps<R> {
22
22
  descriptionId: string;
23
23
  title?: ReactNode | undefined;
24
24
  message?: ReactNode | undefined;
25
+ content?: ReactNode | undefined;
25
26
  buttons: readonly DialogButton<R>[];
26
27
  close: (value: R) => void;
27
28
  dismiss: () => void;
@@ -53,6 +54,7 @@ export interface DialogDefaults extends StyleOptions {
53
54
  confirmAlert?: (StyleOptions & {
54
55
  modal?: boolean | undefined;
55
56
  strategy?: Strategy | undefined;
57
+ skin?: boolean | undefined;
56
58
  }) | undefined;
57
59
  }
58
60
  /** Attributes on the frame, for CSS and Tailwind data-* variants. A type only, kept in step by tests. */
@@ -75,6 +77,8 @@ export interface ButtonDataAttributes extends SlotDataAttributes {
75
77
  export interface BaseOptions extends StyleOptions {
76
78
  title?: ReactNode | undefined;
77
79
  message?: ReactNode | undefined;
80
+ /** your own content between the message and the buttons */
81
+ content?: ReactNode | undefined;
78
82
  closeOnEscape?: boolean | undefined;
79
83
  closeOnClickOutside?: boolean | undefined;
80
84
  strategy?: Strategy | undefined;
@@ -124,6 +128,8 @@ export interface EntryOptions extends BaseOptions {
124
128
  onClickOutside?: (() => void) | undefined;
125
129
  /** @internal the v3 API: Escape from document keydown, blur on body.children[0] */
126
130
  legacy?: boolean | undefined;
131
+ /** @internal confirmAlert opted into the new default look */
132
+ skin?: boolean | undefined;
127
133
  /** @internal confirm's tone, shown as data-tone on the frame */
128
134
  tone?: 'default' | 'danger' | undefined;
129
135
  /** @internal v3 callback, runs before afterClose when the dialog unmounts */
@@ -136,3 +142,15 @@ export interface DialogHandle<R> {
136
142
  dismiss: () => void;
137
143
  update: (patch: Partial<Omit<CustomOptions<R>, 'kind'>>) => void;
138
144
  }
145
+ export interface DialogAction extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'onClick' | 'children'> {
146
+ label: ReactNode;
147
+ variant?: 'primary' | 'secondary' | 'danger' | undefined;
148
+ /** return a Promise to show the button as busy until it settles; the dialog stays open */
149
+ onClick?: ((event: MouseEvent<HTMLButtonElement>) => void | Promise<unknown>) | undefined;
150
+ }
151
+ export interface DialogLayoutProps {
152
+ title?: ReactNode | undefined;
153
+ message?: ReactNode | undefined;
154
+ actions?: readonly DialogAction[] | undefined;
155
+ children?: ReactNode | undefined;
156
+ }