@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,121 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { SendHorizontalIcon } from 'lucide-react'
|
|
3
|
+
import { cn } from '../../lib/cn.ts'
|
|
4
|
+
import { Button } from './button.tsx'
|
|
5
|
+
import { Spinner } from './spinner.tsx'
|
|
6
|
+
import { Textarea } from './textarea.tsx'
|
|
7
|
+
|
|
8
|
+
export interface ComposerProps {
|
|
9
|
+
/** Texto (controlado): o dono do estado é o consumidor — o `<Chat>` por dentro, ou o app
|
|
10
|
+
* (ex.: o composer de criação de sessão do Maestro, que nem tem histórico). */
|
|
11
|
+
value: string
|
|
12
|
+
onChange: (value: string) => void
|
|
13
|
+
/** Enter (sem Shift) ou o botão enviar. Só dispara quando dá pra enviar (ver `submitDisabled`). */
|
|
14
|
+
onSubmit: () => void
|
|
15
|
+
/** Trava o composer enquanto o turno/ação corre — o enviar vira spinner. */
|
|
16
|
+
busy?: boolean
|
|
17
|
+
/** Gate EXTRA de envio além de "vazio" e "busy" (ex.: falta escolher o app). Desabilita o
|
|
18
|
+
* enviar sem esconder a barra. */
|
|
19
|
+
submitDisabled?: boolean
|
|
20
|
+
placeholder?: string
|
|
21
|
+
rows?: number
|
|
22
|
+
autoFocus?: boolean
|
|
23
|
+
/** Controles discretos na barra de ações, à ESQUERDA do enviar (seletores de app, agente,
|
|
24
|
+
* contexto…). Presente → o composer vira DUAS LINHAS (textarea em cima, barra limpa
|
|
25
|
+
* embaixo); ausente → linha única (textarea + enviar inline), o composer clássico do Chat.
|
|
26
|
+
* Estilize os controles como discretos (ghost/sem borda) — a barra não é um formulário. */
|
|
27
|
+
actions?: React.ReactNode
|
|
28
|
+
className?: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* <Composer /> — a caixa de escrever da casa: textarea (Enter envia / Shift+Enter quebra
|
|
33
|
+
* linha) numa pílula elevada (rounded-card + ring-edge + shadow-card), com o enviar dentro.
|
|
34
|
+
* É o composer do `<Chat>` extraído pra ser reusável SOZINHO — o Maestro cria sessão com
|
|
35
|
+
* ele (sem chat nenhum), e a GB prende seletores nele. Com `actions`, ganha uma barra
|
|
36
|
+
* embaixo pros seletores; sem, é a linha única de sempre.
|
|
37
|
+
*/
|
|
38
|
+
export function Composer({
|
|
39
|
+
value,
|
|
40
|
+
onChange,
|
|
41
|
+
onSubmit,
|
|
42
|
+
busy = false,
|
|
43
|
+
submitDisabled = false,
|
|
44
|
+
placeholder = 'Escreva uma mensagem…',
|
|
45
|
+
rows = 1,
|
|
46
|
+
autoFocus,
|
|
47
|
+
actions,
|
|
48
|
+
className,
|
|
49
|
+
}: ComposerProps): React.ReactElement {
|
|
50
|
+
const canSubmit = value.trim() !== '' && !busy && !submitDisabled
|
|
51
|
+
const fire = (): void => {
|
|
52
|
+
if (canSubmit) onSubmit()
|
|
53
|
+
}
|
|
54
|
+
const twoRow = actions !== undefined
|
|
55
|
+
|
|
56
|
+
const textarea = (
|
|
57
|
+
<Textarea
|
|
58
|
+
autoFocus={autoFocus}
|
|
59
|
+
value={value}
|
|
60
|
+
onChange={(e) => onChange(e.target.value)}
|
|
61
|
+
onKeyDown={(e) => {
|
|
62
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
63
|
+
e.preventDefault()
|
|
64
|
+
fire()
|
|
65
|
+
}
|
|
66
|
+
}}
|
|
67
|
+
placeholder={placeholder}
|
|
68
|
+
rows={rows}
|
|
69
|
+
className={cn(
|
|
70
|
+
// `dark:bg-transparent` sobrescreve o `dark:bg-input/30` do primitivo Textarea —
|
|
71
|
+
// dentro da pílula do composer a textarea é PLANA (herda o bg-card), sem fundo de
|
|
72
|
+
// input próprio no escuro (o `bg-transparent` sozinho não pega a variante dark).
|
|
73
|
+
'max-h-40 min-h-0 resize-none border-0 bg-transparent shadow-none focus-visible:ring-0 dark:bg-transparent',
|
|
74
|
+
twoRow ? 'w-full px-1.5 py-1.5' : 'flex-1 px-0 py-2',
|
|
75
|
+
)}
|
|
76
|
+
/>
|
|
77
|
+
)
|
|
78
|
+
const send = (
|
|
79
|
+
<Button
|
|
80
|
+
type="submit"
|
|
81
|
+
size="icon"
|
|
82
|
+
variant="ghost"
|
|
83
|
+
disabled={!canSubmit}
|
|
84
|
+
aria-label="Enviar"
|
|
85
|
+
className="shrink-0 text-muted-foreground hover:text-foreground"
|
|
86
|
+
>
|
|
87
|
+
{busy ? <Spinner size="sm" /> : <SendHorizontalIcon className="size-4" />}
|
|
88
|
+
</Button>
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<form
|
|
93
|
+
data-slot="composer"
|
|
94
|
+
onSubmit={(e) => {
|
|
95
|
+
e.preventDefault()
|
|
96
|
+
fire()
|
|
97
|
+
}}
|
|
98
|
+
className={cn(
|
|
99
|
+
'rounded-card bg-card ring-1 ring-edge shadow-card transition focus-within:ring-2 focus-within:ring-primary/15',
|
|
100
|
+
twoRow ? 'p-2' : 'relative flex items-end gap-2 p-2 pl-3.5',
|
|
101
|
+
className,
|
|
102
|
+
)}
|
|
103
|
+
>
|
|
104
|
+
{twoRow ? (
|
|
105
|
+
<>
|
|
106
|
+
{textarea}
|
|
107
|
+
<div data-slot="composer-actions" className="mt-1 flex items-center justify-between gap-2">
|
|
108
|
+
{/* Seletores à esquerda (discretos), enviar à direita. */}
|
|
109
|
+
<div className="flex min-w-0 items-center gap-1">{actions}</div>
|
|
110
|
+
{send}
|
|
111
|
+
</div>
|
|
112
|
+
</>
|
|
113
|
+
) : (
|
|
114
|
+
<>
|
|
115
|
+
{textarea}
|
|
116
|
+
{send}
|
|
117
|
+
</>
|
|
118
|
+
)}
|
|
119
|
+
</form>
|
|
120
|
+
)
|
|
121
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { CheckIcon, CopyIcon } from 'lucide-react'
|
|
3
|
+
import { cn } from '../../lib/cn.ts'
|
|
4
|
+
|
|
5
|
+
export interface CopyableProps extends Omit<React.ComponentProps<'button'>, 'value'> {
|
|
6
|
+
/** O texto que vai pro clipboard no clique. */
|
|
7
|
+
value: string
|
|
8
|
+
/** Quanto tempo (ms) o feedback de "copiado" dura. Default 1500. */
|
|
9
|
+
feedbackMs?: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Clicar-pra-copiar com feedback: copia `value` pro clipboard e troca o ícone por um
|
|
14
|
+
* check por ~1.5s. Sem filhos é um botão-ícone (bom pra toolbar/célula); com filhos, o
|
|
15
|
+
* rótulo visível + o ícone à direita. Pra IDs, tokens, slugs, URLs. No-op silencioso se
|
|
16
|
+
* o clipboard não estiver disponível (contexto inseguro/SSR).
|
|
17
|
+
*/
|
|
18
|
+
export function Copyable({
|
|
19
|
+
value,
|
|
20
|
+
feedbackMs = 1500,
|
|
21
|
+
children,
|
|
22
|
+
className,
|
|
23
|
+
onClick,
|
|
24
|
+
...props
|
|
25
|
+
}: CopyableProps): React.ReactElement {
|
|
26
|
+
const [copied, setCopied] = React.useState(false)
|
|
27
|
+
const timer = React.useRef<ReturnType<typeof setTimeout> | undefined>(undefined)
|
|
28
|
+
React.useEffect(() => () => clearTimeout(timer.current), [])
|
|
29
|
+
return (
|
|
30
|
+
<button
|
|
31
|
+
type="button"
|
|
32
|
+
data-slot="copyable"
|
|
33
|
+
data-copied={copied || undefined}
|
|
34
|
+
aria-label={copied ? 'Copiado' : 'Copiar'}
|
|
35
|
+
onClick={(e) => {
|
|
36
|
+
onClick?.(e)
|
|
37
|
+
void navigator.clipboard?.writeText(value).then(() => {
|
|
38
|
+
setCopied(true)
|
|
39
|
+
clearTimeout(timer.current)
|
|
40
|
+
timer.current = setTimeout(() => setCopied(false), feedbackMs)
|
|
41
|
+
})
|
|
42
|
+
}}
|
|
43
|
+
className={cn('inline-flex items-center gap-1.5', className)}
|
|
44
|
+
{...props}
|
|
45
|
+
>
|
|
46
|
+
{children}
|
|
47
|
+
{copied ? <CheckIcon className="size-3.5 text-emerald-500" /> : <CopyIcon className="size-3.5" />}
|
|
48
|
+
</button>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import * as DialogPrimitive from '@radix-ui/react-dialog'
|
|
3
|
+
import { XIcon } from 'lucide-react'
|
|
4
|
+
import { cn } from '../../lib/cn.ts'
|
|
5
|
+
|
|
6
|
+
export function Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) {
|
|
7
|
+
return <DialogPrimitive.Root data-slot="dialog" {...props} />
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
|
|
11
|
+
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
|
|
15
|
+
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>) {
|
|
19
|
+
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function DialogOverlay({
|
|
23
|
+
className,
|
|
24
|
+
...props
|
|
25
|
+
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
|
|
26
|
+
return (
|
|
27
|
+
<DialogPrimitive.Overlay
|
|
28
|
+
data-slot="dialog-overlay"
|
|
29
|
+
className={cn(
|
|
30
|
+
'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',
|
|
31
|
+
className,
|
|
32
|
+
)}
|
|
33
|
+
{...props}
|
|
34
|
+
/>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function DialogContent({
|
|
39
|
+
className,
|
|
40
|
+
children,
|
|
41
|
+
showCloseButton = true,
|
|
42
|
+
...props
|
|
43
|
+
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
|
|
44
|
+
/** Mostra o "X" de fechar no canto. Desligue pra um modal que exige ação explícita. */
|
|
45
|
+
showCloseButton?: boolean
|
|
46
|
+
}) {
|
|
47
|
+
return (
|
|
48
|
+
<DialogPortal>
|
|
49
|
+
<DialogOverlay />
|
|
50
|
+
{/* Superfície elevada: mantemos bg-card (divergência da casa; o shadcn usa bg-background). */}
|
|
51
|
+
<DialogPrimitive.Content
|
|
52
|
+
data-slot="dialog-content"
|
|
53
|
+
className={cn(
|
|
54
|
+
// Superfície PURA: só a casca (borda/bg/raio/sombra) + coluna flex com teto de
|
|
55
|
+
// altura. O padding mora nos SLOTS (Header/Body/Footer), não aqui — assim ninguém
|
|
56
|
+
// precisa negar margem pra escapar de um p-6 embutido.
|
|
57
|
+
"fixed left-[50%] top-[50%] z-50 flex max-h-[85vh] w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] flex-col overflow-hidden rounded-dialog bg-card ring-1 ring-edge shadow-dialog duration-200 outline-none data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-lg",
|
|
58
|
+
className,
|
|
59
|
+
)}
|
|
60
|
+
{...props}
|
|
61
|
+
>
|
|
62
|
+
{children}
|
|
63
|
+
{showCloseButton && (
|
|
64
|
+
<DialogPrimitive.Close
|
|
65
|
+
data-slot="dialog-close"
|
|
66
|
+
className="absolute right-4 top-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 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"
|
|
67
|
+
>
|
|
68
|
+
<XIcon />
|
|
69
|
+
<span className="sr-only">Fechar</span>
|
|
70
|
+
</DialogPrimitive.Close>
|
|
71
|
+
)}
|
|
72
|
+
</DialogPrimitive.Content>
|
|
73
|
+
</DialogPortal>
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {
|
|
78
|
+
return (
|
|
79
|
+
<div
|
|
80
|
+
data-slot="dialog-header"
|
|
81
|
+
// Fixo (shrink-0), com divisor embaixo (border-b). p-5: padding igual nos quatro
|
|
82
|
+
// lados, casando com o body. O :has tira o divisor quando o header é seguido
|
|
83
|
+
// DIRETO pelo footer (confirm sem body) — senão o border-b dele encostaria no
|
|
84
|
+
// border-t do footer (2px); aí o divisor é só o do footer.
|
|
85
|
+
className={cn(
|
|
86
|
+
'flex shrink-0 flex-col gap-2 border-b p-5 text-center sm:text-left',
|
|
87
|
+
'[&:has(+[data-slot=dialog-footer])]:border-b-0',
|
|
88
|
+
className,
|
|
89
|
+
)}
|
|
90
|
+
{...props}
|
|
91
|
+
/>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function DialogBody({ className, ...props }: React.ComponentProps<'div'>) {
|
|
96
|
+
return (
|
|
97
|
+
<div
|
|
98
|
+
data-slot="dialog-body"
|
|
99
|
+
// O miolo que ROLA: cresce, scrolla e padda a si mesmo. Pra corpo edge-to-edge
|
|
100
|
+
// (cmdk, iframe de lente) passe `className` zerando o padding.
|
|
101
|
+
className={cn('min-h-0 flex-1 overflow-y-auto p-5', className)}
|
|
102
|
+
{...props}
|
|
103
|
+
/>
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function DialogFooter({ className, ...props }: React.ComponentProps<'div'>) {
|
|
108
|
+
return (
|
|
109
|
+
<div
|
|
110
|
+
data-slot="dialog-footer"
|
|
111
|
+
// Faixa destacada (divergência da casa, ao estilo do site do shadcn): borda em cima
|
|
112
|
+
// + fundo muted. Sangra até as bordas sozinha — o content não tem padding, então não
|
|
113
|
+
// há -mx pra compensar; o overflow-hidden do content arredonda o canto de baixo.
|
|
114
|
+
className={cn(
|
|
115
|
+
'flex shrink-0 flex-col-reverse gap-2 border-t bg-muted/30 px-5 py-4 sm:flex-row sm:justify-end',
|
|
116
|
+
className,
|
|
117
|
+
)}
|
|
118
|
+
{...props}
|
|
119
|
+
/>
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function DialogTitle({
|
|
124
|
+
className,
|
|
125
|
+
...props
|
|
126
|
+
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
|
|
127
|
+
return (
|
|
128
|
+
<DialogPrimitive.Title
|
|
129
|
+
data-slot="dialog-title"
|
|
130
|
+
className={cn('text-lg font-semibold leading-none', className)}
|
|
131
|
+
{...props}
|
|
132
|
+
/>
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function DialogDescription({
|
|
137
|
+
className,
|
|
138
|
+
...props
|
|
139
|
+
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
|
|
140
|
+
return (
|
|
141
|
+
<DialogPrimitive.Description
|
|
142
|
+
data-slot="dialog-description"
|
|
143
|
+
className={cn('text-sm text-muted-foreground', className)}
|
|
144
|
+
{...props}
|
|
145
|
+
/>
|
|
146
|
+
)
|
|
147
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { XIcon } from "lucide-react"
|
|
3
|
+
import { Dialog as DrawerPrimitive } from "radix-ui"
|
|
4
|
+
|
|
5
|
+
import { cn } from '../../lib/cn.ts'
|
|
6
|
+
|
|
7
|
+
function Drawer({ ...props }: React.ComponentProps<typeof DrawerPrimitive.Root>) {
|
|
8
|
+
return <DrawerPrimitive.Root data-slot="drawer" {...props} />
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function DrawerTrigger({
|
|
12
|
+
...props
|
|
13
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {
|
|
14
|
+
return <DrawerPrimitive.Trigger data-slot="drawer-trigger" {...props} />
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function DrawerClose({
|
|
18
|
+
...props
|
|
19
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Close>) {
|
|
20
|
+
return <DrawerPrimitive.Close data-slot="drawer-close" {...props} />
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function DrawerPortal({
|
|
24
|
+
...props
|
|
25
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Portal>) {
|
|
26
|
+
return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props} />
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function DrawerOverlay({
|
|
30
|
+
className,
|
|
31
|
+
...props
|
|
32
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Overlay>) {
|
|
33
|
+
return (
|
|
34
|
+
<DrawerPrimitive.Overlay
|
|
35
|
+
data-slot="drawer-overlay"
|
|
36
|
+
className={cn(
|
|
37
|
+
"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",
|
|
38
|
+
className
|
|
39
|
+
)}
|
|
40
|
+
{...props}
|
|
41
|
+
/>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function DrawerContent({
|
|
46
|
+
className,
|
|
47
|
+
children,
|
|
48
|
+
side = "right",
|
|
49
|
+
showCloseButton = true,
|
|
50
|
+
...props
|
|
51
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Content> & {
|
|
52
|
+
side?: "top" | "right" | "bottom" | "left"
|
|
53
|
+
showCloseButton?: boolean
|
|
54
|
+
}) {
|
|
55
|
+
return (
|
|
56
|
+
<DrawerPortal>
|
|
57
|
+
<DrawerOverlay />
|
|
58
|
+
<DrawerPrimitive.Content
|
|
59
|
+
data-slot="drawer-content"
|
|
60
|
+
className={cn(
|
|
61
|
+
"fixed z-50 flex flex-col gap-4 bg-background shadow-dialog transition ease-in-out data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:animate-in data-[state=open]:duration-500",
|
|
62
|
+
side === "right" &&
|
|
63
|
+
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
|
|
64
|
+
side === "left" &&
|
|
65
|
+
"inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
|
|
66
|
+
side === "top" &&
|
|
67
|
+
"inset-x-0 top-0 h-auto border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
|
|
68
|
+
side === "bottom" &&
|
|
69
|
+
"inset-x-0 bottom-0 h-auto border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
|
|
70
|
+
className
|
|
71
|
+
)}
|
|
72
|
+
{...props}
|
|
73
|
+
>
|
|
74
|
+
{children}
|
|
75
|
+
{showCloseButton && (
|
|
76
|
+
<DrawerPrimitive.Close className="absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none data-[state=open]:bg-secondary">
|
|
77
|
+
<XIcon className="size-4" />
|
|
78
|
+
<span className="sr-only">Close</span>
|
|
79
|
+
</DrawerPrimitive.Close>
|
|
80
|
+
)}
|
|
81
|
+
</DrawerPrimitive.Content>
|
|
82
|
+
</DrawerPortal>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
87
|
+
return (
|
|
88
|
+
<div
|
|
89
|
+
data-slot="drawer-header"
|
|
90
|
+
className={cn("flex flex-col gap-1.5 p-4", className)}
|
|
91
|
+
{...props}
|
|
92
|
+
/>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
97
|
+
return (
|
|
98
|
+
<div
|
|
99
|
+
data-slot="drawer-footer"
|
|
100
|
+
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
|
|
101
|
+
{...props}
|
|
102
|
+
/>
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function DrawerTitle({
|
|
107
|
+
className,
|
|
108
|
+
...props
|
|
109
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Title>) {
|
|
110
|
+
return (
|
|
111
|
+
<DrawerPrimitive.Title
|
|
112
|
+
data-slot="drawer-title"
|
|
113
|
+
className={cn("font-semibold text-foreground", className)}
|
|
114
|
+
{...props}
|
|
115
|
+
/>
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function DrawerDescription({
|
|
120
|
+
className,
|
|
121
|
+
...props
|
|
122
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Description>) {
|
|
123
|
+
return (
|
|
124
|
+
<DrawerPrimitive.Description
|
|
125
|
+
data-slot="drawer-description"
|
|
126
|
+
className={cn("text-sm text-muted-foreground", className)}
|
|
127
|
+
{...props}
|
|
128
|
+
/>
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export {
|
|
133
|
+
Drawer,
|
|
134
|
+
DrawerTrigger,
|
|
135
|
+
DrawerClose,
|
|
136
|
+
DrawerContent,
|
|
137
|
+
DrawerHeader,
|
|
138
|
+
DrawerFooter,
|
|
139
|
+
DrawerTitle,
|
|
140
|
+
DrawerDescription,
|
|
141
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
2
|
+
|
|
3
|
+
import { cn } from '../../lib/cn.ts'
|
|
4
|
+
|
|
5
|
+
function Empty({ className, ...props }: React.ComponentProps<"div">) {
|
|
6
|
+
return (
|
|
7
|
+
<div
|
|
8
|
+
data-slot="empty"
|
|
9
|
+
className={cn(
|
|
10
|
+
"flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6 text-center text-balance md:p-12",
|
|
11
|
+
className
|
|
12
|
+
)}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
19
|
+
return (
|
|
20
|
+
<div
|
|
21
|
+
data-slot="empty-header"
|
|
22
|
+
className={cn(
|
|
23
|
+
"flex max-w-sm flex-col items-center gap-2 text-center",
|
|
24
|
+
className
|
|
25
|
+
)}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const emptyMediaVariants = cva(
|
|
32
|
+
"mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
33
|
+
{
|
|
34
|
+
variants: {
|
|
35
|
+
variant: {
|
|
36
|
+
default: "bg-transparent",
|
|
37
|
+
icon: "flex size-10 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground [&_svg:not([class*='size-'])]:size-6",
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
defaultVariants: {
|
|
41
|
+
variant: "default",
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
function EmptyMedia({
|
|
47
|
+
className,
|
|
48
|
+
variant = "default",
|
|
49
|
+
...props
|
|
50
|
+
}: React.ComponentProps<"div"> & VariantProps<typeof emptyMediaVariants>) {
|
|
51
|
+
return (
|
|
52
|
+
<div
|
|
53
|
+
data-slot="empty-icon"
|
|
54
|
+
data-variant={variant}
|
|
55
|
+
className={cn(emptyMediaVariants({ variant, className }))}
|
|
56
|
+
{...props}
|
|
57
|
+
/>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
62
|
+
return (
|
|
63
|
+
<div
|
|
64
|
+
data-slot="empty-title"
|
|
65
|
+
className={cn("text-lg font-medium tracking-tight", className)}
|
|
66
|
+
{...props}
|
|
67
|
+
/>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
|
|
72
|
+
return (
|
|
73
|
+
<div
|
|
74
|
+
data-slot="empty-description"
|
|
75
|
+
className={cn(
|
|
76
|
+
"text-sm/relaxed text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
|
|
77
|
+
className
|
|
78
|
+
)}
|
|
79
|
+
{...props}
|
|
80
|
+
/>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function EmptyContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
85
|
+
return (
|
|
86
|
+
<div
|
|
87
|
+
data-slot="empty-content"
|
|
88
|
+
className={cn(
|
|
89
|
+
"flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance",
|
|
90
|
+
className
|
|
91
|
+
)}
|
|
92
|
+
{...props}
|
|
93
|
+
/>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export {
|
|
98
|
+
Empty,
|
|
99
|
+
EmptyHeader,
|
|
100
|
+
EmptyTitle,
|
|
101
|
+
EmptyDescription,
|
|
102
|
+
EmptyContent,
|
|
103
|
+
EmptyMedia,
|
|
104
|
+
}
|