@ciromaciel/dashboard-kit 1.6.0 → 1.7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ciromaciel/dashboard-kit",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "The components every dashboard shares: table, empty state, loading state, destructive confirm, danger zone and wordmark.",
@@ -0,0 +1,56 @@
1
+ import { ActionIcon, Tooltip, useMantineColorScheme } from '@mantine/core'
2
+ import { useMounted } from '@mantine/hooks'
3
+ import { IconDeviceDesktop, IconMoon, IconSun } from '@tabler/icons-react'
4
+
5
+ /**
6
+ * The theme button — one icon that cycles Automático → Claro → Escuro
7
+ * (decided 14/10/2026).
8
+ *
9
+ * Automático follows the device and is the default for anyone who never chose.
10
+ * A single button hides its state in the icon, so the state is said in words
11
+ * twice: the tooltip and the accessible name both read "Tema: Claro. Mudar
12
+ * para Escuro." — what it is now, and what one click does.
13
+ *
14
+ * The icon waits for the mount. Mantine reads the stored choice from
15
+ * localStorage on the client only; a prerendered page would otherwise paint the
16
+ * server's icon and swap it on hydration.
17
+ *
18
+ * The same component lives in the components-kit for the sites; the two must
19
+ * move together.
20
+ */
21
+ const NEXT = { auto: 'light', light: 'dark', dark: 'auto' }
22
+ const NAME = { auto: 'Automático', light: 'Claro', dark: 'Escuro' }
23
+ const ICON = { auto: IconDeviceDesktop, light: IconSun, dark: IconMoon }
24
+
25
+ export function ColorSchemeToggle({ size = 36, ...props }) {
26
+ const { colorScheme, setColorScheme } = useMantineColorScheme()
27
+ const isMounted = useMounted()
28
+ const current = isMounted ? colorScheme : 'auto'
29
+ const Icon = ICON[current]
30
+ const label = `Tema: ${NAME[current]}. Mudar para ${NAME[NEXT[current]]}.`
31
+
32
+ return (
33
+ <Tooltip
34
+ label={label}
35
+ position="top"
36
+ withArrow={false}
37
+ >
38
+ <ActionIcon
39
+ data-color-scheme-toggle
40
+ variant="subtle"
41
+ color="gray.9"
42
+ size={size}
43
+ radius={0}
44
+ aria-label={label}
45
+ onClick={() => setColorScheme(NEXT[current])}
46
+ {...props}
47
+ >
48
+ <Icon
49
+ size={18}
50
+ stroke={1.5}
51
+ aria-hidden
52
+ />
53
+ </ActionIcon>
54
+ </Tooltip>
55
+ )
56
+ }
@@ -1,9 +1,10 @@
1
1
  import { ActionIcon, Box, Group, ScrollArea, Stack, Text, TextInput, Tooltip } from '@mantine/core'
2
- import { IconSearch, IconX } from '@tabler/icons-react'
2
+ import { IconPlus, IconSearch, IconX } from '@tabler/icons-react'
3
3
 
4
4
  import { GroupLabel } from './GroupLabel'
5
5
  import { ListSkeleton } from './ListSkeleton'
6
6
  import { NavItem } from './NavItem'
7
+ import { ColorSchemeToggle } from './ColorSchemeToggle'
7
8
 
8
9
  /**
9
10
  * A coluna lateral de um painel, por CONFIGURAÇÃO em vez de por cópia.
@@ -39,8 +40,13 @@ import { NavItem } from './NavItem'
39
40
  * Nada de quota entra aqui. O painel resolve o gate e passa `createAllowed`
40
41
  * booleano — a coluna não sabe o que é um plano.
41
42
  */
42
- /** O botão de criar na linha da busca: quadrado de 36px, preto; em contorno cinza no limite do plano. */
43
- function CreateSquare({ icon: Icon, label, isAllowed, onClick }) {
43
+ /**
44
+ * O botão de criar na linha da busca: quadrado de 36px, preto, colado à busca;
45
+ * em contorno cinza quando o plano chegou ao limite. É a forma única do "+" em
46
+ * todo painel — exportado como `CreateButton` para as colunas que ainda não
47
+ * são o SidebarShell. Nunca um item de lista "+ Novo …" abaixo da busca.
48
+ */
49
+ export function CreateButton({ icon: Icon = IconPlus, label, isAllowed = true, onClick }) {
44
50
  return (
45
51
  <Tooltip
46
52
  label={label}
@@ -196,7 +202,7 @@ export function SidebarShell({
196
202
  }
197
203
  styles={{ input: { height: 36, minHeight: 36, fontSize: 13, background: 'var(--mantine-color-gray-1)' } }}
198
204
  />
199
- {onCreate && <CreateSquare {...criar} />}
205
+ {onCreate && <CreateButton {...criar} />}
200
206
  </Group>
201
207
  )}
202
208
 
@@ -301,15 +307,21 @@ export function SidebarShell({
301
307
  menu dele, junto de Conta e Assinatura. Na coluna, competiriam com
302
308
  a lista — que é o assunto da coluna.
303
309
  */}
304
- {footer && (
305
- <Box
306
- pt={16}
307
- w="100%"
308
- style={{ borderTop: 'var(--rlg-border)' }}
309
- >
310
- {footer}
311
- </Box>
312
- )}
310
+ {/*
311
+ O botão de tema mora ao lado do cartão: é preferência da pessoa,
312
+ como Conta, e não compete com a lista. Sem cartão, ele fica só.
313
+ */}
314
+ <Group
315
+ pt={16}
316
+ w="100%"
317
+ gap={8}
318
+ wrap="nowrap"
319
+ align="center"
320
+ style={{ borderTop: 'var(--rlg-border)' }}
321
+ >
322
+ {footer && <Box style={{ flex: 1, minWidth: 0 }}>{footer}</Box>}
323
+ <ColorSchemeToggle style={footer ? undefined : { marginLeft: 'auto' }} />
324
+ </Group>
313
325
 
314
326
  {children}
315
327
  </Stack>
@@ -17,13 +17,18 @@ import { IconCopy, IconCheck } from '@tabler/icons-react'
17
17
  * usa em FeaturesGrid layout="split"), para que painel e site mostrem código
18
18
  * com a mesma cara. Os valores vieram de lá, não foram escolhidos aqui.
19
19
  *
20
- * O fundo é gray.9 — a única superfície escura do sistema. Ela se justifica
20
+ * O fundo é #11181C — a única superfície escura do sistema. Ela se justifica
21
21
  * porque código é conteúdo citado, não interface: o contraste invertido marca
22
22
  * "isto é a máquina falando", e é o mesmo tratamento de qualquer terminal que
23
23
  * o desenvolvedor já tem aberto ao lado.
24
+ *
25
+ * É LITERAL, não `var(--mantine-color-gray-9)`, de propósito: o terminal é
26
+ * escuro nos dois temas. No modo escuro o gray.9 vira claro, e o texto branco
27
+ * daqui ficaria branco sobre branco. A borda é `--rlg-shell-edge`, que no
28
+ * escuro clareia para o terminal não se fundir com a página.
24
29
  */
25
30
  const DARK = {
26
- shell: 'var(--mantine-color-gray-9)',
31
+ shell: '#11181C',
27
32
  bar: 'rgba(255, 255, 255, 0.06)',
28
33
  /*
29
34
  * O token vem de `tokens.css` do kit, com fallback ao literal que as
@@ -51,7 +56,7 @@ export function Terminal({ children, label, copy = true, actions, style }) {
51
56
  return (
52
57
  <Box
53
58
  style={{
54
- border: '1px solid var(--mantine-color-gray-9)',
59
+ border: '1px solid var(--rlg-shell-edge, #11181C)',
55
60
  background: DARK.shell,
56
61
  overflow: 'hidden',
57
62
  ...style,
@@ -168,7 +173,7 @@ export function TerminalOutput({ children, label = 'output', tone = 'default' })
168
173
  return (
169
174
  <Box
170
175
  style={{
171
- border: '1px solid var(--mantine-color-gray-9)',
176
+ border: '1px solid var(--rlg-shell-edge, #11181C)',
172
177
  borderTop: 'none',
173
178
  background: DARK.shell,
174
179
  overflow: 'hidden',
package/src/index.js CHANGED
@@ -84,7 +84,8 @@ export { ActiveResource } from './components/ActiveResource'
84
84
  export { ColumnBrand } from './components/ColumnBrand'
85
85
  export { ListSkeleton } from './components/ListSkeleton'
86
86
  export { DashboardFrame, SIDEBAR_WIDTH } from './components/DashboardFrame'
87
- export { SidebarShell } from './components/SidebarShell'
87
+ export { SidebarShell, CreateButton } from './components/SidebarShell'
88
+ export { ColorSchemeToggle } from './components/ColorSchemeToggle'
88
89
  export { AUTH_LABELS, AUTH_TEXTS } from './auth-labels'
89
90
  export { CardSkeleton } from './components/CardSkeleton'
90
91
  export { MoreResults } from './components/MoreResults'
package/src/theme.js CHANGED
@@ -98,6 +98,11 @@ export const panelTheme = createTheme({
98
98
  * disc, bar); green.7 is the ink for text on white (5.0:1). Like red,
99
99
  * it never appears without the word or the glyph beside it.
100
100
  */
101
+ /* A rampa que o Mantine usa para as próprias superfícies no escuro
102
+ (corpo, campo, botão default, texto apagado). São os mesmos valores
103
+ do bloco escuro do CSS: dark.7 é a base, dark.6 a superfície
104
+ elevada, dark.4 a borda, dark.0–3 as tintas. */
105
+ dark: ['#D3D8DE', '#B6BEC8', '#98A1AC', '#7D8794', '#2F3840', '#2A333B', '#1A2127', '#0B0F12', '#080B0D', '#050708'],
101
106
  green: ['#F0FDF4', '#DCFCE7', '#BBF7D0', '#86EFAC', '#4ADE80', '#22C55E', '#16A34A', '#15803D', '#166534', '#14532D'],
102
107
 
103
108
  red: ['#FFF5F5', '#FFE3E3', '#FFC9C9', '#FFA8A8', '#FF8787', '#FF6B6B', '#E03131', '#C92A2A', '#B02525', '#962020'],
package/src/tokens.css CHANGED
@@ -90,7 +90,18 @@
90
90
  * runtime, after this sheet.
91
91
  */
92
92
  @media (prefers-contrast: more) {
93
- :root:root {
93
+ :root:root:root[data-mantine-color-scheme='dark'] {
94
+ /* No escuro o deslocamento é para o claro: o filete aparece, o label
95
+ e a legenda sobem um degrau. */
96
+ --mantine-color-gray-2: #3a444d;
97
+ --mantine-color-gray-3: #4b5563;
98
+ --mantine-color-gray-4: #b6bec8;
99
+ --mantine-color-gray-5: #d3d8de;
100
+ --mantine-color-gray-6: #e5e7eb;
101
+ }
102
+
103
+ :root:root[data-mantine-color-scheme='light'],
104
+ :root:root:not([data-mantine-color-scheme]) {
94
105
  --mantine-color-gray-2: #9ca3af;
95
106
  --mantine-color-gray-3: #9ca3af;
96
107
  --mantine-color-gray-4: #4b5563;
@@ -122,3 +133,96 @@
122
133
  :root:root[data-mantine-color-scheme='light'] {
123
134
  --mantine-color-text: var(--mantine-color-gray-6);
124
135
  }
136
+
137
+ /* ===========================================================================
138
+ * MODO ESCURO (14/10/2026) — a rampa por elevação.
139
+ *
140
+ * A regra da Apple: a base recua e cada camada que sobe clareia — página,
141
+ * cartão e modal, menu aberto. Não é a rampa invertida (a proposta antiga
142
+ * espelhava gray.0 ↔ gray.9, e o modal herdava a cor exata da página).
143
+ *
144
+ * Os NOMES não mudam: gray.0 continua sendo "base", gray.9 continua sendo
145
+ * "título". Muda o valor que cada papel recebe, e todo `c="gray.6"` e
146
+ * `var(--mantine-color-gray-2)` já escrito acompanha sozinho. O Mantine monta
147
+ * `--mantine-color-gray-filled` e afins como `var(--mantine-color-gray-9)`,
148
+ * então o botão preenchido também vira.
149
+ *
150
+ * "white" vira a superfície elevada (#1A2127), e isso resolve duas coisas
151
+ * de uma vez: o Paper `bg="white"` sobe um degrau acima da página, e o texto
152
+ * que o autoContrast pôs em "white" sobre um preenchimento gray.9 — agora
153
+ * claro — fica escuro e legível.
154
+ *
155
+ * O que NÃO acompanha é o hexadecimal escrito à mão. Superfícies que são
156
+ * escuras nos dois temas (terminal, bloco de código, player) escrevem as
157
+ * suas cores literais de propósito; todo o resto usa o token.
158
+ *
159
+ * Mesmo bloco no global.css do components-kit e no tokens.css do
160
+ * dashboard-kit — os dois andam juntos.
161
+ * =========================================================================== */
162
+ :root:root[data-mantine-color-scheme='dark'] {
163
+ color-scheme: dark;
164
+
165
+ --mantine-color-gray-0: #0b0f12; /* base da página */
166
+ --mantine-color-gray-1: #12181d; /* palco alternado, hover */
167
+ --mantine-color-gray-2: #222a31; /* filete */
168
+ --mantine-color-gray-3: #2f3840; /* borda forte, desabilitado */
169
+ --mantine-color-gray-4: #7d8794; /* label caixa alta — 5,3:1 */
170
+ --mantine-color-gray-5: #98a1ac; /* legenda — 7,4:1 */
171
+ --mantine-color-gray-6: #b6bec8; /* corpo — 10,3:1 */
172
+ --mantine-color-gray-7: #d3d8de; /* dado — 13,4:1 */
173
+ --mantine-color-gray-8: #e5e7eb;
174
+ --mantine-color-gray-9: #f3f4f6; /* título, botão — 17,5:1 */
175
+
176
+ --mantine-color-white: #1a2127; /* superfície elevada; tinta sobre preenchimento claro */
177
+ --mantine-color-body: #0b0f12;
178
+ --mantine-color-text: var(--mantine-color-gray-6);
179
+ --mantine-color-dimmed: var(--mantine-color-gray-5);
180
+ --mantine-color-placeholder: var(--mantine-color-gray-4);
181
+ --mantine-color-default: #1a2127;
182
+ --mantine-color-default-hover: #232b32;
183
+ --mantine-color-default-color: var(--mantine-color-gray-9);
184
+ --mantine-color-default-border: var(--mantine-color-gray-3);
185
+
186
+ /* Vermelho e verde clareiam: sobre a base escura, o red.6 (#E03131) e o
187
+ green.6 (#16A34A) do claro ficam abaixo de 4,5:1 como texto. */
188
+ --mantine-color-red-0: #2a1416;
189
+ --mantine-color-red-1: #3a1a1d;
190
+ --mantine-color-red-2: #5a2427;
191
+ --mantine-color-red-6: #ff6b6b;
192
+ --mantine-color-red-7: #ff8787;
193
+ --mantine-color-red-filled: #ff6b6b;
194
+ --mantine-color-red-filled-hover: #ff8787;
195
+ --mantine-color-green-6: #22c55e;
196
+ --mantine-color-green-7: #4ade80;
197
+
198
+ --rlg-elevated: #1a2127; /* modal, gaveta */
199
+ --rlg-elevated-2: #232b32; /* menu, popover, lista suspensa, notificação */
200
+ --rlg-shell-edge: #2f3840; /* borda do terminal, escuro nos dois temas */
201
+ }
202
+
203
+ /* As camadas que sobem. No claro todas são brancas e a borda separa; no
204
+ escuro cada uma clareia um degrau. */
205
+ :root[data-mantine-color-scheme='dark'] .mantine-Modal-content,
206
+ :root[data-mantine-color-scheme='dark'] .mantine-Modal-header,
207
+ :root[data-mantine-color-scheme='dark'] .mantine-Drawer-content,
208
+ :root[data-mantine-color-scheme='dark'] .mantine-Drawer-header {
209
+ background-color: var(--rlg-elevated);
210
+ }
211
+ :root[data-mantine-color-scheme='dark'] .mantine-Popover-dropdown,
212
+ :root[data-mantine-color-scheme='dark'] .mantine-Menu-dropdown,
213
+ :root[data-mantine-color-scheme='dark'] .mantine-Combobox-dropdown,
214
+ :root[data-mantine-color-scheme='dark'] .mantine-HoverCard-dropdown,
215
+ :root[data-mantine-color-scheme='dark'] .mantine-Notification-root {
216
+ background-color: var(--rlg-elevated-2);
217
+ }
218
+
219
+ /* Imagens. A ilustração da casa é monocromática por regra (traço na rampa
220
+ cinza), então no escuro ela inverte: o traço preto vira claro e o fundo
221
+ branco some na página. Foto e captura só baixam o brilho, como a Apple
222
+ pede para imagem de fundo branco. `data-keep-colors` desliga as duas. */
223
+ :root[data-mantine-color-scheme='dark'] img[src$='.svg']:not([data-keep-colors]) {
224
+ filter: invert(1) hue-rotate(180deg);
225
+ }
226
+ :root[data-mantine-color-scheme='dark'] img:not([src$='.svg']):not([data-keep-colors]) {
227
+ filter: brightness(0.88);
228
+ }