rl-core-front 0.16.5 → 0.16.6

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": "rl-core-front",
3
- "version": "0.16.5",
3
+ "version": "0.16.6",
4
4
  "description": "Telas e componentes Next.js do core: login com 2FA, usuários, RBAC, auditoria, logs e listagens com filtro dinâmico",
5
5
  "author": "Rodrigo Liberti",
6
6
  "license": "MIT",
@@ -38,6 +38,18 @@ export interface DatePickerProps {
38
38
  /** Descreve o campo para quem usa leitor de tela. */
39
39
  label?: string;
40
40
  placeholder?: string;
41
+ /**
42
+ * O primeiro dia escolhível (`"yyyy-MM-dd"`) — os anteriores ficam apagados
43
+ * no calendário e o digitado é recusado.
44
+ *
45
+ * Existe porque avisar depois não serve para data: o vencimento anterior à
46
+ * compra e o pagamento anterior ao lançamento são erros que a pessoa comete
47
+ * sem perceber, e descobrir no botão Salvar é refazer o caminho. Aqui o dia
48
+ * simplesmente não se deixa escolher.
49
+ */
50
+ minDay?: string;
51
+ /** O último dia escolhível, pela mesma razão. */
52
+ maxDay?: string;
41
53
  }
42
54
 
43
55
  /**
@@ -69,6 +81,8 @@ export function DatePicker({
69
81
  disabled,
70
82
  label,
71
83
  placeholder,
84
+ minDay,
85
+ maxDay,
72
86
  }: DatePickerProps): JSX.Element {
73
87
  const { t, locale } = useI18n();
74
88
  const [open, setOpen] = useState(false);
@@ -104,8 +118,24 @@ export function DatePicker({
104
118
  caret.current = null;
105
119
  });
106
120
 
121
+ /**
122
+ * O dia está dentro da faixa permitida?
123
+ *
124
+ * Comparação de texto ISO, e não de `Date`: `2026-09-03 < 2026-09-08` é
125
+ * verdade em ordem alfabética porque o formato é fixo, e assim não há
126
+ * conversão de fuso no meio para transformar o dia 1º no dia 30.
127
+ */
128
+ const dayAllowed = (iso: string): boolean =>
129
+ (!minDay || iso >= minDay) && (!maxDay || iso <= maxDay);
130
+
107
131
  const pickDay = (day: Date): void => {
108
- onChange(toIsoDay(day));
132
+ const iso = toIsoDay(day);
133
+
134
+ if (!dayAllowed(iso)) {
135
+ return;
136
+ }
137
+
138
+ onChange(iso);
109
139
  setTyped(null);
110
140
  setOpen(false);
111
141
  };
@@ -129,7 +159,9 @@ export function DatePicker({
129
159
  (selected ?? today()).getFullYear(),
130
160
  );
131
161
 
132
- if (iso) {
162
+ // Digitar é o outro caminho para o mesmo campo: sem esta guarda, o dia que
163
+ // o calendário recusa entraria pelo teclado.
164
+ if (iso && dayAllowed(iso)) {
133
165
  onChange(iso);
134
166
  setMonth(fromIsoDay(iso) ?? month);
135
167
  }
@@ -180,6 +212,11 @@ export function DatePicker({
180
212
  };
181
213
 
182
214
  const dayClass = (day: Date): string => {
215
+ if (!dayAllowed(toIsoDay(day))) {
216
+ // Continua desenhado, e não escondido: o calendário sem os dias 1 a 7
217
+ // pareceria quebrado, e é o cinza que diz "existe, mas não aqui".
218
+ return "text-muted-foreground/30 line-through";
219
+ }
183
220
  if (selected && isSameDay(day, selected)) {
184
221
  return "bg-primary text-primary-foreground font-semibold";
185
222
  }
@@ -285,9 +322,13 @@ export function DatePicker({
285
322
  <button
286
323
  key={day.toISOString()}
287
324
  type="button"
325
+ disabled={!dayAllowed(toIsoDay(day))}
288
326
  onClick={() => pickDay(day)}
289
327
  className={cn(
290
- "flex h-8 w-8 items-center justify-center rounded-full text-sm transition-colors hover:bg-accent",
328
+ "flex h-8 w-8 items-center justify-center rounded-full text-sm transition-colors",
329
+ dayAllowed(toIsoDay(day))
330
+ ? "hover:bg-accent"
331
+ : "cursor-not-allowed hover:bg-transparent",
291
332
  dayClass(day),
292
333
  )}
293
334
  >
@@ -19,6 +19,7 @@ import {
19
19
  DialogHeader,
20
20
  DialogTitle,
21
21
  Field,
22
+ HoverTip,
22
23
  Input,
23
24
  Label,
24
25
  } from "#core/components/ui";
@@ -151,23 +152,29 @@ export function RolesPanel(): JSX.Element {
151
152
  {canManage && (
152
153
  <div className="flex">
153
154
  {hasPermission("roles:update:any") && (
154
- <Button
155
- variant="ghost"
156
- size="icon"
157
- onClick={() => openEdit(role)}
158
- >
159
- <Pencil className="h-4 w-4" />
160
- </Button>
155
+ <HoverTip label={t("common.edit")}>
156
+ <Button
157
+ aria-label={t("common.edit")}
158
+ variant="ghost"
159
+ size="icon"
160
+ onClick={() => openEdit(role)}
161
+ >
162
+ <Pencil className="h-4 w-4" />
163
+ </Button>
164
+ </HoverTip>
161
165
  )}
162
166
  {hasPermission("roles:delete:any") && (
163
- <Button
164
- variant="ghost"
165
- size="icon"
166
- className="text-destructive"
167
- onClick={() => remove(role)}
168
- >
169
- <Trash2 className="h-4 w-4" />
170
- </Button>
167
+ <HoverTip label={t("common.delete")}>
168
+ <Button
169
+ aria-label={t("common.delete")}
170
+ variant="ghost"
171
+ size="icon"
172
+ className="text-destructive"
173
+ onClick={() => remove(role)}
174
+ >
175
+ <Trash2 className="h-4 w-4" />
176
+ </Button>
177
+ </HoverTip>
171
178
  )}
172
179
  </div>
173
180
  )}