@softize/opus 8.6.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/CHANGELOG.md +1616 -0
- package/LICENSE +21 -0
- package/README.md +113 -0
- package/bin/cli.mjs +528 -0
- package/bin/lib/check.mjs +307 -0
- package/bin/lib/components.mjs +151 -0
- package/bin/lib/create.mjs +208 -0
- package/bin/lib/db-check-runner.mjs +86 -0
- package/bin/lib/db-migrate-runner.mjs +89 -0
- package/bin/lib/db-scaffold-runner.mjs +84 -0
- package/bin/lib/db.mjs +261 -0
- package/bin/lib/docs-include.mjs +48 -0
- package/bin/lib/gen-dicts.mjs +134 -0
- package/bin/lib/gen-docs.mjs +288 -0
- package/bin/lib/gen-manifest.mjs +102 -0
- package/bin/lib/gen-openapi.mjs +195 -0
- package/bin/lib/gen-runner.mjs +472 -0
- package/bin/lib/gen-stubs.mjs +463 -0
- package/bin/lib/gen.mjs +311 -0
- package/bin/lib/init.mjs +514 -0
- package/bin/lib/introspect.mjs +107 -0
- package/bin/lib/mcp.mjs +85 -0
- package/bin/lib/postinstall.mjs +56 -0
- package/docs/chat-event-protocol.md +85 -0
- package/docs/code-style.md +16 -0
- package/docs/data-layer.md +246 -0
- package/docs/ownership-vs-shadcn-lock.md +102 -0
- package/docs/protocol.md +2053 -0
- package/docs/releasing.md +110 -0
- package/docs/shellnav.md +131 -0
- package/package.json +338 -0
- package/registry/hooks/hooks.json +26 -0
- package/registry/hooks/link-memory-on-start.mjs +46 -0
- package/registry/hooks/opus-check-on-stop.mjs +114 -0
- package/registry/skills/create-action/SKILL.md +49 -0
- package/registry/skills/create-action/scaffold.mjs +122 -0
- package/registry/templates/app/_gitignore +3 -0
- package/registry/templates/app/_npmrc +1 -0
- package/registry/templates/app/_opus/_gitignore +5 -0
- package/registry/templates/app/_prettierrc.json +6 -0
- package/registry/templates/app/index.html +13 -0
- package/registry/templates/app/opus.config.ts +16 -0
- package/registry/templates/app/package.json +43 -0
- package/registry/templates/app/pnpm-workspace.yaml +11 -0
- package/registry/templates/app/public/favicon.svg +4 -0
- package/registry/templates/app/src/App.tsx +37 -0
- package/registry/templates/app/src/domains/tasks/actions/list.test.ts +34 -0
- package/registry/templates/app/src/domains/tasks/actions/list.ts +33 -0
- package/registry/templates/app/src/domains/tasks/index.ts +13 -0
- package/registry/templates/app/src/index.css +18 -0
- package/registry/templates/app/src/main.tsx +25 -0
- package/registry/templates/app/tsconfig.json +20 -0
- package/registry/templates/app/vite.config.ts +46 -0
- package/registry/templates/monorepo/_gitignore +3 -0
- package/registry/templates/monorepo/_npmrc +1 -0
- package/registry/templates/monorepo/package.json +9 -0
- package/registry/templates/monorepo/pnpm-workspace.yaml +14 -0
- package/src/ai/ask.ts +64 -0
- package/src/ai/drivers/anthropic.ts +309 -0
- package/src/ai/index.ts +17 -0
- package/src/audit/drivers/console.ts +117 -0
- package/src/audit/drivers/pg.ts +172 -0
- package/src/audit/index.ts +51 -0
- package/src/auth/drivers/better-auth.ts +103 -0
- package/src/auth/drivers/jwt.ts +188 -0
- package/src/auth/index.ts +9 -0
- package/src/client/drivers/fetch.ts +202 -0
- package/src/client/index.ts +22 -0
- package/src/core/actions.ts +110 -0
- package/src/core/audit.ts +239 -0
- package/src/core/contracts.ts +137 -0
- package/src/core/domain.ts +310 -0
- package/src/core/errors.ts +181 -0
- package/src/core/index.ts +174 -0
- package/src/core/logical-type.ts +31 -0
- package/src/core/reactions.ts +81 -0
- package/src/core/runtime.ts +1167 -0
- package/src/core/schedules.ts +41 -0
- package/src/core/types.ts +1356 -0
- package/src/data/drivers/kysely.ts +389 -0
- package/src/data/index.ts +10 -0
- package/src/data/readonly-pool.ts +160 -0
- package/src/dsl/eval.ts +136 -0
- package/src/dsl/index.ts +29 -0
- package/src/dsl/kysely.ts +230 -0
- package/src/dsl/loads.ts +123 -0
- package/src/dsl/parser.ts +423 -0
- package/src/dsl/types.ts +113 -0
- package/src/events/drivers/mitt.ts +70 -0
- package/src/events/index.ts +9 -0
- package/src/log/drivers/pino.ts +57 -0
- package/src/log/index.ts +9 -0
- package/src/mcp/index.ts +62 -0
- package/src/queue/drivers/bullmq.ts +190 -0
- package/src/queue/index.ts +9 -0
- package/src/scheduler/drivers/node-cron.ts +93 -0
- package/src/scheduler/every.ts +45 -0
- package/src/scheduler/index.ts +9 -0
- package/src/schema/drivers/zod.ts +765 -0
- package/src/schema/entity.ts +439 -0
- package/src/schema/format/locale.ts +144 -0
- package/src/schema/index.ts +65 -0
- package/src/schema/openapi.ts +302 -0
- package/src/schema/scaffold.ts +160 -0
- package/src/server/drivers/fastify.ts +224 -0
- package/src/server/drivers/node.ts +386 -0
- package/src/server/index.ts +142 -0
- package/src/storage/drivers/fs.ts +90 -0
- package/src/storage/drivers/s3.ts +117 -0
- package/src/storage/index.ts +27 -0
- package/src/testing/fake.ts +298 -0
- package/src/testing/index.ts +324 -0
- package/src/ui/components/patterns/action-form-card.tsx +48 -0
- package/src/ui/components/patterns/action-list-dialog.tsx +93 -0
- package/src/ui/components/patterns/app-shell.tsx +227 -0
- package/src/ui/components/patterns/confirm.tsx +226 -0
- package/src/ui/components/patterns/data-state.tsx +75 -0
- package/src/ui/components/patterns/form-dialog.tsx +64 -0
- package/src/ui/components/patterns/form.tsx +584 -0
- package/src/ui/components/patterns/list.tsx +1488 -0
- package/src/ui/components/patterns/page.tsx +46 -0
- package/src/ui/components/patterns/section-shell.tsx +246 -0
- package/src/ui/components/patterns/shell-nav.tsx +150 -0
- package/src/ui/components/patterns/sidebar.tsx +89 -0
- package/src/ui/components/patterns/split.tsx +93 -0
- package/src/ui/components/patterns/trigger.tsx +196 -0
- package/src/ui/components/patterns/view.tsx +84 -0
- package/src/ui/components/primitives/accordion.tsx +64 -0
- package/src/ui/components/primitives/alert-dialog.tsx +190 -0
- package/src/ui/components/primitives/alert.tsx +116 -0
- package/src/ui/components/primitives/aspect-ratio.tsx +9 -0
- package/src/ui/components/primitives/avatar.tsx +107 -0
- package/src/ui/components/primitives/badge.tsx +37 -0
- package/src/ui/components/primitives/breadcrumb.tsx +109 -0
- package/src/ui/components/primitives/button-group.tsx +83 -0
- package/src/ui/components/primitives/button.tsx +102 -0
- package/src/ui/components/primitives/calendar.tsx +218 -0
- package/src/ui/components/primitives/card.tsx +56 -0
- package/src/ui/components/primitives/carousel.tsx +239 -0
- package/src/ui/components/primitives/chat.tsx +407 -0
- package/src/ui/components/primitives/checkbox.tsx +30 -0
- package/src/ui/components/primitives/collapsible.tsx +31 -0
- package/src/ui/components/primitives/command.tsx +182 -0
- package/src/ui/components/primitives/composer.tsx +121 -0
- package/src/ui/components/primitives/copyable.tsx +50 -0
- package/src/ui/components/primitives/dialog.tsx +147 -0
- package/src/ui/components/primitives/drawer.tsx +141 -0
- package/src/ui/components/primitives/empty.tsx +104 -0
- package/src/ui/components/primitives/field.tsx +246 -0
- package/src/ui/components/primitives/icon-picker.tsx +180 -0
- package/src/ui/components/primitives/input-group.tsx +168 -0
- package/src/ui/components/primitives/input-otp.tsx +75 -0
- package/src/ui/components/primitives/input.tsx +72 -0
- package/src/ui/components/primitives/item.tsx +193 -0
- package/src/ui/components/primitives/kbd.tsx +28 -0
- package/src/ui/components/primitives/label.tsx +22 -0
- package/src/ui/components/primitives/markdown.tsx +35 -0
- package/src/ui/components/primitives/menu.tsx +255 -0
- package/src/ui/components/primitives/pagination.tsx +127 -0
- package/src/ui/components/primitives/popover.tsx +87 -0
- package/src/ui/components/primitives/progress.tsx +29 -0
- package/src/ui/components/primitives/radio-group.tsx +43 -0
- package/src/ui/components/primitives/resizable.tsx +51 -0
- package/src/ui/components/primitives/scroll-area.tsx +56 -0
- package/src/ui/components/primitives/select.tsx +479 -0
- package/src/ui/components/primitives/separator.tsx +26 -0
- package/src/ui/components/primitives/skeleton.tsx +13 -0
- package/src/ui/components/primitives/slider.tsx +61 -0
- package/src/ui/components/primitives/sonner.tsx +46 -0
- package/src/ui/components/primitives/spinner.tsx +29 -0
- package/src/ui/components/primitives/switch.tsx +33 -0
- package/src/ui/components/primitives/table.tsx +114 -0
- package/src/ui/components/primitives/tabs.tsx +104 -0
- package/src/ui/components/primitives/textarea.tsx +18 -0
- package/src/ui/components/primitives/toggle-group.tsx +81 -0
- package/src/ui/components/primitives/toggle.tsx +45 -0
- package/src/ui/components/primitives/tooltip.tsx +55 -0
- package/src/ui/components/primitives/truncate.tsx +49 -0
- package/src/ui/docs/DocBrowser.tsx +90 -0
- package/src/ui/docs/changelog.tsx +80 -0
- package/src/ui/docs/content/accordion.md +86 -0
- package/src/ui/docs/content/action-form-card.md +24 -0
- package/src/ui/docs/content/action-form-dialog.md +30 -0
- package/src/ui/docs/content/action-form.md +125 -0
- package/src/ui/docs/content/action-list-dialog.md +68 -0
- package/src/ui/docs/content/action-list.md +194 -0
- package/src/ui/docs/content/action-trigger.md +72 -0
- package/src/ui/docs/content/action-view.md +47 -0
- package/src/ui/docs/content/actions.md +138 -0
- package/src/ui/docs/content/ai.md +112 -0
- package/src/ui/docs/content/alert-dialog.md +73 -0
- package/src/ui/docs/content/alert.md +69 -0
- package/src/ui/docs/content/app-shell.md +155 -0
- package/src/ui/docs/content/aspect-ratio.md +66 -0
- package/src/ui/docs/content/audit.md +84 -0
- package/src/ui/docs/content/auth.md +70 -0
- package/src/ui/docs/content/avatar.md +94 -0
- package/src/ui/docs/content/badge.md +48 -0
- package/src/ui/docs/content/breadcrumb.md +87 -0
- package/src/ui/docs/content/button-group.md +71 -0
- package/src/ui/docs/content/button.md +60 -0
- package/src/ui/docs/content/calendar.md +62 -0
- package/src/ui/docs/content/card.md +49 -0
- package/src/ui/docs/content/carousel.md +85 -0
- package/src/ui/docs/content/chat.md +69 -0
- package/src/ui/docs/content/checkbox.md +75 -0
- package/src/ui/docs/content/cli.md +58 -0
- package/src/ui/docs/content/collapsible.md +64 -0
- package/src/ui/docs/content/command.md +56 -0
- package/src/ui/docs/content/composer.md +50 -0
- package/src/ui/docs/content/confirm.md +120 -0
- package/src/ui/docs/content/copyable.md +30 -0
- package/src/ui/docs/content/customization.md +110 -0
- package/src/ui/docs/content/cycle.md +34 -0
- package/src/ui/docs/content/data-state.md +47 -0
- package/src/ui/docs/content/data.md +99 -0
- package/src/ui/docs/content/dialog.md +60 -0
- package/src/ui/docs/content/drawer.md +55 -0
- package/src/ui/docs/content/empty.md +66 -0
- package/src/ui/docs/content/events.md +61 -0
- package/src/ui/docs/content/field.md +58 -0
- package/src/ui/docs/content/getting-started.md +109 -0
- package/src/ui/docs/content/icon-picker.md +51 -0
- package/src/ui/docs/content/input-group.md +78 -0
- package/src/ui/docs/content/input-otp.md +72 -0
- package/src/ui/docs/content/input.md +78 -0
- package/src/ui/docs/content/item.md +84 -0
- package/src/ui/docs/content/kbd.md +62 -0
- package/src/ui/docs/content/label.md +32 -0
- package/src/ui/docs/content/log.md +55 -0
- package/src/ui/docs/content/markdown.md +41 -0
- package/src/ui/docs/content/mcp.md +44 -0
- package/src/ui/docs/content/menu.md +114 -0
- package/src/ui/docs/content/microcopy.md +83 -0
- package/src/ui/docs/content/page.md +34 -0
- package/src/ui/docs/content/pagination.md +99 -0
- package/src/ui/docs/content/popover.md +49 -0
- package/src/ui/docs/content/progress.md +69 -0
- package/src/ui/docs/content/queue.md +62 -0
- package/src/ui/docs/content/radio-group.md +77 -0
- package/src/ui/docs/content/resizable.md +86 -0
- package/src/ui/docs/content/router.md +56 -0
- package/src/ui/docs/content/runtime.md +77 -0
- package/src/ui/docs/content/scheduler.md +66 -0
- package/src/ui/docs/content/scroll-area.md +89 -0
- package/src/ui/docs/content/section-shell.md +121 -0
- package/src/ui/docs/content/select.md +342 -0
- package/src/ui/docs/content/separator.md +33 -0
- package/src/ui/docs/content/sidebar.md +38 -0
- package/src/ui/docs/content/skeleton.md +34 -0
- package/src/ui/docs/content/slider.md +64 -0
- package/src/ui/docs/content/spinner.md +37 -0
- package/src/ui/docs/content/split.md +33 -0
- package/src/ui/docs/content/storage.md +69 -0
- package/src/ui/docs/content/switch.md +69 -0
- package/src/ui/docs/content/table.md +102 -0
- package/src/ui/docs/content/tabs.md +94 -0
- package/src/ui/docs/content/testing.md +89 -0
- package/src/ui/docs/content/textarea.md +30 -0
- package/src/ui/docs/content/toast.md +67 -0
- package/src/ui/docs/content/toggle-group.md +81 -0
- package/src/ui/docs/content/toggle.md +72 -0
- package/src/ui/docs/content/tokens.md +171 -0
- package/src/ui/docs/content/tooltip.md +50 -0
- package/src/ui/docs/content/truncate.md +37 -0
- package/src/ui/docs/content/ui.md +40 -0
- package/src/ui/docs/content/upgrading.md +48 -0
- package/src/ui/docs/doc-client.tsx +214 -0
- package/src/ui/docs/doc.tsx +301 -0
- package/src/ui/docs/folder.tsx +149 -0
- package/src/ui/docs/index.ts +21 -0
- package/src/ui/docs/markdown.tsx +130 -0
- package/src/ui/docs/md-raw.d.ts +4 -0
- package/src/ui/docs/plugin.ts +104 -0
- package/src/ui/docs/registry.tsx +424 -0
- package/src/ui/docs/standalone.tsx +107 -0
- package/src/ui/drivers/react.tsx +627 -0
- package/src/ui/index.ts +92 -0
- package/src/ui/lib/cn.ts +10 -0
- package/src/ui/lib/zod-pt-br.ts +38 -0
- package/src/ui/meta.ts +412 -0
- package/src/ui/react.tsx +235 -0
- package/src/ui/router.ts +96 -0
- package/src/ui/theme.css +234 -0
- package/src/vite/design.ts +652 -0
- package/src/vite/index.ts +8 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Auditoria
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Auditoria
|
|
6
|
+
|
|
7
|
+
Toda action executada vira um registro imutável: quem, o quê, quando, com que entrada e
|
|
8
|
+
resultado. O runtime emite um `AuditRecord` por execução pro(s) sink(s) configurado(s) — de
|
|
9
|
+
graça, sem o handler pedir. O contrato é o `AuditSink`.
|
|
10
|
+
|
|
11
|
+
## O contrato
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
interface AuditSink {
|
|
15
|
+
emit(record: AuditRecord): Promise<void> | void
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface AuditRecord {
|
|
19
|
+
id: string
|
|
20
|
+
timestamp: string
|
|
21
|
+
action: string
|
|
22
|
+
actionKind?: 'simple' | 'form' | 'list' | 'view' // sink filtra leitura sem tabela por fora
|
|
23
|
+
outcome: 'success' | 'error'
|
|
24
|
+
durationMs: number
|
|
25
|
+
provenance: Provenance // origem do trigger (http, schedule, reaction, ai…)
|
|
26
|
+
// actor.meta chega com name/email do user resolvido (quando presentes) —
|
|
27
|
+
// sink não re-resolve id→nome.
|
|
28
|
+
actor: { id: string | null; type?: 'user' | 'system' | 'integration'; meta?: Record<string, unknown> }
|
|
29
|
+
tenant?: string | null
|
|
30
|
+
input: unknown
|
|
31
|
+
output?: unknown
|
|
32
|
+
error?: ActionError
|
|
33
|
+
severity: 'info' | 'warning' | 'error'
|
|
34
|
+
meta?: Record<string, unknown>
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Drivers
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { consoleAudit } from '@softize/opus/audit/console'
|
|
42
|
+
import { pgAudit } from '@softize/opus/audit/pg'
|
|
43
|
+
|
|
44
|
+
// Dev: uma linha por action (pretty num TTY, json senão).
|
|
45
|
+
const dev = consoleAudit()
|
|
46
|
+
|
|
47
|
+
// Produção: grava na tabela audit_log (Postgres).
|
|
48
|
+
const prod = pgAudit({ pool, table: 'audit_log' })
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Dado sensível: o redator global
|
|
52
|
+
|
|
53
|
+
Sem redator, os sinks persistem input e output **crus** — `user.create`/`setPassword`
|
|
54
|
+
gravaria senha em claro. Os dois drivers aceitam `redact` (função `unknown → unknown`
|
|
55
|
+
aplicada a input/output antes de persistir/imprimir), e a base traz o redator recursivo:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import { redactDeep } from '@softize/opus/audit'
|
|
59
|
+
|
|
60
|
+
// Qualquer profundidade, arrays inclusos; chave que casa
|
|
61
|
+
// /password|passwd|secret|token|authorization|api[-_]?key|credential/i vira '[REDACTED]'.
|
|
62
|
+
const prod = pgAudit({ pool, redact: redactDeep })
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Sink próprio reusa as peças: `redactRecord(record, redator)` e `SENSITIVE_KEY_PATTERN`
|
|
66
|
+
saem do mesmo módulo. O `audit: { redact: [...] }` por action (dot-paths) continua
|
|
67
|
+
valendo — o redator do sink é o cinto de segurança global, não o substituto.
|
|
68
|
+
|
|
69
|
+
## No runtime
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
const runtime = createRuntime({
|
|
73
|
+
// …server/data/auth…
|
|
74
|
+
audit: prod, // um sink, ou vários: [consoleAudit(), pgAudit({ pool })]
|
|
75
|
+
})
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
O que cada action captura se ajusta no contrato (`audit: false` desliga; `audit: { redact:
|
|
79
|
+
['senha'], output: false }` refina). O default captura tudo — input, output e provenance.
|
|
80
|
+
|
|
81
|
+
## Limites (por enquanto)
|
|
82
|
+
|
|
83
|
+
Sink append-only (sem query/retenção embutida — isso é do seu banco). Drivers hoje:
|
|
84
|
+
`console` e `pg`; outros (arquivo, OpenTelemetry…) entram por reincidência.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Autenticação
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Autenticação
|
|
6
|
+
|
|
7
|
+
Quem é o usuário, de qual tenant, e o que ele pode. O contrato é um adapter do core
|
|
8
|
+
(`AuthAdapter`): a cada request o runtime chama `resolveContext` e injeta o resultado no
|
|
9
|
+
handler — `ctx.user`, `ctx.tenantId`, `ctx.can`. O Opus **não** implementa RBAC/ABAC; ele
|
|
10
|
+
delega a decisão pro `can` que o driver pluga.
|
|
11
|
+
|
|
12
|
+
## O contrato
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
interface AuthAdapter {
|
|
16
|
+
resolveContext(req): Promise<{
|
|
17
|
+
user: User | null // { id, ...claims }
|
|
18
|
+
tenantId?: string | null // multi-tenant (opcional)
|
|
19
|
+
can: CanFn // (permission, resource?) => boolean | Promise<boolean>
|
|
20
|
+
}>
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`req` é o request cru do `ServerAdapter`. O `user` sai dos claims/sessão; o `can` é a régua
|
|
25
|
+
de autorização — o Opus só chama, não decide.
|
|
26
|
+
|
|
27
|
+
## Drivers
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { jwtAuth } from '@softize/opus/auth/jwt'
|
|
31
|
+
import { betterAuthSession } from '@softize/opus/auth/better-auth'
|
|
32
|
+
|
|
33
|
+
// JWT: valida o token (HS256 por padrão) e mapeia o payload pro User.
|
|
34
|
+
const jwt = jwtAuth({ secret: process.env.JWT_SECRET! })
|
|
35
|
+
|
|
36
|
+
// better-auth: valida a sessão contra um IdP better-auth remoto.
|
|
37
|
+
const idp = betterAuthSession({
|
|
38
|
+
baseURL: 'https://auth.example.com',
|
|
39
|
+
mapUser: (s) => ({ id: s.user.id, email: s.user.email }),
|
|
40
|
+
can: (user) => (perm) => (user.roles as string[] | undefined)?.includes(perm) ?? false,
|
|
41
|
+
})
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
O `jwt` procura o token no `Authorization: Bearer`, em cookie ou custom (via `getToken`); o
|
|
45
|
+
`secret` pode ser string ou um resolver async por `kid`. O `betterAuthSession` faz fetch da
|
|
46
|
+
sessão no IdP (`timeoutMs`, default 5s) e mapeia pro `User`/tenant/can (o `can` default nega
|
|
47
|
+
tudo — plugue o seu).
|
|
48
|
+
|
|
49
|
+
## No runtime
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
const runtime = createRuntime({
|
|
53
|
+
// …server/data…
|
|
54
|
+
auth: idp,
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
// No handler: o contexto já vem resolvido.
|
|
58
|
+
handler: async (ctx, input) => {
|
|
59
|
+
if (!(await ctx.can('nota.emitir'))) throw error({ code: 'forbidden' })
|
|
60
|
+
return repo.create({ ...input, tenantId: ctx.tenantId })
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
A action declara o gate no contrato (`auth: 'nota.emitir'`) e o runtime cobra antes do
|
|
65
|
+
handler. Sem `auth` configurado, `ctx.user` é null.
|
|
66
|
+
|
|
67
|
+
## Limites (por enquanto)
|
|
68
|
+
|
|
69
|
+
O Opus não modela papéis/permissões — isso é do seu `can`. Drivers hoje: `jwt` e
|
|
70
|
+
`better-auth`; outros (Clerk, Auth.js…) entram por reincidência.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
## Básico
|
|
2
|
+
|
|
3
|
+
AvatarImage com src/alt e AvatarFallback com as iniciais — o fallback aparece enquanto a imagem
|
|
4
|
+
carrega ou se ela falha.
|
|
5
|
+
|
|
6
|
+
```tsx preview
|
|
7
|
+
<Avatar>
|
|
8
|
+
<AvatarImage src="https://github.com/joaocampos.png" alt="João Campos" />
|
|
9
|
+
<AvatarFallback>JC</AvatarFallback>
|
|
10
|
+
</Avatar>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Fallback de iniciais
|
|
14
|
+
|
|
15
|
+
Sem AvatarImage (ou com src quebrado), só o AvatarFallback renderiza — iniciais pra pessoa, ícone
|
|
16
|
+
pra agente.
|
|
17
|
+
|
|
18
|
+
```tsx preview
|
|
19
|
+
<Avatar>
|
|
20
|
+
<AvatarFallback>AL</AvatarFallback>
|
|
21
|
+
</Avatar>
|
|
22
|
+
<Avatar>
|
|
23
|
+
<AvatarFallback>
|
|
24
|
+
<Bot className="size-4" />
|
|
25
|
+
</AvatarFallback>
|
|
26
|
+
</Avatar>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Tamanhos
|
|
30
|
+
|
|
31
|
+
size sm/default/lg — o fallback acompanha o tamanho. sm pra listas densas, lg pra cabeçalho de
|
|
32
|
+
workspace.
|
|
33
|
+
|
|
34
|
+
```tsx preview
|
|
35
|
+
<Avatar size="sm">
|
|
36
|
+
<AvatarFallback>AL</AvatarFallback>
|
|
37
|
+
</Avatar>
|
|
38
|
+
<Avatar size="default">
|
|
39
|
+
<AvatarFallback>AL</AvatarFallback>
|
|
40
|
+
</Avatar>
|
|
41
|
+
<Avatar size="lg">
|
|
42
|
+
<AvatarFallback>AL</AvatarFallback>
|
|
43
|
+
</Avatar>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Com selo de status
|
|
47
|
+
|
|
48
|
+
AvatarBadge é o ponto no canto inferior — verde pro agente developer rodando uma sessão, cinza
|
|
49
|
+
pro ocioso. Tinja com className.
|
|
50
|
+
|
|
51
|
+
```tsx preview
|
|
52
|
+
<Avatar>
|
|
53
|
+
<AvatarFallback>
|
|
54
|
+
<Bot className="size-4" />
|
|
55
|
+
</AvatarFallback>
|
|
56
|
+
<AvatarBadge className="bg-emerald-500" />
|
|
57
|
+
</Avatar>
|
|
58
|
+
<Avatar>
|
|
59
|
+
<AvatarFallback>RV</AvatarFallback>
|
|
60
|
+
<AvatarBadge className="bg-muted-foreground" />
|
|
61
|
+
</Avatar>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Grupo sobreposto
|
|
65
|
+
|
|
66
|
+
AvatarGroup sobrepõe os avatares (com anel de fundo) e AvatarGroupCount fecha a pilha com o
|
|
67
|
+
excedente — os membros do workspace Empresa X.
|
|
68
|
+
|
|
69
|
+
```tsx preview
|
|
70
|
+
<AvatarGroup>
|
|
71
|
+
<Avatar>
|
|
72
|
+
<AvatarImage src="https://github.com/joaocampos.png" alt="João Campos" />
|
|
73
|
+
<AvatarFallback>JC</AvatarFallback>
|
|
74
|
+
</Avatar>
|
|
75
|
+
<Avatar>
|
|
76
|
+
<AvatarFallback>DV</AvatarFallback>
|
|
77
|
+
</Avatar>
|
|
78
|
+
<Avatar>
|
|
79
|
+
<AvatarFallback>RV</AvatarFallback>
|
|
80
|
+
</Avatar>
|
|
81
|
+
<AvatarGroupCount>+3</AvatarGroupCount>
|
|
82
|
+
</AvatarGroup>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Props
|
|
86
|
+
|
|
87
|
+
| Prop | Tipo | Default | Descrição |
|
|
88
|
+
|---|---|---|---|
|
|
89
|
+
| `size` (Avatar) | `'sm' \| 'default' \| 'lg'` | `'default'` | Diâmetro do retrato — o fallback e o selo acompanham. |
|
|
90
|
+
| `src` (AvatarImage) | `string` | | URL da imagem. Enquanto carrega (ou se falha), o AvatarFallback fica no lugar. |
|
|
91
|
+
| `alt` (AvatarImage) | `string` | | Texto alternativo da imagem — o nome da pessoa ou do agente. |
|
|
92
|
+
| `children` (AvatarFallback) | `React.ReactNode` | | O que aparece sem imagem: iniciais (pessoa) ou ícone (agente). |
|
|
93
|
+
| `className` (AvatarBadge) | `string` | | Selo no canto inferior — tinja o fundo (ex.: bg-emerald-500) pra refletir o status. |
|
|
94
|
+
| `children` (AvatarGroupCount) | `React.ReactNode` | | O excedente da pilha (ex.: "+3"), fechando o AvatarGroup. |
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
## Intenção
|
|
2
|
+
|
|
3
|
+
As quatro variantes do shadcn: categoria, papel, alerta. Badge é não-interativo — pra clique, use
|
|
4
|
+
Button.
|
|
5
|
+
|
|
6
|
+
```tsx preview
|
|
7
|
+
<Badge>Agente</Badge>
|
|
8
|
+
<Badge variant="secondary">Skill</Badge>
|
|
9
|
+
<Badge variant="outline">Monorepo</Badge>
|
|
10
|
+
<Badge variant="destructive">Arquivado</Badge>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Tons de status da casa
|
|
14
|
+
|
|
15
|
+
success/warning/info (fill tingido + borda translúcida) — convenção da casa pra estado semântico,
|
|
16
|
+
igual ao KindBadge do Maestro. Não-interativos.
|
|
17
|
+
|
|
18
|
+
```tsx preview
|
|
19
|
+
<Badge variant="success">Ativa</Badge>
|
|
20
|
+
<Badge variant="warning">Aguardando revisor</Badge>
|
|
21
|
+
<Badge variant="info">Em sessão</Badge>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Com ícone
|
|
25
|
+
|
|
26
|
+
Um svg filho ganha size-3 automaticamente — bom pra reforçar o estado sem crescer o rótulo.
|
|
27
|
+
|
|
28
|
+
```tsx preview
|
|
29
|
+
<Badge variant="success"><CircleCheck /> Regressão verde</Badge>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Como link (asChild)
|
|
33
|
+
|
|
34
|
+
asChild renderiza o filho (Radix Slot) — um `<a>` com cara de badge, com hover próprio das
|
|
35
|
+
variantes.
|
|
36
|
+
|
|
37
|
+
```tsx preview
|
|
38
|
+
<Badge asChild variant="secondary">
|
|
39
|
+
<a href="#" onClick={(e) => e.preventDefault()}>Empresa X</a>
|
|
40
|
+
</Badge>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Props
|
|
44
|
+
|
|
45
|
+
| Prop | Tipo | Default | Descrição |
|
|
46
|
+
|---|---|---|---|
|
|
47
|
+
| `variant` | `'default' \| 'secondary' \| 'destructive' \| 'outline' \| 'success' \| 'warning' \| 'info'` | `'default'` | Intenção (default/secondary/destructive/outline) ou tom de status da casa (success/warning/info). |
|
|
48
|
+
| `asChild` | `boolean` | `false` | Renderiza como o filho (Radix Slot) em vez de `<span>` — ex.: um `<a>` com cara de badge. |
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
## Básico
|
|
2
|
+
|
|
3
|
+
A composição é manual: BreadcrumbLink nos níveis navegáveis, BreadcrumbPage no atual (não
|
|
4
|
+
clicável, aria-current=page) e um BreadcrumbSeparator entre cada item.
|
|
5
|
+
|
|
6
|
+
```tsx preview col-start
|
|
7
|
+
<Breadcrumb>
|
|
8
|
+
<BreadcrumbList>
|
|
9
|
+
<BreadcrumbItem>
|
|
10
|
+
<BreadcrumbLink href="#">Empresa X</BreadcrumbLink>
|
|
11
|
+
</BreadcrumbItem>
|
|
12
|
+
<BreadcrumbSeparator />
|
|
13
|
+
<BreadcrumbItem>
|
|
14
|
+
<BreadcrumbLink href="#">empresa-x-api</BreadcrumbLink>
|
|
15
|
+
</BreadcrumbItem>
|
|
16
|
+
<BreadcrumbSeparator />
|
|
17
|
+
<BreadcrumbItem>
|
|
18
|
+
<BreadcrumbPage>Sessão #142</BreadcrumbPage>
|
|
19
|
+
</BreadcrumbItem>
|
|
20
|
+
</BreadcrumbList>
|
|
21
|
+
</Breadcrumb>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Com ícone e separador custom
|
|
25
|
+
|
|
26
|
+
O primeiro nível pode levar um ícone do lucide. BreadcrumbSeparator aceita children pra trocar o
|
|
27
|
+
chevron padrão por outro glifo (aqui, uma barra).
|
|
28
|
+
|
|
29
|
+
```tsx preview col-start
|
|
30
|
+
<Breadcrumb>
|
|
31
|
+
<BreadcrumbList>
|
|
32
|
+
<BreadcrumbItem>
|
|
33
|
+
<BreadcrumbLink href="#" className="flex items-center gap-1.5">
|
|
34
|
+
<Home className="size-3.5" />
|
|
35
|
+
Workspaces
|
|
36
|
+
</BreadcrumbLink>
|
|
37
|
+
</BreadcrumbItem>
|
|
38
|
+
<BreadcrumbSeparator>
|
|
39
|
+
<Slash />
|
|
40
|
+
</BreadcrumbSeparator>
|
|
41
|
+
<BreadcrumbItem>
|
|
42
|
+
<BreadcrumbLink href="#">Empresa X</BreadcrumbLink>
|
|
43
|
+
</BreadcrumbItem>
|
|
44
|
+
<BreadcrumbSeparator>
|
|
45
|
+
<Slash />
|
|
46
|
+
</BreadcrumbSeparator>
|
|
47
|
+
<BreadcrumbItem>
|
|
48
|
+
<BreadcrumbPage>Agentes</BreadcrumbPage>
|
|
49
|
+
</BreadcrumbItem>
|
|
50
|
+
</BreadcrumbList>
|
|
51
|
+
</Breadcrumb>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Colapsado
|
|
55
|
+
|
|
56
|
+
Trilha funda demais pro espaço: BreadcrumbEllipsis substitui os níveis do meio (que viram um
|
|
57
|
+
menu/popover) e mantém só a raiz e o destino.
|
|
58
|
+
|
|
59
|
+
```tsx preview col-start
|
|
60
|
+
<Breadcrumb>
|
|
61
|
+
<BreadcrumbList>
|
|
62
|
+
<BreadcrumbItem>
|
|
63
|
+
<BreadcrumbLink href="#">Empresa X</BreadcrumbLink>
|
|
64
|
+
</BreadcrumbItem>
|
|
65
|
+
<BreadcrumbSeparator />
|
|
66
|
+
<BreadcrumbItem>
|
|
67
|
+
<BreadcrumbEllipsis />
|
|
68
|
+
</BreadcrumbItem>
|
|
69
|
+
<BreadcrumbSeparator />
|
|
70
|
+
<BreadcrumbItem>
|
|
71
|
+
<BreadcrumbLink href="#">empresa-x-web</BreadcrumbLink>
|
|
72
|
+
</BreadcrumbItem>
|
|
73
|
+
<BreadcrumbSeparator />
|
|
74
|
+
<BreadcrumbItem>
|
|
75
|
+
<BreadcrumbPage>Skill clean-code</BreadcrumbPage>
|
|
76
|
+
</BreadcrumbItem>
|
|
77
|
+
</BreadcrumbList>
|
|
78
|
+
</Breadcrumb>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Props
|
|
82
|
+
|
|
83
|
+
| Prop | Tipo | Default | Descrição |
|
|
84
|
+
|---|---|---|---|
|
|
85
|
+
| `asChild` (BreadcrumbLink) | `boolean` | `false` | Funde as props no filho (via Slot) — use pra integrar o Link do seu roteador no lugar do `<a>` nativo. |
|
|
86
|
+
| `href` (BreadcrumbLink) | `string` | | Destino do nível navegável — o que o consumidor decide por item. |
|
|
87
|
+
| `children` (BreadcrumbSeparator) | `React.ReactNode` | `<ChevronRight />` | Glifo entre os itens. Omita pro chevron padrão ou passe outro ícone. |
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
## Básico
|
|
2
|
+
|
|
3
|
+
Envolva os botões no ButtonGroup: as bordas internas colapsam e só as pontas ficam arredondadas.
|
|
4
|
+
Use a mesma variant nos filhos pra manter a barra uniforme.
|
|
5
|
+
|
|
6
|
+
```tsx preview
|
|
7
|
+
<ButtonGroup>
|
|
8
|
+
<Button variant="outline">Visão geral</Button>
|
|
9
|
+
<Button variant="outline">Sessões</Button>
|
|
10
|
+
<Button variant="outline">Skills</Button>
|
|
11
|
+
</ButtonGroup>
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Ação dividida
|
|
15
|
+
|
|
16
|
+
O padrão da casa pra ação primária + variações: o botão principal, um separador e um gatilho de
|
|
17
|
+
menu (size=icon).
|
|
18
|
+
|
|
19
|
+
```tsx preview
|
|
20
|
+
<ButtonGroup>
|
|
21
|
+
<Button icon={Play}>Rodar agente developer</Button>
|
|
22
|
+
<ButtonGroupSeparator />
|
|
23
|
+
<Button size="icon" aria-label="Mais opções">
|
|
24
|
+
<ChevronDown />
|
|
25
|
+
</Button>
|
|
26
|
+
</ButtonGroup>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Com rótulo
|
|
30
|
+
|
|
31
|
+
ButtonGroupText cola um rótulo (fundo muted) ao grupo — bom pra prefixar o contexto. Aceita ícone
|
|
32
|
+
via [&_svg] e asChild pra virar `<label>`.
|
|
33
|
+
|
|
34
|
+
```tsx preview
|
|
35
|
+
<ButtonGroup>
|
|
36
|
+
<ButtonGroupText>
|
|
37
|
+
<GitBranch />
|
|
38
|
+
empresa-x-api
|
|
39
|
+
</ButtonGroupText>
|
|
40
|
+
<Button variant="outline" icon={RotateCw}>
|
|
41
|
+
Sincronizar
|
|
42
|
+
</Button>
|
|
43
|
+
</ButtonGroup>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Vertical
|
|
47
|
+
|
|
48
|
+
orientation=vertical empilha os filhos em coluna — as bordas que colapsam passam a ser as de
|
|
49
|
+
cima/baixo. Combina com size=icon pra uma mini-toolbar.
|
|
50
|
+
|
|
51
|
+
```tsx preview col-start
|
|
52
|
+
<ButtonGroup orientation="vertical">
|
|
53
|
+
<Button variant="outline" size="icon" aria-label="Rodar sessão">
|
|
54
|
+
<Play />
|
|
55
|
+
</Button>
|
|
56
|
+
<Button variant="outline" size="icon" aria-label="Pausar sessão">
|
|
57
|
+
<Pause />
|
|
58
|
+
</Button>
|
|
59
|
+
<Button variant="outline" size="icon" aria-label="Reiniciar sessão">
|
|
60
|
+
<RotateCw />
|
|
61
|
+
</Button>
|
|
62
|
+
</ButtonGroup>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Props
|
|
66
|
+
|
|
67
|
+
| Prop | Tipo | Default | Descrição |
|
|
68
|
+
|---|---|---|---|
|
|
69
|
+
| `orientation` (ButtonGroup) | `'horizontal' \| 'vertical'` | `'horizontal'` | Direção do bloco — vertical empilha os filhos em coluna. |
|
|
70
|
+
| `orientation` (ButtonGroupSeparator) | `'horizontal' \| 'vertical'` | `'vertical'` | Direção do traço divisor — herde do grupo (vertical no horizontal). |
|
|
71
|
+
| `asChild` (ButtonGroupText) | `boolean` | `false` | Renderiza como o filho (Radix Slot) — ex.: virar `<label>` sem forkar o estilo. |
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
## Variantes
|
|
2
|
+
|
|
3
|
+
variant é intenção, não decoração: default pra ação principal (uma por tela), destructive só
|
|
4
|
+
quando destrói de verdade.
|
|
5
|
+
|
|
6
|
+
```tsx preview
|
|
7
|
+
<Button>Salvar</Button>
|
|
8
|
+
<Button variant="secondary">Duplicar</Button>
|
|
9
|
+
<Button variant="outline">Cancelar</Button>
|
|
10
|
+
<Button variant="ghost">Detalhes</Button>
|
|
11
|
+
<Button variant="destructive">Excluir</Button>
|
|
12
|
+
<Button variant="link">Ver documentação</Button>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Tamanhos
|
|
16
|
+
|
|
17
|
+
size icon exige aria-label (não tem texto pra ler). Os só-ícone vêm em três: `icon` (36px), `icon-sm` (32px, pra fileira densa como o header) e `icon-xs` (24px, pra ação DENTRO de um campo — o `trailing` do Input/Select).
|
|
18
|
+
|
|
19
|
+
```tsx preview
|
|
20
|
+
<Button size="sm">Pequeno</Button>
|
|
21
|
+
<Button>Padrão</Button>
|
|
22
|
+
<Button size="lg">Grande</Button>
|
|
23
|
+
<Button size="icon" aria-label="Novo"><Plus /></Button>
|
|
24
|
+
<Button size="icon-sm" variant="ghost" aria-label="Novo"><Plus /></Button>
|
|
25
|
+
<Button size="icon-xs" variant="ghost" aria-label="Novo"><Plus /></Button>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Estados
|
|
29
|
+
|
|
30
|
+
busy = ação em andamento (DEPOIS do clique): o Spinner e o disabled vêm do botão. Com icon, o
|
|
31
|
+
Spinner TROCA o ícone (não soma). Não confunda com carregar conteúdo (ANTES) — isso é Spinner
|
|
32
|
+
centralizado/Skeleton num nível de página.
|
|
33
|
+
|
|
34
|
+
```tsx preview
|
|
35
|
+
<Button disabled>Desabilitado</Button>
|
|
36
|
+
<Button busy>Salvar</Button>
|
|
37
|
+
<Button icon={Plus}>Novo</Button>
|
|
38
|
+
<Button icon={Plus} busy>Novo</Button>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Como outro elemento (asChild)
|
|
42
|
+
|
|
43
|
+
Âncora com cara de botão: asChild renderiza o filho (Radix Slot) — sem forkar estilo.
|
|
44
|
+
buttonVariants serve pro caso sem filho único.
|
|
45
|
+
|
|
46
|
+
```tsx preview
|
|
47
|
+
<Button asChild variant="outline">
|
|
48
|
+
<a href="#" onClick={(e) => e.preventDefault()}>Abrir documentação</a>
|
|
49
|
+
</Button>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Props
|
|
53
|
+
|
|
54
|
+
| Prop | Tipo | Default | Descrição |
|
|
55
|
+
|---|---|---|---|
|
|
56
|
+
| `variant` | `'default' \| 'secondary' \| 'outline' \| 'ghost' \| 'destructive' \| 'link'` | `'default'` | A intenção da ação — define cor e peso visual. |
|
|
57
|
+
| `size` | `'default' \| 'sm' \| 'lg' \| 'icon' \| 'icon-sm' \| 'icon-xs'` | `'default'` | O tamanho. Os icon* são quadrados (36/32/24px), pra botão só de ícone (com aria-label). |
|
|
58
|
+
| `asChild` | `boolean` | `false` | Renderiza como o filho (Radix Slot) em vez de `<button>` — pra âncoras e afins. |
|
|
59
|
+
| `busy` | `boolean` | `false` | Ação em andamento (depois do clique): mostra Spinner + desabilita. Não é "carregando" de conteúdo (que é Spinner/Skeleton num nível de página). |
|
|
60
|
+
| `icon` | `React.ElementType` | | Ícone à esquerda (ex.: icon={Plus}). No busy é trocado pelo Spinner — não soma. |
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
## Dia único
|
|
2
|
+
|
|
3
|
+
mode=single guarda uma Date — controle por selected/onSelect. defaultMonth abre o calendário no mês certo sem mexer na seleção.
|
|
4
|
+
|
|
5
|
+
```tsx preview
|
|
6
|
+
const [day, setDay] = useState<Date | undefined>(new Date(2026, 5, 22))
|
|
7
|
+
|
|
8
|
+
render(
|
|
9
|
+
<Calendar
|
|
10
|
+
mode="single"
|
|
11
|
+
selected={day}
|
|
12
|
+
onSelect={setDay}
|
|
13
|
+
defaultMonth={new Date(2026, 5, 1)}
|
|
14
|
+
/>,
|
|
15
|
+
)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Intervalo
|
|
19
|
+
|
|
20
|
+
mode=range guarda { from, to } — o padrão pra filtrar sessões por janela de datas. O primeiro clique fixa o início; o segundo, o fim.
|
|
21
|
+
|
|
22
|
+
```tsx preview
|
|
23
|
+
const [week, setWeek] = useState<DateRange | undefined>({
|
|
24
|
+
from: new Date(2026, 5, 15),
|
|
25
|
+
to: new Date(2026, 5, 19),
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
render(
|
|
29
|
+
<Calendar
|
|
30
|
+
mode="range"
|
|
31
|
+
selected={week}
|
|
32
|
+
onSelect={setWeek}
|
|
33
|
+
defaultMonth={new Date(2026, 5, 1)}
|
|
34
|
+
/>,
|
|
35
|
+
)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Navegação por dropdown
|
|
39
|
+
|
|
40
|
+
captionLayout=dropdown troca o título do mês por seletores de mês e ano — bom pra pular pra um período distante (ex.: histórico de um repositório) sem clicar mês a mês.
|
|
41
|
+
|
|
42
|
+
```tsx preview
|
|
43
|
+
<Calendar
|
|
44
|
+
mode="single"
|
|
45
|
+
captionLayout="dropdown"
|
|
46
|
+
defaultMonth={new Date(2026, 5, 1)}
|
|
47
|
+
/>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Props
|
|
51
|
+
|
|
52
|
+
| Prop | Tipo | Default | Descrição |
|
|
53
|
+
|---|---|---|---|
|
|
54
|
+
| `mode` | `'single' \| 'multiple' \| 'range'` | | O tipo de seleção — define o formato de selected/onSelect (Date, Date[] ou { from, to }). |
|
|
55
|
+
| `selected` | `Date \| Date[] \| DateRange` | | A seleção atual no modo controlado — o formato segue o mode. Pareie com onSelect. |
|
|
56
|
+
| `onSelect` | `(selected) => void` | | Chamado quando o usuário escolhe uma data. O argumento segue o mode. |
|
|
57
|
+
| `defaultMonth` | `Date` | | O mês exibido ao montar, sem afetar a seleção. |
|
|
58
|
+
| `captionLayout` | `'label' \| 'dropdown' \| 'dropdown-months' \| 'dropdown-years'` | `'label'` | Como o título do mês aparece — label é texto fixo; dropdown vira seletores de mês e ano. |
|
|
59
|
+
| `numberOfMonths` | `number` | `1` | Quantos meses mostrar lado a lado — útil pra escolher um intervalo longo. |
|
|
60
|
+
| `disabled` | `Matcher` | | Datas não selecionáveis — uma Date, um array, um { from, to } ou um predicado (date) => boolean. |
|
|
61
|
+
| `buttonVariant` | `Button['variant']` | `'ghost'` | A variante dos botões de navegação (anterior/próximo). |
|
|
62
|
+
| `showOutsideDays` | `boolean` | `true` | Mostra os dias do mês vizinho que completam a primeira e a última semana. |
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
## Box simples
|
|
2
|
+
|
|
3
|
+
O uso mais comum: a superfície de conteúdo em repouso (rounded-card + ring-edge + shadow-card — ver Tokens & Tema → Elevação por papel) com o padding pelo className. O Card não força flex/gap/padding — você compõe o miolo (sem brigar com space-y/flex seu).
|
|
4
|
+
|
|
5
|
+
```tsx preview col
|
|
6
|
+
<Card className="p-4">
|
|
7
|
+
<p className="text-sm text-muted-foreground">3 sessões ativas neste momento.</p>
|
|
8
|
+
</Card>
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Estruturado (header / conteúdo / rodapé)
|
|
12
|
+
|
|
13
|
+
Pra painel com estrutura: cada slot é dono do próprio padding (como o Dialog). CardTitle/CardDescription no header; CardFooter alinha as ações.
|
|
14
|
+
|
|
15
|
+
```tsx preview col
|
|
16
|
+
<Card>
|
|
17
|
+
<CardHeader>
|
|
18
|
+
<CardTitle>Empresa X</CardTitle>
|
|
19
|
+
<CardDescription>2 repositórios · 3 agentes vinculados.</CardDescription>
|
|
20
|
+
</CardHeader>
|
|
21
|
+
<CardContent>
|
|
22
|
+
<p className="text-sm text-muted-foreground">Última sessão concluída há 2 horas.</p>
|
|
23
|
+
</CardContent>
|
|
24
|
+
<CardFooter className="gap-2">
|
|
25
|
+
<Button size="sm">Abrir sessão</Button>
|
|
26
|
+
<Button size="sm" variant="outline">Ver repositórios</Button>
|
|
27
|
+
</CardFooter>
|
|
28
|
+
</Card>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Com divisor
|
|
32
|
+
|
|
33
|
+
border-b no header / border-t no footer dão o divisor quando a estrutura pede separação visível. A aresta externa é o anel (`ring-edge`), não uma borda — divergência declarada vs shadcn (border + shadow-sm); os divisores internos seguem sendo `border`.
|
|
34
|
+
|
|
35
|
+
```tsx preview col
|
|
36
|
+
<Card>
|
|
37
|
+
<CardHeader className="border-b">
|
|
38
|
+
<CardTitle>Skill code-style</CardTitle>
|
|
39
|
+
<CardDescription>Padrão softize de código e microcopy.</CardDescription>
|
|
40
|
+
</CardHeader>
|
|
41
|
+
<CardContent>
|
|
42
|
+
<p className="text-sm text-muted-foreground">Vinculada a 4 agentes neste workspace.</p>
|
|
43
|
+
</CardContent>
|
|
44
|
+
<CardFooter className="justify-between border-t">
|
|
45
|
+
<Badge variant="success">Publicada</Badge>
|
|
46
|
+
<Button size="sm" variant="outline">Editar skill</Button>
|
|
47
|
+
</CardFooter>
|
|
48
|
+
</Card>
|
|
49
|
+
```
|