@ciromaciel/dashboard-kit 1.10.0 → 1.11.1
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/package.json +1 -1
- package/src/components/DeleteModal.jsx +20 -12
- package/src/components/undoable.jsx +131 -0
- package/src/index.js +1 -0
- package/src/tokens.css +9 -0
package/package.json
CHANGED
|
@@ -313,36 +313,44 @@ export function DeleteModal({
|
|
|
313
313
|
* is also the modal's only rule: above it everything that is read
|
|
314
314
|
* and typed, below it the act.
|
|
315
315
|
*
|
|
316
|
-
*
|
|
317
|
-
* destrutivo à esquerda".
|
|
318
|
-
*
|
|
316
|
+
* Until 14/10/2026 the destructive button sat on the LEFT, filled,
|
|
317
|
+
* by the forms rule "destrutivo à esquerda". That rule is for a
|
|
318
|
+
* form whose main act is saving; here deleting IS the act, and the
|
|
319
|
+
* alert rule applies instead.
|
|
319
320
|
*/}
|
|
320
321
|
<Box
|
|
321
322
|
px={{ base: 24, md: 32 }}
|
|
322
323
|
py={20}
|
|
323
324
|
style={{ borderTop: 'var(--rlg-border)' }}
|
|
324
325
|
>
|
|
326
|
+
{/* Cancel on the leading side, the destructive act on the
|
|
327
|
+
trailing one, outlined — Apple's alert rule, decided with
|
|
328
|
+
the Ciro on 14/10/2026. The destructive button keeps its
|
|
329
|
+
colour (it says what kind of act this is) but not the fill
|
|
330
|
+
of a primary action: deleting is never the step the screen
|
|
331
|
+
pushes toward. */}
|
|
325
332
|
<Group
|
|
326
|
-
justify="
|
|
333
|
+
justify="flex-end"
|
|
327
334
|
gap={8}
|
|
328
335
|
>
|
|
336
|
+
<Button
|
|
337
|
+
variant="subtle"
|
|
338
|
+
color="gray.7"
|
|
339
|
+
onClick={onClose}
|
|
340
|
+
disabled={loading}
|
|
341
|
+
>
|
|
342
|
+
Cancelar
|
|
343
|
+
</Button>
|
|
329
344
|
<Button
|
|
330
345
|
className="rlg-danger"
|
|
331
346
|
color="red.7"
|
|
347
|
+
variant="outline"
|
|
332
348
|
onClick={confirm}
|
|
333
349
|
disabled={!canDelete}
|
|
334
350
|
loading={loading}
|
|
335
351
|
>
|
|
336
352
|
{confirmLabel}
|
|
337
353
|
</Button>
|
|
338
|
-
<Button
|
|
339
|
-
variant="subtle"
|
|
340
|
-
color="gray.7"
|
|
341
|
-
onClick={onClose}
|
|
342
|
-
disabled={loading}
|
|
343
|
-
>
|
|
344
|
-
Cancelar
|
|
345
|
-
</Button>
|
|
346
354
|
</Group>
|
|
347
355
|
</Box>
|
|
348
356
|
</Modal>
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { Button, Group, Text } from '@mantine/core'
|
|
2
|
+
import { notifications } from '@mantine/notifications'
|
|
3
|
+
|
|
4
|
+
import { notify } from './notify'
|
|
5
|
+
|
|
6
|
+
/** How long the undo stays on offer. Long enough to read one line and reach the button. */
|
|
7
|
+
export const UNDO_MS = 6000
|
|
8
|
+
|
|
9
|
+
let sequence = 0
|
|
10
|
+
const pending = []
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Delete now, offer Desfazer, send the request only when the offer ends.
|
|
14
|
+
*
|
|
15
|
+
* Apple's rule (decided with the Ciro on 14/10/2026): for an action people
|
|
16
|
+
* perform often, undo beats confirmation. A dialog or a hold is friction paid on
|
|
17
|
+
* every delete to protect the rare mistake; undo charges nothing and still
|
|
18
|
+
* catches the mistake — after it happens, when the person can see it.
|
|
19
|
+
*
|
|
20
|
+
* Nothing is sent to the server while the offer stands. The item leaves the
|
|
21
|
+
* screen (`onHide`), the request (`onCommit`) waits `ms`, and Desfazer only puts
|
|
22
|
+
* the item back (`onRestore`). If the request fails, the item comes back too and
|
|
23
|
+
* the error stays on screen.
|
|
24
|
+
*
|
|
25
|
+
* ⌘Z / Ctrl+Z undoes the latest pending delete, outside text fields — the
|
|
26
|
+
* shortcut every Apple app gives undo.
|
|
27
|
+
*
|
|
28
|
+
* Closing the toast keeps the delete and sends it at once. Leaving the page
|
|
29
|
+
* sends every pending delete (`pagehide`).
|
|
30
|
+
*
|
|
31
|
+
* NOT for credentials or connections: revoking a key or a webhook has to take
|
|
32
|
+
* effect at once, and those keep the hold. See `10-modal-de-exclusao.md`.
|
|
33
|
+
*
|
|
34
|
+
* @param {object} options
|
|
35
|
+
* @param {string} options.message What left, in a few words: "Campo excluído".
|
|
36
|
+
* @param {Function} options.onCommit Sends the delete. May return a promise.
|
|
37
|
+
* @param {Function} [options.onHide] Takes the item off the screen.
|
|
38
|
+
* @param {Function} [options.onRestore] Puts it back.
|
|
39
|
+
* @param {number} [options.ms]
|
|
40
|
+
* @returns {{ undo: Function, commit: Function }}
|
|
41
|
+
*/
|
|
42
|
+
export function undoable({ message, onCommit, onHide, onRestore, ms = UNDO_MS }) {
|
|
43
|
+
const id = `rlg-undo-${++sequence}`
|
|
44
|
+
let isSettled = false
|
|
45
|
+
|
|
46
|
+
const settle = () => {
|
|
47
|
+
isSettled = true
|
|
48
|
+
clearTimeout(timer)
|
|
49
|
+
const index = pending.indexOf(entry)
|
|
50
|
+
if (index >= 0) pending.splice(index, 1)
|
|
51
|
+
notifications.hide(id)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const commit = async () => {
|
|
55
|
+
if (isSettled) return
|
|
56
|
+
settle()
|
|
57
|
+
try {
|
|
58
|
+
await onCommit()
|
|
59
|
+
} catch (error) {
|
|
60
|
+
onRestore?.()
|
|
61
|
+
notify.error('Não excluiu', error?.message || 'Tente de novo.')
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const undo = () => {
|
|
66
|
+
if (isSettled) return
|
|
67
|
+
settle()
|
|
68
|
+
onRestore?.()
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const entry = { undo, commit }
|
|
72
|
+
onHide?.()
|
|
73
|
+
pending.push(entry)
|
|
74
|
+
listen()
|
|
75
|
+
const timer = setTimeout(commit, ms)
|
|
76
|
+
|
|
77
|
+
notifications.show({
|
|
78
|
+
id,
|
|
79
|
+
color: 'gray',
|
|
80
|
+
autoClose: false,
|
|
81
|
+
onClose: commit,
|
|
82
|
+
message: (
|
|
83
|
+
<Group
|
|
84
|
+
justify="space-between"
|
|
85
|
+
wrap="nowrap"
|
|
86
|
+
gap={12}
|
|
87
|
+
>
|
|
88
|
+
<Text
|
|
89
|
+
fz={13}
|
|
90
|
+
c="inherit"
|
|
91
|
+
>
|
|
92
|
+
{message}
|
|
93
|
+
</Text>
|
|
94
|
+
<Button
|
|
95
|
+
size="compact-sm"
|
|
96
|
+
variant="subtle"
|
|
97
|
+
color="gray.9"
|
|
98
|
+
onClick={undo}
|
|
99
|
+
>
|
|
100
|
+
Desfazer
|
|
101
|
+
</Button>
|
|
102
|
+
</Group>
|
|
103
|
+
),
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
return entry
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let isListening = false
|
|
110
|
+
|
|
111
|
+
function listen() {
|
|
112
|
+
if (isListening || typeof window === 'undefined') return
|
|
113
|
+
isListening = true
|
|
114
|
+
|
|
115
|
+
window.addEventListener('keydown', event => {
|
|
116
|
+
if (!(event.metaKey || event.ctrlKey) || event.shiftKey || event.key.toLowerCase() !== 'z') return
|
|
117
|
+
if (pending.length === 0 || isEditable(event.target)) return
|
|
118
|
+
event.preventDefault()
|
|
119
|
+
pending.at(-1).undo()
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
window.addEventListener('pagehide', () => {
|
|
123
|
+
for (const entry of [...pending]) entry.commit()
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** A text field keeps its own ⌘Z: it undoes typing, not a delete. */
|
|
128
|
+
function isEditable(target) {
|
|
129
|
+
if (!target || typeof target.closest !== 'function') return false
|
|
130
|
+
return Boolean(target.closest('input, textarea, select, [contenteditable=""], [contenteditable="true"]'))
|
|
131
|
+
}
|
package/src/index.js
CHANGED
|
@@ -64,6 +64,7 @@ export { DangerZone } from './components/DangerZone'
|
|
|
64
64
|
export { ZenLabel } from './components/ZenLabel'
|
|
65
65
|
export { Tag } from './components/Tag'
|
|
66
66
|
export { notify } from './components/notify'
|
|
67
|
+
export { undoable, UNDO_MS } from './components/undoable'
|
|
67
68
|
export { NOTIFICATIONS_PROPS, NOTIFY_DESCRIPTION_CSS, notificationTheme } from './components/notify-theme'
|
|
68
69
|
export { panelTheme } from './theme'
|
|
69
70
|
export { Wordmark } from './components/Wordmark'
|
package/src/tokens.css
CHANGED
|
@@ -306,3 +306,12 @@
|
|
|
306
306
|
scroll-behavior: auto !important;
|
|
307
307
|
}
|
|
308
308
|
}
|
|
309
|
+
|
|
310
|
+
/* THE BURGER DRAWS WITH THE TEXT INK. Mantine paints it with
|
|
311
|
+
--mantine-color-white in dark mode, and this system remaps white to the
|
|
312
|
+
elevated surface (#1A2127) — the same colour as the bar it sits on, so the
|
|
313
|
+
phone menu button vanished in dark mode (14/10/2026). Doubled class to win
|
|
314
|
+
over Mantine's rule regardless of stylesheet order. */
|
|
315
|
+
.mantine-Burger-root.mantine-Burger-root {
|
|
316
|
+
--burger-color: var(--mantine-color-gray-9);
|
|
317
|
+
}
|