@ciromaciel/dashboard-kit 1.0.4 → 1.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 +2 -1
- package/package.json +2 -2
- package/src/components/DangerZone.css +25 -0
- package/src/components/DangerZone.jsx +106 -0
- package/src/components/DeleteModal.jsx +9 -0
- package/src/components/HoldToConfirm.jsx +2 -0
- package/src/components/SidebarShell.jsx +20 -6
- package/src/index.js +5 -0
- package/src/tokens.css +11 -0
package/README.md
CHANGED
|
@@ -41,7 +41,8 @@ function Projects({ rows, loading }) {
|
|
|
41
41
|
| `SectionCard` · `Field` · `FieldStacked` · `CardActions` | The section card |
|
|
42
42
|
| `PageHeader` | Title and breadcrumb |
|
|
43
43
|
| `HoldToConfirmButton` · `HoldToConfirmIcon` | The reversible destructive confirm |
|
|
44
|
-
| `DeleteModal` · `deleteTitleScale` | The top of the escalation: reversible → hold → this |
|
|
44
|
+
| `DeleteModal` · `deleteTitleScale` | The top of the escalation: reversible → hold → this. `before` holds the way out (backup) |
|
|
45
|
+
| `DangerZone` | The door to `DeleteModal` at the end of a settings page |
|
|
45
46
|
| `AuthLoader` · `AuthScreen` · `AUTH_LABELS` · `AUTH_TEXTS` | The moment the dashboard does not know who you are |
|
|
46
47
|
| `Wordmark` | The mark, in two roles (`variant="mark"` / `"label"`). The name comes by prop |
|
|
47
48
|
| `ZenLabel` · `notify` · `NOTIFICATIONS_PROPS` · `NOTIFY_DESCRIPTION_CSS` · `notificationTheme` | Label and notice |
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ciromaciel/dashboard-kit",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"description": "The components every dashboard shares: table, empty state, loading state, destructive confirm and wordmark.",
|
|
6
|
+
"description": "The components every dashboard shares: table, empty state, loading state, destructive confirm, danger zone and wordmark.",
|
|
7
7
|
"main": "src/index.js",
|
|
8
8
|
"files": [
|
|
9
9
|
"src",
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The row reflows by width: label · description · action on a wide card, one
|
|
3
|
+
* column on a phone. A media query cannot live in inline style, and a
|
|
4
|
+
* `useMediaQuery` would render the wrong layout on the first frame.
|
|
5
|
+
*/
|
|
6
|
+
.rlg-danger-zone {
|
|
7
|
+
border: 1px solid var(--mantine-color-red-2);
|
|
8
|
+
background: var(--mantine-color-body, #fff);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.rlg-danger-row {
|
|
12
|
+
display: grid;
|
|
13
|
+
grid-template-columns: minmax(0, 1fr);
|
|
14
|
+
gap: 10px;
|
|
15
|
+
align-items: center;
|
|
16
|
+
padding: 16px;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@media (min-width: 48em) {
|
|
20
|
+
.rlg-danger-row {
|
|
21
|
+
grid-template-columns: minmax(160px, 220px) minmax(0, 1fr) auto;
|
|
22
|
+
gap: 8px 24px;
|
|
23
|
+
padding: 16px 20px;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Box, Button, Group, Stack, Text } from '@mantine/core'
|
|
2
|
+
import { IconAlertTriangle, IconTrash } from '@tabler/icons-react'
|
|
3
|
+
|
|
4
|
+
import './DangerZone.css'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The danger zone — the door to an irreversible action on a settings page.
|
|
8
|
+
*
|
|
9
|
+
* An audit found six versions across seven panels: "Danger Zone" in English,
|
|
10
|
+
* "Zona de perigo", "Zona de risco" with a gray eyebrow, red borders from red.2
|
|
11
|
+
* to red.6, filled, light and outline buttons, and one panel with no zone at
|
|
12
|
+
* all — the delete hidden inside a collapsed "advanced" card. None of it
|
|
13
|
+
* encoded a difference in the product.
|
|
14
|
+
*
|
|
15
|
+
* The door says what happens in one sentence and nothing else. The inventory
|
|
16
|
+
* and the backup exit belong to `DeleteModal`, which measures on open; showing
|
|
17
|
+
* them here too would put the same list in two places and fetch it on every
|
|
18
|
+
* visit to a page people open to rename things.
|
|
19
|
+
*
|
|
20
|
+
* @param {object} props
|
|
21
|
+
* @param {string} props.title The action, verb first: "Excluir projeto".
|
|
22
|
+
* @param {string} props.description What is lost, in one sentence.
|
|
23
|
+
* @param {Function} props.onAction Opens the confirmation. Never deletes directly.
|
|
24
|
+
* @param {string} props.actionLabel Defaults to `${title}…` — the ellipsis says a step follows.
|
|
25
|
+
* @param {string} props.hint
|
|
26
|
+
* @param {boolean} props.disabled
|
|
27
|
+
* @param {boolean} props.loading
|
|
28
|
+
* @param {string} props.eyebrow Same word as DeleteModal's.
|
|
29
|
+
*/
|
|
30
|
+
export function DangerZone({ title, description, onAction, actionLabel = `${title}…`, hint = 'Irreversível', disabled = false, loading = false, eyebrow = 'Zona de risco' }) {
|
|
31
|
+
return (
|
|
32
|
+
<Box
|
|
33
|
+
className="rlg-danger-zone"
|
|
34
|
+
component="section"
|
|
35
|
+
aria-label={eyebrow}
|
|
36
|
+
>
|
|
37
|
+
<Group
|
|
38
|
+
gap={6}
|
|
39
|
+
wrap="nowrap"
|
|
40
|
+
px={{ base: 16, sm: 20 }}
|
|
41
|
+
pt={14}
|
|
42
|
+
>
|
|
43
|
+
<IconAlertTriangle
|
|
44
|
+
size={13}
|
|
45
|
+
stroke={2.5}
|
|
46
|
+
style={{ color: 'var(--mantine-color-red-6)', flex: 'none' }}
|
|
47
|
+
/>
|
|
48
|
+
<Text
|
|
49
|
+
fz={10}
|
|
50
|
+
fw={800}
|
|
51
|
+
c="red.6"
|
|
52
|
+
tt="uppercase"
|
|
53
|
+
lts="1.5px"
|
|
54
|
+
>
|
|
55
|
+
{eyebrow}
|
|
56
|
+
</Text>
|
|
57
|
+
</Group>
|
|
58
|
+
|
|
59
|
+
<Box className="rlg-danger-row">
|
|
60
|
+
<Stack gap={2}>
|
|
61
|
+
<Text
|
|
62
|
+
fz={13}
|
|
63
|
+
fw={800}
|
|
64
|
+
c="gray.9"
|
|
65
|
+
>
|
|
66
|
+
{title}
|
|
67
|
+
</Text>
|
|
68
|
+
{hint ? (
|
|
69
|
+
<Text
|
|
70
|
+
fz={12}
|
|
71
|
+
c="gray.5"
|
|
72
|
+
lh={1.4}
|
|
73
|
+
>
|
|
74
|
+
{hint}
|
|
75
|
+
</Text>
|
|
76
|
+
) : null}
|
|
77
|
+
</Stack>
|
|
78
|
+
|
|
79
|
+
<Text
|
|
80
|
+
fz={13}
|
|
81
|
+
c="gray.6"
|
|
82
|
+
lh={1.5}
|
|
83
|
+
>
|
|
84
|
+
{description}
|
|
85
|
+
</Text>
|
|
86
|
+
|
|
87
|
+
<Button
|
|
88
|
+
className="rlg-danger"
|
|
89
|
+
color="red.7"
|
|
90
|
+
variant="outline"
|
|
91
|
+
leftSection={
|
|
92
|
+
<IconTrash
|
|
93
|
+
size={14}
|
|
94
|
+
stroke={1.5}
|
|
95
|
+
/>
|
|
96
|
+
}
|
|
97
|
+
onClick={onAction}
|
|
98
|
+
disabled={disabled}
|
|
99
|
+
loading={loading}
|
|
100
|
+
>
|
|
101
|
+
{actionLabel}
|
|
102
|
+
</Button>
|
|
103
|
+
</Box>
|
|
104
|
+
</Box>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
@@ -88,6 +88,7 @@ export function deleteTitleScale(value) {
|
|
|
88
88
|
* @param {boolean} props.loading
|
|
89
89
|
* @param {boolean} props.caseSensitive See `matches` below.
|
|
90
90
|
* @param {string} props.eyebrow
|
|
91
|
+
* @param {ReactNode} props.before The way out before confirming — a backup, an export.
|
|
91
92
|
*/
|
|
92
93
|
export function DeleteModal({
|
|
93
94
|
opened,
|
|
@@ -102,6 +103,7 @@ export function DeleteModal({
|
|
|
102
103
|
loading = false,
|
|
103
104
|
caseSensitive = false,
|
|
104
105
|
eyebrow = 'Zona de risco',
|
|
106
|
+
before = null,
|
|
105
107
|
}) {
|
|
106
108
|
const [typed, setTyped] = useState('')
|
|
107
109
|
|
|
@@ -232,6 +234,12 @@ export function DeleteModal({
|
|
|
232
234
|
</Box>
|
|
233
235
|
) : null}
|
|
234
236
|
|
|
237
|
+
{/*
|
|
238
|
+
* The exit comes BEFORE the field: offering the backup after
|
|
239
|
+
* the confirmation is offering it too late.
|
|
240
|
+
*/}
|
|
241
|
+
{before ? <Box mt={24}>{before}</Box> : null}
|
|
242
|
+
|
|
235
243
|
{/*
|
|
236
244
|
* No rule before the confirmation — the footer's is the only
|
|
237
245
|
* one. Two hairlines inside 520px cut the modal into three
|
|
@@ -319,6 +327,7 @@ export function DeleteModal({
|
|
|
319
327
|
gap={8}
|
|
320
328
|
>
|
|
321
329
|
<Button
|
|
330
|
+
className="rlg-danger"
|
|
322
331
|
color="red.7"
|
|
323
332
|
onClick={confirm}
|
|
324
333
|
disabled={!canDelete}
|
|
@@ -105,6 +105,7 @@ export function HoldToConfirmIcon({ icon, label, onConfirm, disabled, color = 'r
|
|
|
105
105
|
/>
|
|
106
106
|
)}
|
|
107
107
|
<ActionIcon
|
|
108
|
+
className="rlg-danger"
|
|
108
109
|
color={active ? color : 'gray'}
|
|
109
110
|
aria-label={label}
|
|
110
111
|
disabled={disabled}
|
|
@@ -128,6 +129,7 @@ export function HoldToConfirmButton({ children, holdingLabel, onConfirm, disable
|
|
|
128
129
|
|
|
129
130
|
return (
|
|
130
131
|
<Button
|
|
132
|
+
className="rlg-danger"
|
|
131
133
|
color="red.6"
|
|
132
134
|
disabled={disabled}
|
|
133
135
|
loading={loading}
|
|
@@ -162,14 +162,28 @@ export function SidebarShell({
|
|
|
162
162
|
planos. Escondê-lo deixaria o limite invisível, e a pessoa
|
|
163
163
|
procuraria o botão em vez de descobrir que precisa de
|
|
164
164
|
upgrade.
|
|
165
|
+
|
|
166
|
+
O CONTORNO É O QUE O SEPARA DA NAVEGAÇÃO. Na coluna, sem
|
|
167
|
+
moldura é um lugar para ir, preenchido de preto é onde você
|
|
168
|
+
está, e com contorno é algo que você cria. Preto sólido
|
|
169
|
+
diria "você está aqui"; cor gastaria a única exceção do
|
|
170
|
+
sistema, o vermelho de perda. A borda é a mesma linha fina
|
|
171
|
+
dos contadores ao lado dos itens.
|
|
165
172
|
*/}
|
|
166
173
|
{onCreate && (
|
|
167
|
-
<
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
174
|
+
<Box
|
|
175
|
+
w="100%"
|
|
176
|
+
mb={4}
|
|
177
|
+
data-create
|
|
178
|
+
style={{ border: '1px solid var(--mantine-color-gray-3)' }}
|
|
179
|
+
>
|
|
180
|
+
<NavItem
|
|
181
|
+
icon={createIcon}
|
|
182
|
+
label={<span style={{ fontWeight: 700 }}>{createAllowed ? createLabel : createBlockedLabel}</span>}
|
|
183
|
+
muted={!createAllowed}
|
|
184
|
+
onClick={createAllowed ? onCreate : onCreateBlocked}
|
|
185
|
+
/>
|
|
186
|
+
</Box>
|
|
173
187
|
)}
|
|
174
188
|
|
|
175
189
|
{groupLabel && <GroupLabel>{groupLabel}</GroupLabel>}
|
package/src/index.js
CHANGED
|
@@ -56,6 +56,11 @@ export { HoldToConfirmIcon, HoldToConfirmButton } from './components/HoldToConfi
|
|
|
56
56
|
// of them across the seven panels and no two alike, so the shape lives here
|
|
57
57
|
// now. `design-system/10-modal-de-exclusao.md` says why.
|
|
58
58
|
export { DeleteModal, deleteTitleScale } from './components/DeleteModal'
|
|
59
|
+
|
|
60
|
+
// ── The danger zone ──────────────────────────────────────────────────────
|
|
61
|
+
// The door to the modal above, at the end of a settings page. One sentence and
|
|
62
|
+
// one button; the inventory and the backup live in the modal.
|
|
63
|
+
export { DangerZone } from './components/DangerZone'
|
|
59
64
|
export { ZenLabel } from './components/ZenLabel'
|
|
60
65
|
export { notify } from './components/notify'
|
|
61
66
|
export { NOTIFICATIONS_PROPS, NOTIFY_DESCRIPTION_CSS, notificationTheme } from './components/notify-theme'
|
package/src/tokens.css
CHANGED
|
@@ -45,3 +45,14 @@
|
|
|
45
45
|
*/
|
|
46
46
|
--rlg-status-up: #16a34a;
|
|
47
47
|
}
|
|
48
|
+
|
|
49
|
+
/*
|
|
50
|
+
* The focus ring of a destructive control is red.
|
|
51
|
+
*
|
|
52
|
+
* Mantine draws every ring in the primary colour — gray.9 in all seven themes —
|
|
53
|
+
* so a red outline button showed a black ring the moment focus returned to it
|
|
54
|
+
* after closing its modal. `.rlg-danger` marks the kit's destructive controls.
|
|
55
|
+
*/
|
|
56
|
+
.rlg-danger:focus-visible {
|
|
57
|
+
outline-color: var(--mantine-color-red-7);
|
|
58
|
+
}
|