@softize/opus 13.0.0 → 13.1.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/CHANGELOG.md +47 -0
- package/PROMOTED.md +46 -0
- package/README.md +28 -19
- package/bin/cli.mjs +87 -216
- package/bin/lib/cli-shared.mjs +131 -0
- package/bin/lib/db.mjs +16 -74
- package/bin/lib/gen-openapi.mjs +3 -3
- package/bin/lib/gen-runner.mjs +1 -1
- package/bin/lib/gen.mjs +14 -69
- package/bin/lib/mcp.mjs +3 -1
- package/bin/lib/seed.mjs +5 -62
- package/docs/ownership-vs-shadcn-lock.md +2 -3
- package/docs/protocol.md +7 -7
- package/docs/releasing.md +8 -2
- package/package.json +7 -3
- package/registry/templates/app/package.json +1 -1
- package/registry/templates/app/src/main.tsx +4 -4
- package/src/audit/drivers/console.ts +1 -0
- package/src/auth/drivers/better-auth.ts +1 -0
- package/src/auth/drivers/jwt.ts +1 -0
- package/src/cache/drivers/memory.ts +1 -0
- package/src/client/drivers/fetch.ts +2 -1
- package/src/core/actions.ts +6 -1
- package/src/core/audit.ts +9 -3
- package/src/core/contracts.ts +7 -0
- package/src/core/domain.ts +1 -1
- package/src/core/errors.ts +18 -15
- package/src/core/index.ts +4 -2
- package/src/core/package-version.ts +26 -0
- package/src/core/reactions.ts +1 -1
- package/src/core/runtime.ts +33 -23
- package/src/core/schedules.ts +1 -1
- package/src/core/types.ts +2 -2
- package/src/dsl/eval.ts +2 -2
- package/src/dsl/kysely.ts +2 -2
- package/src/dsl/loads.ts +1 -1
- package/src/dsl/parser.ts +5 -5
- package/src/events/drivers/mitt.ts +1 -0
- package/src/mcp/index.ts +2 -1
- package/src/observability/drivers/opentelemetry.ts +1 -0
- package/src/queue/drivers/bullmq.ts +3 -3
- package/src/scheduler/drivers/node-cron.ts +3 -2
- package/src/scheduler/every.ts +7 -7
- package/src/schema/openapi.ts +3 -3
- package/src/seed/index.ts +29 -0
- package/src/server/drivers/fastify.ts +5 -2
- package/src/server/drivers/node.ts +9 -6
- package/src/server/index.ts +3 -1
- package/src/storage/drivers/fs.ts +1 -0
- package/src/testing/index.ts +3 -3
- package/src/ui/components/patterns/confirm.tsx +2 -2
- package/src/ui/components/patterns/content-header.tsx +7 -1
- package/src/ui/components/patterns/data-state.tsx +1 -1
- package/src/ui/components/patterns/dock.tsx +20 -3
- package/src/ui/components/patterns/form.tsx +12 -8
- package/src/ui/components/patterns/list.tsx +4 -4
- package/src/ui/components/patterns/page.tsx +19 -1
- package/src/ui/components/patterns/shell-nav.tsx +10 -3
- package/src/ui/components/patterns/sidebar.tsx +17 -6
- package/src/ui/components/patterns/trigger.tsx +14 -16
- package/src/ui/components/patterns/view.tsx +26 -17
- package/src/ui/components/primitives/alert.tsx +11 -5
- package/src/ui/components/primitives/ask.tsx +3 -3
- package/src/ui/components/primitives/badge.tsx +11 -6
- package/src/ui/components/primitives/breadcrumb.tsx +2 -2
- package/src/ui/components/primitives/button.tsx +16 -3
- package/src/ui/components/primitives/calendar.tsx +28 -2
- package/src/ui/components/primitives/carousel.tsx +3 -3
- package/src/ui/components/primitives/chat.tsx +1 -1
- package/src/ui/components/primitives/checkbox.tsx +1 -1
- package/src/ui/components/primitives/command.tsx +2 -2
- package/src/ui/components/primitives/control.ts +12 -0
- package/src/ui/components/primitives/copyable.tsx +1 -1
- package/src/ui/components/primitives/dialog.tsx +12 -7
- package/src/ui/components/primitives/dot.tsx +5 -0
- package/src/ui/components/primitives/drawer.tsx +10 -3
- package/src/ui/components/primitives/field.tsx +3 -3
- package/src/ui/components/primitives/icon-picker.tsx +3 -1
- package/src/ui/components/primitives/input-group.tsx +1 -1
- package/src/ui/components/primitives/input-otp.tsx +1 -1
- package/src/ui/components/primitives/input.tsx +2 -2
- package/src/ui/components/primitives/progress.tsx +32 -3
- package/src/ui/components/primitives/radio-group.tsx +1 -1
- package/src/ui/components/primitives/resizable.tsx +3 -1
- package/src/ui/components/primitives/select.tsx +5 -5
- package/src/ui/components/primitives/slider.tsx +5 -1
- package/src/ui/components/primitives/sonner.tsx +3 -0
- package/src/ui/components/primitives/switch.tsx +1 -0
- package/src/ui/components/primitives/tabs.tsx +1 -0
- package/src/ui/components/primitives/textarea.tsx +1 -1
- package/src/ui/components/primitives/toggle.tsx +1 -1
- package/src/ui/components/primitives/tooltip.tsx +1 -0
- package/src/ui/docs/changelog.tsx +1 -1
- package/src/ui/docs/content/action-form.md +10 -3
- package/src/ui/docs/content/action-list.md +10 -1
- package/src/ui/docs/content/action-trigger.md +8 -1
- package/src/ui/docs/content/action-view.md +9 -2
- package/src/ui/docs/content/ask.md +11 -0
- package/src/ui/docs/content/calendar.md +13 -0
- package/src/ui/docs/content/card.md +26 -0
- package/src/ui/docs/content/chat.md +20 -0
- package/src/ui/docs/content/cli.md +71 -19
- package/src/ui/docs/content/composer.md +15 -0
- package/src/ui/docs/content/content.md +15 -0
- package/src/ui/docs/content/copyable.md +8 -0
- package/src/ui/docs/content/detail.md +19 -1
- package/src/ui/docs/content/dictionary-value.md +9 -2
- package/src/ui/docs/content/dock.md +8 -0
- package/src/ui/docs/content/dot.md +8 -0
- package/src/ui/docs/content/empty.md +2 -2
- package/src/ui/docs/content/getting-started.md +2 -2
- package/src/ui/docs/content/icon-picker.md +11 -0
- package/src/ui/docs/content/label.md +7 -0
- package/src/ui/docs/content/menu.md +6 -0
- package/src/ui/docs/content/metric-card.md +13 -0
- package/src/ui/docs/content/page.md +8 -0
- package/src/ui/docs/content/popover.md +6 -0
- package/src/ui/docs/content/progress.md +8 -11
- package/src/ui/docs/content/select.md +5 -5
- package/src/ui/docs/content/semantic-context.md +2 -2
- package/src/ui/docs/content/sidebar.md +14 -8
- package/src/ui/docs/content/skeleton.md +6 -0
- package/src/ui/docs/content/split.md +21 -0
- package/src/ui/docs/content/textarea.md +7 -0
- package/src/ui/docs/content/tokens.md +4 -4
- package/src/ui/docs/content/truncate.md +8 -0
- package/src/ui/docs/content/ui.md +14 -0
- package/src/ui/docs/doc-client.tsx +5 -5
- package/src/ui/docs/doc.tsx +26 -14
- package/src/ui/docs/registry.tsx +5 -5
- package/src/ui/docs/standalone.tsx +2 -2
- package/src/ui/drivers/react.tsx +17 -12
- package/src/ui/lib/action-errors.ts +45 -0
- package/src/ui/lib/zod-pt-br.ts +31 -4
- package/src/ui/meta.ts +3 -3
- package/src/ui/react.tsx +2 -0
- package/src/ui/theme.css +10 -8
- package/src/vite/design.ts +6 -18
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
ChevronLeftIcon,
|
|
5
5
|
ChevronRightIcon,
|
|
6
6
|
} from "lucide-react";
|
|
7
|
+
import { ptBR } from "date-fns/locale";
|
|
7
8
|
import {
|
|
8
9
|
DayPicker,
|
|
9
10
|
getDefaultClassNames,
|
|
@@ -13,12 +14,34 @@ import {
|
|
|
13
14
|
import { cn } from "../../lib/cn.ts";
|
|
14
15
|
import { Button, buttonVariants } from "./button.tsx";
|
|
15
16
|
|
|
17
|
+
/** Rótulos acessíveis da navegação em pt-BR — o react-day-picker só traduz nomes de mês e
|
|
18
|
+
* dia pelo `locale`; os aria-labels dos controles continuariam em inglês. */
|
|
19
|
+
const LABELS_PT_BR: NonNullable<React.ComponentProps<typeof DayPicker>["labels"]> = {
|
|
20
|
+
labelPrevious: () => "Mês anterior",
|
|
21
|
+
labelNext: () => "Próximo mês",
|
|
22
|
+
labelMonthDropdown: () => "Escolher o mês",
|
|
23
|
+
labelYearDropdown: () => "Escolher o ano",
|
|
24
|
+
labelDayButton: (date, modifiers, options) => {
|
|
25
|
+
const day = date.toLocaleDateString(options?.locale?.code ?? "pt-BR", {
|
|
26
|
+
weekday: "long",
|
|
27
|
+
day: "numeric",
|
|
28
|
+
month: "long",
|
|
29
|
+
year: "numeric",
|
|
30
|
+
});
|
|
31
|
+
const prefix = modifiers.today ? "Hoje, " : "";
|
|
32
|
+
const suffix = modifiers.selected ? ", selecionado" : "";
|
|
33
|
+
return `${prefix}${day}${suffix}`;
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
16
37
|
function Calendar({
|
|
17
38
|
className,
|
|
18
39
|
classNames,
|
|
19
40
|
showOutsideDays = true,
|
|
20
41
|
captionLayout = "label",
|
|
21
42
|
buttonVariant = "ghost",
|
|
43
|
+
locale = ptBR,
|
|
44
|
+
labels,
|
|
22
45
|
formatters,
|
|
23
46
|
components,
|
|
24
47
|
...props
|
|
@@ -30,6 +53,8 @@ function Calendar({
|
|
|
30
53
|
return (
|
|
31
54
|
<DayPicker
|
|
32
55
|
showOutsideDays={showOutsideDays}
|
|
56
|
+
locale={locale}
|
|
57
|
+
labels={{ ...LABELS_PT_BR, ...labels }}
|
|
33
58
|
className={cn(
|
|
34
59
|
"group/calendar bg-background p-3 [--cell-size:--spacing(8)] [[data-slot=card-body]_&]:bg-transparent [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
|
|
35
60
|
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
|
|
@@ -38,8 +63,9 @@ function Calendar({
|
|
|
38
63
|
)}
|
|
39
64
|
captionLayout={captionLayout}
|
|
40
65
|
formatters={{
|
|
41
|
-
|
|
42
|
-
|
|
66
|
+
// O dateLib já carrega o `locale` em vigor: o dropdown fala a mesma língua da legenda.
|
|
67
|
+
formatMonthDropdown: (date, dateLib) =>
|
|
68
|
+
dateLib?.format(date, "LLL") ?? date.toLocaleString("pt-BR", { month: "short" }),
|
|
43
69
|
...formatters,
|
|
44
70
|
}}
|
|
45
71
|
classNames={{
|
|
@@ -120,7 +120,7 @@ function Carousel({
|
|
|
120
120
|
onKeyDownCapture={handleKeyDown}
|
|
121
121
|
className={cn("relative", className)}
|
|
122
122
|
role="region"
|
|
123
|
-
aria-roledescription="
|
|
123
|
+
aria-roledescription="carrossel"
|
|
124
124
|
data-slot="carousel"
|
|
125
125
|
{...props}
|
|
126
126
|
>
|
|
@@ -194,7 +194,7 @@ function CarouselPrevious({
|
|
|
194
194
|
{...props}
|
|
195
195
|
>
|
|
196
196
|
<ArrowLeft />
|
|
197
|
-
<span className="sr-only">
|
|
197
|
+
<span className="sr-only">Slide anterior</span>
|
|
198
198
|
</Button>
|
|
199
199
|
)
|
|
200
200
|
}
|
|
@@ -224,7 +224,7 @@ function CarouselNext({
|
|
|
224
224
|
{...props}
|
|
225
225
|
>
|
|
226
226
|
<ArrowRight />
|
|
227
|
-
<span className="sr-only">
|
|
227
|
+
<span className="sr-only">Próximo slide</span>
|
|
228
228
|
</Button>
|
|
229
229
|
)
|
|
230
230
|
}
|
|
@@ -193,7 +193,7 @@ const ChatTranscript = React.memo(function ChatTranscript({
|
|
|
193
193
|
return (
|
|
194
194
|
<div
|
|
195
195
|
key={i}
|
|
196
|
-
className="max-w-[88%] self-start rounded-xl border border-
|
|
196
|
+
className="max-w-[88%] self-start rounded-xl border border-context-danger-border bg-context-danger-subtle px-3.5 py-2.5 text-sm text-context-danger-emphasis"
|
|
197
197
|
>
|
|
198
198
|
{item.content}
|
|
199
199
|
</div>
|
|
@@ -12,7 +12,7 @@ function Checkbox({
|
|
|
12
12
|
<CheckboxPrimitive.Root
|
|
13
13
|
data-slot="checkbox"
|
|
14
14
|
className={cn(
|
|
15
|
-
"peer size-4 shrink-0 rounded-[0.25rem] border border-input transition-shadow outline-none focus-visible:border-ring focus-visible:ring-[0.1875rem] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-
|
|
15
|
+
"peer size-4 shrink-0 rounded-[0.25rem] border border-input transition-shadow outline-none focus-visible:border-ring focus-visible:ring-[0.1875rem] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-context-danger aria-invalid:ring-context-danger/20 data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:bg-input/30 dark:aria-invalid:ring-context-danger/40 dark:data-[state=checked]:bg-primary",
|
|
16
16
|
className
|
|
17
17
|
)}
|
|
18
18
|
{...props}
|
|
@@ -28,8 +28,8 @@ function Command({
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
function CommandDialog({
|
|
31
|
-
title = "
|
|
32
|
-
description = "
|
|
31
|
+
title = "Paleta de comandos",
|
|
32
|
+
description = "Busque um comando para executar…",
|
|
33
33
|
children,
|
|
34
34
|
className,
|
|
35
35
|
showCloseButton = true,
|
|
@@ -1,2 +1,14 @@
|
|
|
1
1
|
/** Geometria compartilhada pelos controles inline da casa. */
|
|
2
2
|
export type ControlShape = 'default' | 'pill'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Anel de foco da casa: fino (0.1875rem), translúcido e encostado na borda — sem offset.
|
|
6
|
+
* Todo controle focável usa esta constante; espessuras próprias (`ring-2`, `ring-4`) ou
|
|
7
|
+
* `ring-offset-*` quebram a leitura de "foco" que o tema calibrou (ver theme.css, `--ring`).
|
|
8
|
+
*/
|
|
9
|
+
export const focusRing =
|
|
10
|
+
'focus-visible:border-ring focus-visible:ring-[0.1875rem] focus-visible:ring-ring/50'
|
|
11
|
+
|
|
12
|
+
/** O mesmo anel, disparado por um descendente focado (grupos que envolvem um campo). */
|
|
13
|
+
export const focusRingWithin =
|
|
14
|
+
'focus-within:border-ring focus-within:ring-[0.1875rem] focus-within:ring-ring/50'
|
|
@@ -44,7 +44,7 @@ export function Copyable({
|
|
|
44
44
|
{...props}
|
|
45
45
|
>
|
|
46
46
|
{children}
|
|
47
|
-
{copied ? <CheckIcon className="size-3.5 text-
|
|
47
|
+
{copied ? <CheckIcon className="size-3.5 text-context-success-emphasis" /> : <CopyIcon className="size-3.5" />}
|
|
48
48
|
</button>
|
|
49
49
|
)
|
|
50
50
|
}
|
|
@@ -3,6 +3,7 @@ import * as DialogPrimitive from '@radix-ui/react-dialog'
|
|
|
3
3
|
import { AlertDialog as AlertDialogPrimitive } from 'radix-ui'
|
|
4
4
|
import { XIcon } from 'lucide-react'
|
|
5
5
|
import { cn } from '../../lib/cn.ts'
|
|
6
|
+
import { focusRing } from './control.ts'
|
|
6
7
|
|
|
7
8
|
export type DialogMode = 'default' | 'alert'
|
|
8
9
|
|
|
@@ -110,25 +111,26 @@ export function DialogClose({
|
|
|
110
111
|
)
|
|
111
112
|
}
|
|
112
113
|
|
|
114
|
+
/** Véu compartilhado por Dialog e Drawer: mesmo escurecimento, desfoque e animação. */
|
|
115
|
+
export const overlayClasses =
|
|
116
|
+
'fixed inset-0 z-50 bg-black/30 backdrop-blur-xs data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0'
|
|
117
|
+
|
|
113
118
|
export function DialogOverlay({
|
|
114
119
|
className,
|
|
115
120
|
...props
|
|
116
121
|
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
|
|
117
122
|
const { mode } = useDialogContext()
|
|
118
|
-
const
|
|
119
|
-
'fixed inset-0 z-50 bg-black/30 backdrop-blur-xs data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0',
|
|
120
|
-
className,
|
|
121
|
-
)
|
|
123
|
+
const overlay = cn(overlayClasses, className)
|
|
122
124
|
return mode === 'alert' ? (
|
|
123
125
|
<AlertDialogPrimitive.Overlay
|
|
124
126
|
data-slot="dialog-overlay"
|
|
125
|
-
className={
|
|
127
|
+
className={overlay}
|
|
126
128
|
{...props}
|
|
127
129
|
/>
|
|
128
130
|
) : (
|
|
129
131
|
<DialogPrimitive.Overlay
|
|
130
132
|
data-slot="dialog-overlay"
|
|
131
|
-
className={
|
|
133
|
+
className={overlay}
|
|
132
134
|
{...props}
|
|
133
135
|
/>
|
|
134
136
|
)
|
|
@@ -181,7 +183,10 @@ export function DialogContent({
|
|
|
181
183
|
{showClose && (
|
|
182
184
|
<DialogPrimitive.Close
|
|
183
185
|
data-slot="dialog-close"
|
|
184
|
-
className=
|
|
186
|
+
className={cn(
|
|
187
|
+
"absolute right-4 top-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus-visible:outline-hidden disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
188
|
+
focusRing,
|
|
189
|
+
)}
|
|
185
190
|
>
|
|
186
191
|
<XIcon />
|
|
187
192
|
<span className="sr-only">Fechar</span>
|
|
@@ -5,6 +5,11 @@ import { cn } from '../../lib/cn.ts'
|
|
|
5
5
|
|
|
6
6
|
export type DotContext = UiContext
|
|
7
7
|
export type DotVariant = 'solid' | 'outline'
|
|
8
|
+
/**
|
|
9
|
+
* @deprecated Aliases de migração: `default` → `context="primary"`, `secondary` →
|
|
10
|
+
* `context="neutral"`, `destructive` → `context="danger"`; `success`, `warning` e `info` → o
|
|
11
|
+
* `context` homônimo. Declare `context` + `variant`.
|
|
12
|
+
*/
|
|
8
13
|
export type LegacyDotVariant =
|
|
9
14
|
| 'default'
|
|
10
15
|
| 'secondary'
|
|
@@ -3,6 +3,8 @@ import { XIcon } from "lucide-react";
|
|
|
3
3
|
import { Dialog as DrawerPrimitive } from "radix-ui";
|
|
4
4
|
|
|
5
5
|
import { cn } from "../../lib/cn.ts";
|
|
6
|
+
import { focusRing } from "./control.ts";
|
|
7
|
+
import { overlayClasses } from "./dialog.tsx";
|
|
6
8
|
|
|
7
9
|
function Drawer({
|
|
8
10
|
...props
|
|
@@ -36,7 +38,7 @@ function DrawerOverlay({
|
|
|
36
38
|
<DrawerPrimitive.Overlay
|
|
37
39
|
data-slot="drawer-overlay"
|
|
38
40
|
className={cn(
|
|
39
|
-
|
|
41
|
+
overlayClasses,
|
|
40
42
|
className,
|
|
41
43
|
)}
|
|
42
44
|
{...props}
|
|
@@ -75,9 +77,14 @@ function DrawerContent({
|
|
|
75
77
|
>
|
|
76
78
|
{children}
|
|
77
79
|
{showCloseButton && (
|
|
78
|
-
<DrawerPrimitive.Close
|
|
80
|
+
<DrawerPrimitive.Close
|
|
81
|
+
className={cn(
|
|
82
|
+
"absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus-visible:outline-hidden disabled:pointer-events-none data-[state=open]:bg-secondary",
|
|
83
|
+
focusRing,
|
|
84
|
+
)}
|
|
85
|
+
>
|
|
79
86
|
<XIcon className="size-4" />
|
|
80
|
-
<span className="sr-only">
|
|
87
|
+
<span className="sr-only">Fechar</span>
|
|
81
88
|
</DrawerPrimitive.Close>
|
|
82
89
|
)}
|
|
83
90
|
</DrawerPrimitive.Content>
|
|
@@ -53,7 +53,7 @@ function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
const fieldVariants = cva(
|
|
56
|
-
"group/field flex w-full data-[invalid=true]:text-
|
|
56
|
+
"group/field flex w-full data-[invalid=true]:text-context-danger-emphasis",
|
|
57
57
|
{
|
|
58
58
|
variants: {
|
|
59
59
|
orientation: {
|
|
@@ -113,7 +113,7 @@ function FieldLabel({
|
|
|
113
113
|
<Label
|
|
114
114
|
data-slot="field-label"
|
|
115
115
|
className={cn(
|
|
116
|
-
"group/field-label peer/field-label flex w-fit gap-2 text-xs leading-snug font-medium text-muted-foreground group-data-[invalid=true]/field:text-
|
|
116
|
+
"group/field-label peer/field-label flex w-fit gap-2 text-xs leading-snug font-medium text-muted-foreground group-data-[invalid=true]/field:text-context-danger-emphasis group-data-[disabled=true]/field:opacity-50",
|
|
117
117
|
"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&>*]:data-[slot=field]:p-4",
|
|
118
118
|
"has-[>[data-slot=field]]:has-data-[state=checked]:border-primary has-[>[data-slot=field]]:has-data-[state=checked]:bg-primary/5 dark:has-[>[data-slot=field]]:has-data-[state=checked]:bg-primary/10",
|
|
119
119
|
className
|
|
@@ -224,7 +224,7 @@ function FieldError({
|
|
|
224
224
|
<div
|
|
225
225
|
role="alert"
|
|
226
226
|
data-slot="field-error"
|
|
227
|
-
className={cn("text-sm font-normal text-
|
|
227
|
+
className={cn("text-sm font-normal text-context-danger-emphasis", className)}
|
|
228
228
|
{...props}
|
|
229
229
|
>
|
|
230
230
|
{content}
|
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
type LucideIcon,
|
|
47
47
|
} from 'lucide-react'
|
|
48
48
|
import { cn } from '../../lib/cn.ts'
|
|
49
|
+
import { focusRing } from './control.ts'
|
|
49
50
|
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from './command.tsx'
|
|
50
51
|
import { Popover, PopoverContent, PopoverTrigger } from './popover.tsx'
|
|
51
52
|
|
|
@@ -141,7 +142,8 @@ export function IconPicker({
|
|
|
141
142
|
aria-describedby={ariaDescribedBy}
|
|
142
143
|
data-slot="icon-picker"
|
|
143
144
|
className={cn(
|
|
144
|
-
'flex h-10 w-full items-center gap-2 rounded-md border border-input bg-transparent px-3 text-sm outline-none transition-[color,box-shadow]
|
|
145
|
+
'flex h-10 w-full items-center gap-2 rounded-md border border-input bg-transparent px-3 text-sm outline-none transition-[color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50 dark:bg-input/30',
|
|
146
|
+
focusRing,
|
|
145
147
|
className,
|
|
146
148
|
)}
|
|
147
149
|
>
|
|
@@ -27,7 +27,7 @@ function InputGroup({ className, shape = 'default', ...props }: React.ComponentP
|
|
|
27
27
|
"has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-[0.1875rem] has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50",
|
|
28
28
|
|
|
29
29
|
// Error state.
|
|
30
|
-
"has-[[data-slot][aria-invalid=true]]:border-
|
|
30
|
+
"has-[[data-slot][aria-invalid=true]]:border-context-danger has-[[data-slot][aria-invalid=true]]:ring-context-danger/20 dark:has-[[data-slot][aria-invalid=true]]:ring-context-danger/40",
|
|
31
31
|
|
|
32
32
|
className
|
|
33
33
|
)}
|
|
@@ -49,7 +49,7 @@ function InputOTPSlot({
|
|
|
49
49
|
data-slot="input-otp-slot"
|
|
50
50
|
data-active={isActive}
|
|
51
51
|
className={cn(
|
|
52
|
-
"relative flex h-9 w-9 items-center justify-center border-y border-r border-input text-sm transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md aria-invalid:border-
|
|
52
|
+
"relative flex h-9 w-9 items-center justify-center border-y border-r border-input text-sm transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md aria-invalid:border-context-danger data-[active=true]:z-10 data-[active=true]:border-ring data-[active=true]:ring-[0.1875rem] data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:border-context-danger data-[active=true]:aria-invalid:ring-context-danger/20 dark:bg-input/30 dark:data-[active=true]:aria-invalid:ring-context-danger/40",
|
|
53
53
|
className
|
|
54
54
|
)}
|
|
55
55
|
{...props}
|
|
@@ -14,7 +14,7 @@ export interface InputProps extends React.ComponentProps<"input"> {
|
|
|
14
14
|
|
|
15
15
|
// O <input> CRU (sem adorno): ELE é o campo — borda, foco, ref e className moram nele.
|
|
16
16
|
const bareInput =
|
|
17
|
-
"h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30 focus-visible:border-ring focus-visible:ring-[0.1875rem] focus-visible:ring-ring/50 aria-invalid:border-
|
|
17
|
+
"h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30 focus-visible:border-ring focus-visible:ring-[0.1875rem] focus-visible:ring-ring/50 aria-invalid:border-context-danger aria-invalid:ring-context-danger/20 dark:aria-invalid:ring-context-danger/40"
|
|
18
18
|
|
|
19
19
|
function Input({ className, type, icon, trailing, ...props }: InputProps) {
|
|
20
20
|
// Sem adorno: o <input> cru de sempre — retrocompatível (refs, forms, InputGroupInput).
|
|
@@ -33,7 +33,7 @@ function Input({ className, type, icon, trailing, ...props }: InputProps) {
|
|
|
33
33
|
className={cn(
|
|
34
34
|
"flex h-9 w-full min-w-0 items-center gap-1.5 rounded-md border border-input bg-transparent px-3 text-base transition-[color,box-shadow] outline-none md:text-sm dark:bg-input/30",
|
|
35
35
|
"has-[input:focus-visible]:border-ring has-[input:focus-visible]:ring-[0.1875rem] has-[input:focus-visible]:ring-ring/50",
|
|
36
|
-
"has-[input[aria-invalid=true]]:border-
|
|
36
|
+
"has-[input[aria-invalid=true]]:border-context-danger has-[input[aria-invalid=true]]:ring-context-danger/20 dark:has-[input[aria-invalid=true]]:ring-context-danger/40",
|
|
37
37
|
"has-[input:disabled]:pointer-events-none has-[input:disabled]:opacity-50",
|
|
38
38
|
className,
|
|
39
39
|
)}
|
|
@@ -1,25 +1,54 @@
|
|
|
1
1
|
import * as React from "react"
|
|
2
2
|
import { Progress as ProgressPrimitive } from "radix-ui"
|
|
3
3
|
|
|
4
|
+
import type { UiContext } from '../../../core/ui-context.ts'
|
|
4
5
|
import { cn } from '../../lib/cn.ts'
|
|
5
6
|
|
|
7
|
+
export type ProgressContext = UiContext
|
|
8
|
+
|
|
9
|
+
const trackClasses: Record<ProgressContext, string> = {
|
|
10
|
+
neutral: 'bg-context-neutral-subtle',
|
|
11
|
+
primary: 'bg-context-primary/20',
|
|
12
|
+
info: 'bg-context-info/20',
|
|
13
|
+
success: 'bg-context-success/20',
|
|
14
|
+
warning: 'bg-context-warning/20',
|
|
15
|
+
danger: 'bg-context-danger/20',
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const indicatorClasses: Record<ProgressContext, string> = {
|
|
19
|
+
neutral: 'bg-context-neutral-emphasis',
|
|
20
|
+
primary: 'bg-context-primary',
|
|
21
|
+
info: 'bg-context-info',
|
|
22
|
+
success: 'bg-context-success',
|
|
23
|
+
warning: 'bg-context-warning',
|
|
24
|
+
danger: 'bg-context-danger',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ProgressProps extends React.ComponentProps<typeof ProgressPrimitive.Root> {
|
|
28
|
+
/** O que o andamento comunica: `success` para meta atingida, `warning`/`danger` para atenção. */
|
|
29
|
+
context?: ProgressContext
|
|
30
|
+
}
|
|
31
|
+
|
|
6
32
|
function Progress({
|
|
7
33
|
className,
|
|
8
34
|
value,
|
|
35
|
+
context = 'primary',
|
|
9
36
|
...props
|
|
10
|
-
}:
|
|
37
|
+
}: ProgressProps) {
|
|
11
38
|
return (
|
|
12
39
|
<ProgressPrimitive.Root
|
|
13
40
|
data-slot="progress"
|
|
41
|
+
data-context={context}
|
|
14
42
|
className={cn(
|
|
15
|
-
"relative h-2 w-full overflow-hidden rounded-full
|
|
43
|
+
"relative h-2 w-full overflow-hidden rounded-full",
|
|
44
|
+
trackClasses[context],
|
|
16
45
|
className
|
|
17
46
|
)}
|
|
18
47
|
{...props}
|
|
19
48
|
>
|
|
20
49
|
<ProgressPrimitive.Indicator
|
|
21
50
|
data-slot="progress-indicator"
|
|
22
|
-
className="h-full w-full flex-1
|
|
51
|
+
className={cn("h-full w-full flex-1 transition-all", indicatorClasses[context])}
|
|
23
52
|
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
|
|
24
53
|
/>
|
|
25
54
|
</ProgressPrimitive.Root>
|
|
@@ -25,7 +25,7 @@ function RadioGroupItem({
|
|
|
25
25
|
<RadioGroupPrimitive.Item
|
|
26
26
|
data-slot="radio-group-item"
|
|
27
27
|
className={cn(
|
|
28
|
-
"aspect-square size-4 shrink-0 rounded-full border border-input text-primary transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[0.1875rem] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-
|
|
28
|
+
"aspect-square size-4 shrink-0 rounded-full border border-input text-primary transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[0.1875rem] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-context-danger aria-invalid:ring-context-danger/20 dark:bg-input/30 dark:aria-invalid:ring-context-danger/40",
|
|
29
29
|
className
|
|
30
30
|
)}
|
|
31
31
|
{...props}
|
|
@@ -2,6 +2,7 @@ import { GripVerticalIcon } from "lucide-react"
|
|
|
2
2
|
import * as ResizablePrimitive from "react-resizable-panels"
|
|
3
3
|
|
|
4
4
|
import { cn } from '../../lib/cn.ts'
|
|
5
|
+
import { focusRing } from './control.ts'
|
|
5
6
|
|
|
6
7
|
function ResizablePanelGroup({
|
|
7
8
|
className,
|
|
@@ -34,7 +35,8 @@ function ResizableHandle({
|
|
|
34
35
|
<ResizablePrimitive.Separator
|
|
35
36
|
data-slot="resizable-handle"
|
|
36
37
|
className={cn(
|
|
37
|
-
"relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:
|
|
38
|
+
"relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-hidden aria-[orientation=horizontal]:h-px aria-[orientation=horizontal]:w-full aria-[orientation=horizontal]:after:left-0 aria-[orientation=horizontal]:after:h-1 aria-[orientation=horizontal]:after:w-full aria-[orientation=horizontal]:after:translate-x-0 aria-[orientation=horizontal]:after:-translate-y-1/2 [&[aria-orientation=horizontal]>div]:rotate-90",
|
|
39
|
+
focusRing,
|
|
38
40
|
className
|
|
39
41
|
)}
|
|
40
42
|
{...props}
|
|
@@ -25,7 +25,7 @@ import { Check, ChevronDownIcon, SearchIcon, X } from 'lucide-react'
|
|
|
25
25
|
import { Command as CommandPrimitive } from 'cmdk'
|
|
26
26
|
|
|
27
27
|
import { cn } from '../../lib/cn.ts'
|
|
28
|
-
import type
|
|
28
|
+
import { focusRing, focusRingWithin, type ControlShape } from './control.ts'
|
|
29
29
|
import { CommandEmpty, CommandGroup, CommandItem, CommandList } from './command.tsx'
|
|
30
30
|
import { Popover, PopoverAnchor, PopoverContent } from './popover.tsx'
|
|
31
31
|
|
|
@@ -162,8 +162,8 @@ function NativeSelect({
|
|
|
162
162
|
onChange={(e) => onChange(e.target.value)}
|
|
163
163
|
className={cn(
|
|
164
164
|
"h-9 w-full min-w-0 appearance-none rounded-md border border-input bg-transparent px-3 py-2 pr-9 text-sm transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground disabled:pointer-events-none disabled:cursor-not-allowed data-[size=sm]:h-8 data-[size=sm]:py-1 data-[shape=pill]:rounded-full dark:bg-input/30 dark:hover:bg-input/50",
|
|
165
|
-
|
|
166
|
-
'aria-invalid:border-
|
|
165
|
+
focusRing,
|
|
166
|
+
'aria-invalid:border-context-danger aria-invalid:ring-context-danger/20 dark:aria-invalid:ring-context-danger/40',
|
|
167
167
|
icon ? 'pl-9' : '',
|
|
168
168
|
)}
|
|
169
169
|
>
|
|
@@ -359,8 +359,8 @@ function CustomSelect(props: Exclude<SelectProps, SelectNativeProps>): React.Rea
|
|
|
359
359
|
className={cn(
|
|
360
360
|
'flex items-center gap-1.5 rounded-md outline-none transition-[color,box-shadow] data-[shape=pill]:rounded-full',
|
|
361
361
|
ghost
|
|
362
|
-
? 'min-h-9 px-3 py-1 text-muted-foreground hover:bg-muted hover:text-foreground
|
|
363
|
-
:
|
|
362
|
+
? cn('min-h-9 px-3 py-1 text-muted-foreground hover:bg-muted hover:text-foreground data-[size=sm]:min-h-8 data-[size=sm]:px-2', focusRingWithin)
|
|
363
|
+
: cn('min-h-9 border border-input bg-transparent px-3 py-1 aria-invalid:border-context-danger aria-invalid:ring-context-danger/20 data-[size=sm]:min-h-8 data-[size=sm]:py-0.5 dark:bg-input/30 dark:aria-invalid:ring-context-danger/40', focusRingWithin),
|
|
364
364
|
variant === 'default' && 'flex-wrap',
|
|
365
365
|
outline && 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-input/50',
|
|
366
366
|
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
@@ -2,6 +2,7 @@ import * as React from "react"
|
|
|
2
2
|
import { Slider as SliderPrimitive } from "radix-ui"
|
|
3
3
|
|
|
4
4
|
import { cn } from '../../lib/cn.ts'
|
|
5
|
+
import { focusRing } from './control.ts'
|
|
5
6
|
|
|
6
7
|
function Slider({
|
|
7
8
|
className,
|
|
@@ -51,7 +52,10 @@ function Slider({
|
|
|
51
52
|
<SliderPrimitive.Thumb
|
|
52
53
|
data-slot="slider-thumb"
|
|
53
54
|
key={index}
|
|
54
|
-
className=
|
|
55
|
+
className={cn(
|
|
56
|
+
"block size-4 shrink-0 rounded-full border border-primary bg-background shadow-sm transition-[color,box-shadow] hover:ring-[0.1875rem] hover:ring-ring/50 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50",
|
|
57
|
+
focusRing,
|
|
58
|
+
)}
|
|
55
59
|
/>
|
|
56
60
|
))}
|
|
57
61
|
</SliderPrimitive.Root>
|
|
@@ -170,6 +170,9 @@ export function Toaster({
|
|
|
170
170
|
<Sonner
|
|
171
171
|
data-slot="toaster"
|
|
172
172
|
theme="system"
|
|
173
|
+
// O sonner 1.x só expõe o rótulo da região; o do botão de fechar ("Close toast") vem
|
|
174
|
+
// com o 2.x (`closeButtonAriaLabel`).
|
|
175
|
+
containerAriaLabel="Notificações"
|
|
173
176
|
className={cn('toaster group', className)}
|
|
174
177
|
icons={{
|
|
175
178
|
success: (
|
|
@@ -22,6 +22,7 @@ function Switch({
|
|
|
22
22
|
>
|
|
23
23
|
<SwitchPrimitive.Thumb
|
|
24
24
|
data-slot="switch-thumb"
|
|
25
|
+
// px: o percurso do thumb desconta a borda (um pixel de cada lado) no calc do translate.
|
|
25
26
|
className={cn(
|
|
26
27
|
"pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0 dark:data-[state=checked]:bg-primary-foreground dark:data-[state=unchecked]:bg-foreground"
|
|
27
28
|
)}
|
|
@@ -77,6 +77,7 @@ function TabsTrigger({
|
|
|
77
77
|
return (
|
|
78
78
|
<TabsPrimitive.Trigger
|
|
79
79
|
data-slot="tabs-trigger"
|
|
80
|
+
// px: altura e indicador compensam a hairline do contêiner (um pixel físico, não escala).
|
|
80
81
|
className={cn(
|
|
81
82
|
"relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[0.1875rem] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none dark:text-muted-foreground dark:hover:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
82
83
|
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent",
|
|
@@ -7,7 +7,7 @@ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
|
|
7
7
|
<textarea
|
|
8
8
|
data-slot="textarea"
|
|
9
9
|
className={cn(
|
|
10
|
-
"flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-base transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[0.1875rem] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-
|
|
10
|
+
"flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-base transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[0.1875rem] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-context-danger aria-invalid:ring-context-danger/20 md:text-sm dark:bg-input/30 dark:aria-invalid:ring-context-danger/40",
|
|
11
11
|
className
|
|
12
12
|
)}
|
|
13
13
|
{...props}
|
|
@@ -5,7 +5,7 @@ import { Toggle as TogglePrimitive } from "radix-ui"
|
|
|
5
5
|
import { cn } from '../../lib/cn.ts'
|
|
6
6
|
|
|
7
7
|
const toggleVariants = cva(
|
|
8
|
-
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none hover:bg-muted hover:text-muted-foreground focus-visible:border-ring focus-visible:ring-[0.1875rem] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-
|
|
8
|
+
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none hover:bg-muted hover:text-muted-foreground focus-visible:border-ring focus-visible:ring-[0.1875rem] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-context-danger aria-invalid:ring-context-danger/20 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground dark:aria-invalid:ring-context-danger/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
9
9
|
{
|
|
10
10
|
variants: {
|
|
11
11
|
variant: {
|
|
@@ -46,6 +46,7 @@ function TooltipContent({
|
|
|
46
46
|
{...props}
|
|
47
47
|
>
|
|
48
48
|
{children}
|
|
49
|
+
{/* px: a seta sobe dois pixels para encaixar na borda do conteúdo, independente da fonte. */}
|
|
49
50
|
<TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[0.125rem] bg-foreground fill-foreground" />
|
|
50
51
|
</TooltipPrimitive.Content>
|
|
51
52
|
</TooltipPrimitive.Portal>
|
|
@@ -54,7 +54,7 @@ export function ChangelogView({ source }: { source: string }): React.ReactElemen
|
|
|
54
54
|
<div className="flex flex-wrap items-baseline gap-x-3 gap-y-1">
|
|
55
55
|
<h2 className="text-xl font-semibold tracking-tight">{v.version}</h2>
|
|
56
56
|
{v.date !== undefined && <span className="text-sm text-muted-foreground">{v.date}</span>}
|
|
57
|
-
{v.breaking !== undefined && <Badge context="danger" variant="solid">
|
|
57
|
+
{v.breaking !== undefined && <Badge context="danger" variant="solid">Incompatível</Badge>}
|
|
58
58
|
</div>
|
|
59
59
|
{v.body.length > 0 && (
|
|
60
60
|
<div className="mt-3">
|
|
@@ -55,12 +55,19 @@ Monte os providers de consulta e execução uma vez na raiz do aplicativo:
|
|
|
55
55
|
```tsx
|
|
56
56
|
/* main.tsx do app. */
|
|
57
57
|
<QueryClientProvider client={queryClient}>
|
|
58
|
-
<
|
|
58
|
+
<OpusProvider client={apiClient}>
|
|
59
59
|
<App />
|
|
60
|
-
</
|
|
60
|
+
</OpusProvider>
|
|
61
61
|
</QueryClientProvider>
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
+
## useFormAction
|
|
65
|
+
|
|
66
|
+
`ActionForm` é uma composição sobre `useFormAction(action, { defaultValues, onSuccess, onError })`,
|
|
67
|
+
que devolve o `form` do react-hook-form já com o resolver Zod em pt-BR, `submit`, `isLoading`,
|
|
68
|
+
`error`, `isSuccess` e `reset`. Use o hook direto quando o formulário precisar de um layout ou de um
|
|
69
|
+
ciclo de vida que o pattern não cobre; validação e execução continuam iguais.
|
|
70
|
+
|
|
64
71
|
## Propriedades de ActionForm
|
|
65
72
|
|
|
66
73
|
| Propriedade | Tipo | Padrão | Descrição |
|
|
@@ -85,7 +92,7 @@ Monte os providers de consulta e execução uma vez na raiz do aplicativo:
|
|
|
85
92
|
|
|
86
93
|
As opções seguem esta precedência: `options` no campo, `fieldOptions` no formulário, `options` no
|
|
87
94
|
`FieldSpec`, metadata de `t.dict` no schema e, por último, as chaves de `z.enum`. Uma fonte mais
|
|
88
|
-
específica substitui as seguintes. Dicionários registrados no `
|
|
95
|
+
específica substitui as seguintes. Dicionários registrados no `OpusProvider` resolvem rótulos por
|
|
89
96
|
referência; itens estáticos permanecem como declarados no contrato.
|
|
90
97
|
|
|
91
98
|
## Widgets declarativos
|
|
@@ -97,6 +97,7 @@ periods: [
|
|
|
97
97
|
{ value: 'thisMonth', label: 'Este mês' },
|
|
98
98
|
]
|
|
99
99
|
// Presets computáveis: today · yesterday · last7 · last30 · thisMonth · lastMonth.
|
|
100
|
+
// presetRange('last7') → { from: 'YYYY-MM-DD', to: 'YYYY-MM-DD' } — o mesmo cálculo do pattern.
|
|
100
101
|
```
|
|
101
102
|
|
|
102
103
|
## Paginação e carregamento
|
|
@@ -171,7 +172,7 @@ render(
|
|
|
171
172
|
- **`depends: ['outroFiltro']`:** desabilita o filtro até que suas dependências tenham valor e o
|
|
172
173
|
limpa quando uma delas muda.
|
|
173
174
|
- **`options: { kind: 'dictionary', ref: 'sessionStatus' }`:** resolve opções e rótulos por um
|
|
174
|
-
dicionário registrado no `
|
|
175
|
+
dicionário registrado no `OpusProvider`. `filterOptions` substitui essa fonte quando informado.
|
|
175
176
|
- **`options: { kind: 'lookup', source: 'x.lookup' }`:** consulta uma action com `{ q }` e os valores
|
|
176
177
|
das dependências. A resposta segue o formato `{ value, label }`.
|
|
177
178
|
|
|
@@ -244,6 +245,14 @@ render(
|
|
|
244
245
|
| `sort` | `{ fields, default }` — ordenação inicial; header ordenável escreve `sort: 'chave:dir'`. |
|
|
245
246
|
| `periods` | `{ value, label }[]` — o controle de período (presets + Personalizado com calendário); materializa em `from`/`to` no input. |
|
|
246
247
|
|
|
248
|
+
## useListAction
|
|
249
|
+
|
|
250
|
+
Quando o layout não cabe no `ActionList`, `useListAction(action, input)` entrega o mesmo fetch
|
|
251
|
+
declarativo (`items`, `total`, `error`, `isLoading`, `isFetching`, `refetch`) sob o
|
|
252
|
+
`QueryClientProvider`, e `presetRange(preset)` materializa os presets de período (`today`, `last7`,
|
|
253
|
+
`thisMonth`…) em `{ from, to }` no formato `YYYY-MM-DD` — o mesmo cálculo que o pattern faz antes de
|
|
254
|
+
chamar a action.
|
|
255
|
+
|
|
247
256
|
## Propriedades de ActionList
|
|
248
257
|
|
|
249
258
|
| Propriedade | Tipo | Padrão | Descrição |
|