@softize/opus 18.5.0 → 20.0.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 +275 -0
- package/bin/cli.mjs +5 -0
- package/bin/lib/check.mjs +198 -10
- package/bin/lib/copy.mjs +29 -0
- package/bin/lib/mcp.mjs +1 -1
- package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +298 -0
- package/docs/adr/0024-fill-height-propagates-by-context.md +118 -0
- package/docs/data-products.md +15 -9
- package/docs/protocol.md +3 -4
- package/docs/releasing.md +2 -2
- package/package.json +2 -2
- package/registry/skills/create-opus-action/scripts/scaffold.mjs +2 -1
- package/registry/templates/app/package.json +1 -1
- package/src/core/runtime.ts +96 -8
- package/src/core/types.ts +5 -2
- package/src/ui/components/patterns/action-form-card.tsx +11 -1
- package/src/ui/components/patterns/content-header.tsx +16 -12
- package/src/ui/components/patterns/fill-region.ts +150 -0
- package/src/ui/components/patterns/heading-level.ts +16 -0
- package/src/ui/components/patterns/list.tsx +103 -73
- package/src/ui/components/patterns/page.tsx +20 -4
- package/src/ui/components/patterns/pane.tsx +294 -0
- package/src/ui/components/patterns/presentation.tsx +81 -54
- package/src/ui/components/patterns/sidebar.tsx +13 -72
- package/src/ui/components/patterns/split.tsx +51 -38
- package/src/ui/components/patterns/surface-anatomy.ts +246 -0
- package/src/ui/components/patterns/surface-header.tsx +51 -6
- package/src/ui/components/patterns/surface-media.tsx +69 -0
- package/src/ui/components/patterns/trigger.tsx +12 -2
- package/src/ui/components/primitives/alert.tsx +9 -1
- package/src/ui/components/primitives/avatar.tsx +7 -3
- package/src/ui/components/primitives/card.tsx +91 -29
- package/src/ui/components/primitives/carousel.tsx +34 -5
- package/src/ui/components/primitives/checkbox.tsx +11 -3
- package/src/ui/components/primitives/command.tsx +14 -5
- package/src/ui/components/primitives/copyable.tsx +120 -10
- package/src/ui/components/primitives/detail.tsx +10 -13
- package/src/ui/components/primitives/dialog.tsx +179 -45
- package/src/ui/components/primitives/drawer.tsx +131 -24
- package/src/ui/components/primitives/empty.tsx +1 -1
- package/src/ui/components/primitives/icon-picker.tsx +6 -2
- package/src/ui/components/primitives/icon-tile.tsx +7 -0
- package/src/ui/components/primitives/input-group.tsx +9 -2
- package/src/ui/components/primitives/input-otp.tsx +1 -1
- package/src/ui/components/primitives/kbd.tsx +1 -1
- package/src/ui/components/primitives/label.tsx +3 -0
- package/src/ui/components/primitives/menu.tsx +5 -0
- package/src/ui/components/primitives/metric-card.tsx +98 -39
- package/src/ui/components/primitives/popover.tsx +1 -1
- package/src/ui/components/primitives/progress.tsx +9 -1
- package/src/ui/components/primitives/slider.tsx +62 -11
- package/src/ui/components/primitives/truncate.tsx +10 -1
- package/src/ui/docs/content/accordion.md +74 -27
- package/src/ui/docs/content/action-form-card.md +24 -15
- package/src/ui/docs/content/action-form-dialog.md +17 -7
- package/src/ui/docs/content/action-form.md +92 -77
- package/src/ui/docs/content/action-list-dialog.md +75 -38
- package/src/ui/docs/content/action-list.md +134 -52
- package/src/ui/docs/content/action-trigger.md +27 -10
- package/src/ui/docs/content/action-view.md +14 -14
- package/src/ui/docs/content/actions.md +161 -66
- package/src/ui/docs/content/ai.md +43 -44
- package/src/ui/docs/content/alert.md +83 -29
- package/src/ui/docs/content/ask.md +51 -26
- package/src/ui/docs/content/aspect-ratio.md +22 -24
- package/src/ui/docs/content/audit.md +23 -19
- package/src/ui/docs/content/auth.md +38 -19
- package/src/ui/docs/content/avatar.md +54 -5
- package/src/ui/docs/content/badge.md +29 -9
- package/src/ui/docs/content/breadcrumb.md +21 -8
- package/src/ui/docs/content/button.md +73 -43
- package/src/ui/docs/content/calendar.md +134 -26
- package/src/ui/docs/content/card.md +252 -35
- package/src/ui/docs/content/carousel.md +37 -6
- package/src/ui/docs/content/chat.md +118 -37
- package/src/ui/docs/content/checkbox.md +73 -7
- package/src/ui/docs/content/cli.md +94 -55
- package/src/ui/docs/content/collapsible.md +43 -19
- package/src/ui/docs/content/command.md +30 -8
- package/src/ui/docs/content/composer.md +23 -8
- package/src/ui/docs/content/content.md +37 -7
- package/src/ui/docs/content/copyable.md +39 -8
- package/src/ui/docs/content/customization.md +55 -46
- package/src/ui/docs/content/cycle.md +33 -18
- package/src/ui/docs/content/data-products.md +115 -0
- package/src/ui/docs/content/data-state.md +20 -6
- package/src/ui/docs/content/data.md +20 -19
- package/src/ui/docs/content/detail.md +12 -5
- package/src/ui/docs/content/dialog.md +219 -28
- package/src/ui/docs/content/dictionary-value.md +4 -4
- package/src/ui/docs/content/dock.md +54 -19
- package/src/ui/docs/content/dot.md +35 -5
- package/src/ui/docs/content/drawer.md +193 -22
- package/src/ui/docs/content/empty-value.md +20 -6
- package/src/ui/docs/content/empty.md +24 -3
- package/src/ui/docs/content/events.md +76 -16
- package/src/ui/docs/content/executions.md +9 -9
- package/src/ui/docs/content/field.md +21 -6
- package/src/ui/docs/content/getting-started.md +44 -34
- package/src/ui/docs/content/icon-picker.md +19 -13
- package/src/ui/docs/content/icon-tile.md +28 -5
- package/src/ui/docs/content/input-otp.md +58 -7
- package/src/ui/docs/content/input.md +22 -3
- package/src/ui/docs/content/item.md +53 -6
- package/src/ui/docs/content/kbd.md +5 -1
- package/src/ui/docs/content/label.md +13 -4
- package/src/ui/docs/content/log.md +5 -5
- package/src/ui/docs/content/markdown.md +5 -4
- package/src/ui/docs/content/mcp.md +49 -23
- package/src/ui/docs/content/menu.md +95 -10
- package/src/ui/docs/content/metric-card.md +68 -21
- package/src/ui/docs/content/observability.md +10 -9
- package/src/ui/docs/content/page.md +125 -11
- package/src/ui/docs/content/pagination.md +13 -5
- package/src/ui/docs/content/pane.md +300 -0
- package/src/ui/docs/content/popover.md +42 -5
- package/src/ui/docs/content/presentation.md +91 -47
- package/src/ui/docs/content/progress.md +27 -14
- package/src/ui/docs/content/queue.md +39 -21
- package/src/ui/docs/content/radio-group.md +26 -4
- package/src/ui/docs/content/router.md +29 -10
- package/src/ui/docs/content/runtime.md +90 -54
- package/src/ui/docs/content/scheduler.md +53 -21
- package/src/ui/docs/content/scroll-area.md +42 -35
- package/src/ui/docs/content/select.md +78 -33
- package/src/ui/docs/content/semantic-context.md +21 -2
- package/src/ui/docs/content/separator.md +15 -9
- package/src/ui/docs/content/sidebar.md +25 -45
- package/src/ui/docs/content/skeleton.md +13 -4
- package/src/ui/docs/content/slider.md +37 -9
- package/src/ui/docs/content/spinner.md +4 -3
- package/src/ui/docs/content/split.md +34 -20
- package/src/ui/docs/content/storage.md +8 -9
- package/src/ui/docs/content/switch.md +10 -7
- package/src/ui/docs/content/table.md +46 -8
- package/src/ui/docs/content/tabs.md +14 -4
- package/src/ui/docs/content/testing.md +76 -31
- package/src/ui/docs/content/textarea.md +5 -4
- package/src/ui/docs/content/toast.md +48 -7
- package/src/ui/docs/content/toggle.md +3 -3
- package/src/ui/docs/content/tokens.md +15 -26
- package/src/ui/docs/content/tooltip.md +18 -2
- package/src/ui/docs/content/truncate.md +36 -6
- package/src/ui/docs/content/ui.md +74 -22
- package/src/ui/docs/content/upgrading.md +45 -36
- package/src/ui/docs/plugin.ts +36 -2
- package/src/ui/docs/registry.tsx +14 -2
- package/src/ui/drivers/react.tsx +16 -8
- package/src/ui/meta.ts +38 -32
- package/src/ui/react.tsx +14 -0
- package/src/vite/design.ts +16 -4
|
@@ -1,38 +1,44 @@
|
|
|
1
1
|
## Seção recolhível
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use `Collapsible` para esconder um bloco secundário atrás de um gatilho, mantendo o restante da
|
|
4
|
+
tela visível. `CollapsibleTrigger` já renderiza um `<button>` e alterna o `CollapsibleContent`.
|
|
5
|
+
Sem `open`, o componente guarda o próprio estado; `defaultOpen` apenas define se ele começa aberto.
|
|
4
6
|
|
|
5
7
|
```tsx preview col
|
|
6
8
|
<Collapsible defaultOpen className="w-full max-w-sm space-y-2">
|
|
7
|
-
<CollapsibleTrigger className="flex w-full items-center justify-between rounded-md border border-border px-3 py-2 text-sm font-medium hover:bg-muted">
|
|
8
|
-
|
|
9
|
-
<
|
|
9
|
+
<CollapsibleTrigger className="flex w-full items-center justify-between rounded-md border border-border px-3 py-2 text-sm font-medium hover:bg-muted [&[data-state=open]>svg]:rotate-180">
|
|
10
|
+
Serviços incluídos na revisão
|
|
11
|
+
<ChevronDown className="size-4 text-muted-foreground transition-transform" />
|
|
10
12
|
</CollapsibleTrigger>
|
|
11
13
|
<CollapsibleContent className="space-y-1 px-3 text-sm text-muted-foreground">
|
|
12
|
-
<p>
|
|
13
|
-
<p>
|
|
14
|
-
<p>
|
|
14
|
+
<p>Troca de óleo</p>
|
|
15
|
+
<p>Filtro de ar</p>
|
|
16
|
+
<p>Alinhamento</p>
|
|
15
17
|
</CollapsibleContent>
|
|
16
18
|
</Collapsible>
|
|
17
19
|
```
|
|
18
20
|
|
|
21
|
+
O gatilho recebe `data-state="open"` ou `data-state="closed"`. O seletor
|
|
22
|
+
`[&[data-state=open]>svg]:rotate-180` usa esse atributo para girar o ícone sem estado próprio.
|
|
23
|
+
|
|
19
24
|
## Controlado
|
|
20
25
|
|
|
21
|
-
open
|
|
26
|
+
Use `open` e `onOpenChange` quando outra parte da tela precisar conhecer o estado, por exemplo para
|
|
27
|
+
trocar o texto do gatilho ou guardar a preferência da pessoa.
|
|
22
28
|
|
|
23
29
|
```tsx preview col
|
|
24
30
|
const [open, setOpen] = useState(false)
|
|
25
31
|
|
|
26
32
|
render(
|
|
27
33
|
<Collapsible open={open} onOpenChange={setOpen} className="w-full max-w-sm space-y-2">
|
|
28
|
-
<CollapsibleTrigger className="flex w-full items-center justify-between rounded-md border border-border px-3 py-2 text-sm font-medium hover:bg-muted">
|
|
29
|
-
|
|
30
|
-
<ChevronDown className=
|
|
34
|
+
<CollapsibleTrigger className="flex w-full items-center justify-between rounded-md border border-border px-3 py-2 text-sm font-medium hover:bg-muted [&[data-state=open]>svg]:rotate-180">
|
|
35
|
+
{open ? 'Ocultar peças utilizadas' : 'Mostrar peças utilizadas'}
|
|
36
|
+
<ChevronDown className="size-4 text-muted-foreground transition-transform" />
|
|
31
37
|
</CollapsibleTrigger>
|
|
32
38
|
<CollapsibleContent className="space-y-1 px-3 text-sm text-muted-foreground">
|
|
33
|
-
<p>
|
|
34
|
-
<p>
|
|
35
|
-
<p>
|
|
39
|
+
<p>Filtro de óleo</p>
|
|
40
|
+
<p>Pastilhas de freio</p>
|
|
41
|
+
<p>Velas de ignição</p>
|
|
36
42
|
</CollapsibleContent>
|
|
37
43
|
</Collapsible>,
|
|
38
44
|
)
|
|
@@ -40,16 +46,17 @@ render(
|
|
|
40
46
|
|
|
41
47
|
## Desabilitado
|
|
42
48
|
|
|
43
|
-
disabled no Collapsible
|
|
49
|
+
Use `disabled` no `Collapsible` quando a seção não puder mudar de estado por enquanto. O gatilho
|
|
50
|
+
fica desabilitado e a seção permanece como está; no exemplo, fechada.
|
|
44
51
|
|
|
45
52
|
```tsx preview col
|
|
46
53
|
<Collapsible disabled className="w-full max-w-sm space-y-2">
|
|
47
54
|
<CollapsibleTrigger className="flex w-full items-center justify-between rounded-md border border-border px-3 py-2 text-sm font-medium disabled:cursor-not-allowed disabled:opacity-50">
|
|
48
|
-
|
|
49
|
-
<
|
|
55
|
+
Revisões anteriores
|
|
56
|
+
<ChevronDown className="size-4 text-muted-foreground" />
|
|
50
57
|
</CollapsibleTrigger>
|
|
51
58
|
<CollapsibleContent className="px-3 text-sm text-muted-foreground">
|
|
52
|
-
<p>
|
|
59
|
+
<p>O histórico fica disponível depois da sincronização com a concessionária.</p>
|
|
53
60
|
</CollapsibleContent>
|
|
54
61
|
</Collapsible>
|
|
55
62
|
```
|
|
@@ -61,4 +68,21 @@ disabled no Collapsible trava o gatilho — a seção fica fixa no estado atual
|
|
|
61
68
|
| `defaultOpen` | `boolean` | `false` | Estado inicial no modo não controlado. |
|
|
62
69
|
| `open` | `boolean` | | Estado no modo controlado. Use com `onOpenChange`. |
|
|
63
70
|
| `onOpenChange` | `(open: boolean) => void` | | Chamado a cada abertura ou fechamento. |
|
|
64
|
-
| `disabled` | `boolean` | `false` |
|
|
71
|
+
| `disabled` | `boolean` | `false` | Desabilita o gatilho e mantém a seção no estado atual. |
|
|
72
|
+
| `className` | `string` | | Ajusta o contêiner. |
|
|
73
|
+
|
|
74
|
+
## Propriedades de CollapsibleTrigger
|
|
75
|
+
|
|
76
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
77
|
+
|---|---|---|---|
|
|
78
|
+
| `asChild` | `boolean` | `false` | Usa o filho como gatilho, como um `Button`, sem criar outro `<button>`. |
|
|
79
|
+
| `className` | `string` | | Ajusta o gatilho. O estado chega em `data-state`. |
|
|
80
|
+
| `children` | `ReactNode` | | Rótulo do gatilho e, quando houver, o ícone de estado. |
|
|
81
|
+
|
|
82
|
+
## Propriedades de CollapsibleContent
|
|
83
|
+
|
|
84
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
85
|
+
|---|---|---|---|
|
|
86
|
+
| `forceMount` | `boolean` | `false` | Mantém o conteúdo montado mesmo fechado, por exemplo para animá-lo por CSS. |
|
|
87
|
+
| `className` | `string` | | Ajusta a região recolhível. |
|
|
88
|
+
| `children` | `ReactNode` | | Conteúdo exibido quando a seção está aberta. |
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
## Lista filtrável inline
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Use `Command` quando a pessoa precisar encontrar um item ou uma ação digitando parte do nome e
|
|
4
|
+
escolher pelo teclado. A lista filtra enquanto a busca muda, organiza os itens em grupos e mostra
|
|
5
|
+
um estado vazio quando nada corresponde. `Command` também é a base do `Select` pesquisável; para
|
|
6
|
+
escolher um valor em um formulário, use [`Select`](/ui/select).
|
|
5
7
|
|
|
6
8
|
```tsx preview
|
|
7
9
|
<Command className="max-w-sm rounded-lg border border-border">
|
|
@@ -14,26 +16,38 @@ do Select pesquisável; para uma escolha em formulário, use Select.
|
|
|
14
16
|
</CommandGroup>
|
|
15
17
|
<CommandSeparator />
|
|
16
18
|
<CommandGroup heading="Ações">
|
|
17
|
-
<CommandItem><Plus />
|
|
19
|
+
<CommandItem><Plus /> Criar sessão <CommandShortcut>⌘N</CommandShortcut></CommandItem>
|
|
18
20
|
<CommandItem><Play /> Iniciar ambiente</CommandItem>
|
|
19
21
|
</CommandGroup>
|
|
20
22
|
</CommandList>
|
|
21
23
|
</Command>
|
|
22
24
|
```
|
|
23
25
|
|
|
24
|
-
##
|
|
26
|
+
## Paleta de comandos em modal
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
`CommandDialog` apresenta a mesma lista dentro de um [`Dialog`](/ui/dialog), com um cabeçalho
|
|
29
|
+
compacto que mostra o título e o botão de fechar. A descrição fica disponível somente para
|
|
30
|
+
tecnologias assistivas. O componente não registra atalho de teclado: para abrir a paleta com
|
|
31
|
+
⌘K, o aplicativo escuta o evento e altera `open`, como no exemplo.
|
|
29
32
|
|
|
30
33
|
```tsx preview
|
|
31
34
|
const [open, setOpen] = useState(false)
|
|
32
35
|
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
const onKeyDown = (event: KeyboardEvent): void => {
|
|
38
|
+
if (event.key.toLowerCase() === 'k' && (event.metaKey || event.ctrlKey)) {
|
|
39
|
+
event.preventDefault()
|
|
40
|
+
setOpen((current) => !current)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
document.addEventListener('keydown', onKeyDown)
|
|
44
|
+
return () => document.removeEventListener('keydown', onKeyDown)
|
|
45
|
+
}, [])
|
|
46
|
+
|
|
33
47
|
render(
|
|
34
48
|
<>
|
|
35
49
|
<Button variant="outline" onClick={() => setOpen(true)}>
|
|
36
|
-
Abrir
|
|
50
|
+
Abrir paleta <CommandShortcut>⌘K</CommandShortcut>
|
|
37
51
|
</Button>
|
|
38
52
|
<CommandDialog open={open} onOpenChange={setOpen}>
|
|
39
53
|
<CommandInput placeholder="Buscar comando…" />
|
|
@@ -80,6 +94,7 @@ render(
|
|
|
80
94
|
| `title` | `string` | `'Paleta de comandos'` | Título exibido no header compacto; também nomeia o diálogo para tecnologias assistivas. |
|
|
81
95
|
| `description` | `string` | `'Busque um comando para executar…'` | Descrição disponível somente para tecnologias assistivas. |
|
|
82
96
|
| `showCloseButton` | `boolean` | `true` | Exibe o botão de fechamento. |
|
|
97
|
+
| `className` | `string` | | Classes do conteúdo do diálogo, como a largura. |
|
|
83
98
|
|
|
84
99
|
## Propriedades de CommandItem
|
|
85
100
|
|
|
@@ -96,3 +111,10 @@ render(
|
|
|
96
111
|
|---|---|---|---|
|
|
97
112
|
| `heading` | `React.ReactNode` | | Título exibido acima dos itens do grupo. |
|
|
98
113
|
| `value` | `string` | | Identificador do grupo; obrigatório e único quando não há `heading`. |
|
|
114
|
+
|
|
115
|
+
`CommandList` limita a altura a `18.75rem` e rola o excedente; `CommandEmpty` aparece somente quando
|
|
116
|
+
nenhum item corresponde à busca; `CommandSeparator` divide grupos; e `CommandShortcut` mostra, à
|
|
117
|
+
direita do item, a combinação de teclas que o aplicativo associou à ação. Esses componentes não têm
|
|
118
|
+
propriedades próprias além das props nativas do elemento que renderizam; `className` soma classes
|
|
119
|
+
ao estilo padrão de cada um. O atalho exibido por `CommandShortcut` é apenas informativo:
|
|
120
|
+
registrar o evento continua com o aplicativo.
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
## Envio de texto
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use `Composer` quando a tela precisar receber um texto para envio sem apresentar uma conversa, como
|
|
4
|
+
a descrição que abre uma nova sessão de trabalho. É a mesma caixa de escrita do [Chat](/ui/chat):
|
|
5
|
+
um campo que cresce com o texto, com o botão de enviar dentro da moldura. **Enter** envia e
|
|
6
|
+
**Shift+Enter** quebra a linha.
|
|
7
|
+
|
|
8
|
+
O componente é controlado: `value` e `onChange` pertencem a quem o compõe, que também limpa o texto
|
|
9
|
+
depois do envio. Os callbacks opcionais `onHistoryPrevious` e `onHistoryNext` permitem que esse
|
|
10
|
+
dono use **↑** e **↓** para navegar por um histórico próprio; sem eles, as setas mantêm o
|
|
11
|
+
comportamento nativo do campo.
|
|
4
12
|
|
|
5
13
|
```tsx preview col
|
|
6
14
|
const [text, setText] = React.useState('')
|
|
@@ -15,9 +23,15 @@ render(
|
|
|
15
23
|
)
|
|
16
24
|
```
|
|
17
25
|
|
|
18
|
-
## Com seletores
|
|
26
|
+
## Com seletores na barra de ações
|
|
27
|
+
|
|
28
|
+
Com `actions`, o composer passa a ter duas linhas: o campo em cima e, embaixo, uma barra com os
|
|
29
|
+
seletores à esquerda e o botão de enviar à direita. Use ali escolhas que definem o destino do texto,
|
|
30
|
+
como o aplicativo, a tarefa ou o agente. Mantenha os controles discretos, como um `Button` `ghost` ou
|
|
31
|
+
um [`Select`](/ui/select) `variant="ghost"`: a barra acompanha o texto e não é um formulário.
|
|
19
32
|
|
|
20
|
-
|
|
33
|
+
`submitDisabled` impede o envio enquanto falta uma condição além do texto, como escolher o
|
|
34
|
+
aplicativo, sem esconder a barra.
|
|
21
35
|
|
|
22
36
|
```tsx preview col
|
|
23
37
|
const [text, setText] = React.useState('')
|
|
@@ -55,11 +69,12 @@ render(
|
|
|
55
69
|
|---|---|---|---|
|
|
56
70
|
| `value` | `string` | | Texto controlado; o dono do estado é quem compõe. |
|
|
57
71
|
| `onChange` | `(value: string) => void` | | Recebe cada alteração do texto. |
|
|
58
|
-
| `onSubmit` | `() => void` | | Enter (sem Shift) ou
|
|
72
|
+
| `onSubmit` | `() => void` | | Chamado por Enter (sem Shift) ou pelo botão de enviar, somente quando há texto e nem `busy` nem `submitDisabled` estão ativos. |
|
|
59
73
|
| `onHistoryPrevious` / `onHistoryNext` | `() => boolean` | | Navegação por ↑/↓ no histórico do dono; retorne `true` quando a tecla foi consumida. |
|
|
60
|
-
| `busy` | `boolean` | `false` |
|
|
61
|
-
| `submitDisabled` | `boolean` | `false` |
|
|
74
|
+
| `busy` | `boolean` | `false` | Bloqueia o envio enquanto a operação anterior corre e troca o ícone de enviar por um spinner. O campo continua editável. |
|
|
75
|
+
| `submitDisabled` | `boolean` | `false` | Bloqueia o envio por uma condição do consumidor, como um seletor ainda sem escolha. |
|
|
62
76
|
| `placeholder` | `string` | `'Escreva uma mensagem…'` | Texto de orientação do campo. |
|
|
63
|
-
| `rows` | `number` | `1` | Linhas iniciais do
|
|
77
|
+
| `rows` | `number` | `1` | Linhas iniciais do campo, que cresce com o conteúdo até `10rem`. |
|
|
64
78
|
| `autoFocus` | `boolean` | | Foca o campo ao montar. |
|
|
65
|
-
| `actions` | `ReactNode` | | Controles discretos à esquerda do enviar;
|
|
79
|
+
| `actions` | `ReactNode` | | Controles discretos à esquerda do botão de enviar; com eles, o composer passa a ter duas linhas. |
|
|
80
|
+
| `className` | `string` | | Classes aplicadas à moldura do composer. |
|
|
@@ -4,12 +4,19 @@ Use `Content` para dar título e estrutura a uma região dentro de `PageBody`, `
|
|
|
4
4
|
`DialogBody` ou outra superfície. Ele renderiza uma `section` ligada ao próprio título e mantém o
|
|
5
5
|
espaçamento entre header e body. `ContentHeader` pertence sempre a essa estrutura; não o use solto.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
O `Content` é contínuo por definição e não expõe `layout`: ele é uma região nomeada dentro de outra
|
|
8
|
+
superfície, e a escolha entre regiões seccionadas e contínuas pertence a quem a hospeda — o
|
|
9
|
+
[`Card`](/ui/card), o [`Dialog`](/ui/dialog), o [`Drawer`](/ui/drawer) ou o [`Pane`](/ui/pane).
|
|
10
|
+
Quando a própria região precisar de uma fronteira visível, use `framed`, descrito adiante. Pelo
|
|
11
|
+
mesmo motivo o `Content` não tem rodapé: um rodapé pertence à superfície que a hospeda.
|
|
12
|
+
|
|
13
|
+
No caso comum, prefira o shorthand. `count` mostra o total de itens ao lado do título:
|
|
8
14
|
|
|
9
15
|
```tsx preview col
|
|
10
16
|
render(
|
|
11
17
|
<Content
|
|
12
18
|
title="Dispositivos conectados"
|
|
19
|
+
count={3}
|
|
13
20
|
description="Sessões com acesso à sua conta."
|
|
14
21
|
actions={<Button variant="outline">Encerrar outras sessões</Button>}
|
|
15
22
|
>
|
|
@@ -126,13 +133,33 @@ do pai correto, filhos estruturais indiretos e a mistura de shorthand com compos
|
|
|
126
133
|
## Coleção nomeada
|
|
127
134
|
|
|
128
135
|
Quando a região principal da página é uma coleção, `Content` nomeia e governa essa coleção, enquanto
|
|
129
|
-
`ActionList` executa a consulta. Coloque criar, importar e outras ações sobre o conjunto completo em
|
|
136
|
+
[`ActionList`](/ui/action-list) executa a consulta. Coloque criar, importar e outras ações sobre o conjunto completo em
|
|
130
137
|
`ContentActions`. Busca, filtros, atualização e operações dependentes do recorte atual permanecem na
|
|
131
138
|
toolbar da lista. Essa divisão aproxima cada comando do objeto que ele afeta sem criar uma família
|
|
132
|
-
paralela de componentes `List*`.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
139
|
+
paralela de componentes `List*`.
|
|
140
|
+
|
|
141
|
+
Quando a coleção é o assunto da página, use `variant="page"` com `level={1}`: o `Content` assume a
|
|
142
|
+
escala do cabeçalho de [`Page`](/ui/page) e passa a ser o heading principal. As ações ficam no
|
|
143
|
+
extremo oposto ao título, como em qualquer variante.
|
|
144
|
+
|
|
145
|
+
```tsx preview col
|
|
146
|
+
render(
|
|
147
|
+
<Content level={1} variant="page" title="Clientes" actions={<Button>Criar cliente</Button>}>
|
|
148
|
+
<div className="rounded-lg border border-border p-4 text-sm text-muted-foreground">
|
|
149
|
+
A lista de clientes ocupa o corpo.
|
|
150
|
+
</div>
|
|
151
|
+
</Content>,
|
|
152
|
+
);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
A criação usa um `Button` textual na variante padrão (`solid`) e no tamanho `default`, sem ícone,
|
|
156
|
+
nomeado `Criar {recurso}` (por exemplo, `Criar cliente`); o diálogo aberto pelo gatilho repete esse
|
|
157
|
+
título, e a edição usa `Editar {recurso}`.
|
|
158
|
+
|
|
159
|
+
Em uma [`Page`](/ui/page) com `layout="fill"`, a coleção pode ocupar a altura da página e rolar só os
|
|
160
|
+
resultados. `Content` e `ContentBody` repassam a altura até a [`ActionList`](/ui/action-list) sem
|
|
161
|
+
classes adicionais; um `Content` irmão, sem lista, mantém a altura do próprio conteúdo. A seção
|
|
162
|
+
“Ocupar a altura disponível” da `ActionList` mostra a composição completa.
|
|
136
163
|
|
|
137
164
|
## Propriedades de Content
|
|
138
165
|
|
|
@@ -141,12 +168,15 @@ gatilho repete esse título e a edição usa `Editar recurso`.
|
|
|
141
168
|
| `title` | `ReactNode` | | Forma curta: título da região (vira o heading ligado à `section`). |
|
|
142
169
|
| `count` | `number` | | Forma curta: total de itens ao lado do título da região. |
|
|
143
170
|
| `description` | `ReactNode` | | Forma curta: frase de apoio sob o título. |
|
|
144
|
-
| `actions` | `ReactNode` | | Forma curta: ações sobre a região inteira
|
|
171
|
+
| `actions` | `ReactNode` | | Forma curta: ações sobre a região inteira, no extremo oposto ao título. |
|
|
145
172
|
| `level` | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | `2` | Nível semântico do heading, independente do destaque visual. |
|
|
146
173
|
| `variant` | `'page' \| 'section'` | `'section'` | Hierarquia visual: `page` reproduz o cabeçalho de `Page`; `section` é a região dentro de uma superfície. |
|
|
147
174
|
| `as` | `'section' \| 'fieldset'` | `'section'` | Usa uma região com heading ou um grupo de campos cujo título é a legenda nativa. |
|
|
148
175
|
| `framed` | `boolean` | `false` | Envolve o header e o body com a moldura estrutural da região. |
|
|
149
176
|
| `frameVariant` | `'default' \| 'legend'` | `'default'` | Em `fieldset` com moldura, mantém a borda contínua ou integra visualmente a legenda à borda. Não altera a semântica. |
|
|
177
|
+
| `children` | `ReactNode` | | Corpo da região na forma curta, ou `ContentHeader` e `ContentBody` na composição explícita. |
|
|
178
|
+
|
|
179
|
+
As demais propriedades são as nativas de `section` ou, com `as="fieldset"`, de `fieldset`.
|
|
150
180
|
|
|
151
181
|
Na composição explícita, `ContentHeader` recebe `ContentTitle`, `ContentMeta`, `ContentDescription` e
|
|
152
182
|
`ContentActions`, e `ContentBody` recebe o conteúdo; nenhum desses slots aceita `title` ou `level`
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
## Copiar por ícone
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use `Copyable` para que a pessoa copie um valor exato com um clique, como um identificador, uma
|
|
4
|
+
chave ou um endereço. Sem filhos, ele é um botão só com ícone: copia `value` e troca o ícone por um
|
|
5
|
+
check durante 1,5 segundo. Essa forma cabe em barras de ferramentas e células estreitas, ao lado do
|
|
6
|
+
valor exibido.
|
|
4
7
|
|
|
5
8
|
```tsx preview
|
|
6
9
|
<Copyable value="opus_sk_1a2b3c4d5e6f" className="text-muted-foreground hover:text-foreground" />
|
|
@@ -8,7 +11,8 @@ Sem filhos, é um botão-ícone: copia `value` e o ícone vira um check por ~1.5
|
|
|
8
11
|
|
|
9
12
|
## Com rótulo
|
|
10
13
|
|
|
11
|
-
Com filhos, o valor visível fica à esquerda e o ícone à direita — clicar no conjunto copia.
|
|
14
|
+
Com filhos, o valor visível fica à esquerda e o ícone à direita — clicar no conjunto copia. Borda,
|
|
15
|
+
fonte monoespaçada e demais detalhes visuais entram por `className`.
|
|
12
16
|
|
|
13
17
|
```tsx preview
|
|
14
18
|
<Copyable
|
|
@@ -19,21 +23,48 @@ Com filhos, o valor visível fica à esquerda e o ícone à direita — clicar n
|
|
|
19
23
|
</Copyable>
|
|
20
24
|
```
|
|
21
25
|
|
|
26
|
+
## Nome acessível
|
|
27
|
+
|
|
28
|
+
Com rótulo visível, o nome do botão é o próprio texto dos filhos. Quem usa comando de voz aciona o
|
|
29
|
+
botão dizendo o que vê, e o nome não muda durante a confirmação. Sem filhos, o botão se chama
|
|
30
|
+
“Copiar” e, durante a confirmação, “Copiado”.
|
|
31
|
+
|
|
32
|
+
A confirmação também é anunciada por leitores de tela: uma região de status, invisível na tela e
|
|
33
|
+
fora do botão, diz “Copiado” sem interromper a leitura em andamento.
|
|
34
|
+
|
|
35
|
+
Quando houver mais de um botão só com ícone na mesma tela, passe `aria-label` para distinguir o que
|
|
36
|
+
cada um copia, como “Copiar o ID do pedido”. Com rótulo visível, o `aria-label` raramente é
|
|
37
|
+
necessário; se usá-lo, comece pelo texto que a pessoa vê. Um `aria-label` próprio substitui os nomes
|
|
38
|
+
padrão, inclusive o “Copiado”, e o anúncio da confirmação continua.
|
|
39
|
+
|
|
22
40
|
## Duração do feedback
|
|
23
41
|
|
|
24
|
-
`feedbackMs` ajusta por quanto tempo a confirmação aparece; o padrão é 1500 milissegundos.
|
|
25
|
-
área de transferência não estiver disponível, a ação não produz efeito.
|
|
42
|
+
`feedbackMs` ajusta por quanto tempo a confirmação aparece; o padrão é 1500 milissegundos.
|
|
26
43
|
|
|
27
44
|
```tsx preview
|
|
28
|
-
<Copyable
|
|
29
|
-
|
|
45
|
+
<Copyable
|
|
46
|
+
value="https://opus.softize.dev/w/empresa-x"
|
|
47
|
+
feedbackMs={3000}
|
|
48
|
+
className="text-muted-foreground hover:text-foreground"
|
|
49
|
+
>
|
|
50
|
+
Copiar link
|
|
30
51
|
</Copyable>
|
|
31
52
|
```
|
|
32
53
|
|
|
54
|
+
A confirmação só aparece depois que a área de transferência aceita o texto. Se ela não estiver
|
|
55
|
+
disponível, como em uma página sem HTTPS, ou recusar a escrita, o ícone não muda.
|
|
56
|
+
|
|
33
57
|
## Propriedades de Copyable
|
|
34
58
|
|
|
35
59
|
| Propriedade | Tipo | Padrão | Descrição |
|
|
36
60
|
|---|---|---|---|
|
|
37
|
-
| `value` | `string` | | O texto que vai para a área de transferência no clique. |
|
|
61
|
+
| `value` | `string` | obrigatório | O texto que vai para a área de transferência no clique. |
|
|
38
62
|
| `feedbackMs` | `number` | `1500` | Duração do estado “copiado”, em milissegundos. |
|
|
39
|
-
| `children` | `ReactNode` | | Rótulo ao lado do ícone
|
|
63
|
+
| `children` | `ReactNode` | | Rótulo visível ao lado do ícone. Quando presente, é o nome acessível do botão. |
|
|
64
|
+
| `aria-label` | `string` | sem filhos, `'Copiar'`, ou `'Copiado'` durante a confirmação | Nome acessível do botão. Substitui o nome vindo dos filhos e os nomes padrão. |
|
|
65
|
+
|
|
66
|
+
Os demais atributos vão para o elemento `button`. Um `onClick` próprio roda antes da cópia. O
|
|
67
|
+
botão é o único elemento que o Copyable coloca no contêiner: a região de status fica no fim do
|
|
68
|
+
`body`, visível apenas para leitores de tela, e é uma só para todos os Copyable da página. Ela
|
|
69
|
+
aparece com o primeiro Copyable montado e sai com o último, então uma tabela com um botão de copiar
|
|
70
|
+
por linha não multiplica regiões.
|
|
@@ -4,15 +4,16 @@ title: Customização
|
|
|
4
4
|
|
|
5
5
|
# Customização
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
Quando a aplicação precisa de outra marca, outra largura ou outro comportamento, o Opus oferece
|
|
8
|
+
cinco alavancas, da mais global à mais pontual. Todas partem de compor e configurar os componentes
|
|
9
|
+
existentes; nenhuma exige copiar um componente para dentro do projeto e mantê-lo à parte. O que não
|
|
10
|
+
couber nelas evolui no próprio Opus, para valer em todos os projetos.
|
|
10
11
|
|
|
11
12
|
## 1 · Identidade por tokens
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
Para aplicar a marca, sobrescreva as variáveis CSS em `:root` depois de importar `theme.css`. Todos
|
|
15
|
+
os componentes acompanham a mudança, e o tema escuro também: a classe `.dark` redeclara as mesmas
|
|
16
|
+
variáveis.
|
|
16
17
|
|
|
17
18
|
```css
|
|
18
19
|
/* index.css do app — depois do import do tema. */
|
|
@@ -26,27 +27,20 @@ divergência declarada), para valer para casa toda.
|
|
|
26
27
|
```
|
|
27
28
|
|
|
28
29
|
O Opus deriva de `rounded-xs` a `rounded-2xl` dessa base. Assim, a marca muda a presença dos
|
|
29
|
-
cantos sem substituir as utilities escolhidas pelos componentes nem criar tokens por papel.
|
|
30
|
+
cantos sem substituir as utilities escolhidas pelos componentes nem criar tokens por papel. A lista
|
|
31
|
+
completa de variáveis está em [Tokens](/ui/tokens).
|
|
30
32
|
|
|
31
33
|
O tema não define `font-size` em `html`: a fonte raiz pertence ao navegador e à aplicação.
|
|
32
34
|
Quando o produto precisa de outra densidade, declare essa escolha no CSS do app. Por exemplo,
|
|
33
|
-
`html { font-size: 93.75%; }`
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
## Layout desktop
|
|
37
|
-
|
|
38
|
-
O Opus oferece suporte a partir de `64rem` e não muda a composição conforme a largura da viewport.
|
|
39
|
-
Classes de tamanho, como `size="sm"`, escolhem a densidade do componente; não representam
|
|
40
|
-
breakpoints. Overflow e limites contra a janela continuam protegendo o conteúdo.
|
|
41
|
-
|
|
42
|
-
Não adicione uma adaptação isolada por viewport ou container em `className`. Uma futura
|
|
43
|
-
responsividade precisa começar pelo shell e formar um contrato comum para navegação, splits,
|
|
44
|
-
tabelas e componentes.
|
|
35
|
+
`html { font-size: 93.75%; }` reduz a raiz a 15/16 da fonte que o navegador oferece, e tudo que o
|
|
36
|
+
Opus mede em `rem` acompanha a proporção. O valor continua sendo uma escolha do app, não uma regra
|
|
37
|
+
da biblioteca.
|
|
45
38
|
|
|
46
39
|
## 2 · className em tudo
|
|
47
40
|
|
|
48
|
-
|
|
49
|
-
|
|
41
|
+
Todo componente aceita `className` e o combina com as próprias classes por tailwind-merge: quando
|
|
42
|
+
duas classes disputam a mesma propriedade, a do consumidor vence. Use essa alavanca para o layout
|
|
43
|
+
local, como largura, margem e grid, e não para repintar o visual dos componentes.
|
|
50
44
|
|
|
51
45
|
```tsx
|
|
52
46
|
<Button className="w-full">Continuar</Button>
|
|
@@ -56,73 +50,88 @@ tabelas e componentes.
|
|
|
56
50
|
|
|
57
51
|
## 3 · Recomposição estrutural
|
|
58
52
|
|
|
59
|
-
|
|
60
|
-
|
|
53
|
+
Os componentes são divididos em partes nomeadas, como `CardHeader` e `CardBody`, e a tela usa só as
|
|
54
|
+
que precisa. `asChild` faz o filho assumir o estilo e o comportamento do componente, e as funções
|
|
55
|
+
`*Variants` aplicam o visual de um componente a um elemento qualquer.
|
|
61
56
|
|
|
62
57
|
```tsx preview
|
|
63
58
|
<Button asChild variant="outline">
|
|
64
|
-
<a href="#" onClick={(e) => e.preventDefault()}
|
|
59
|
+
<a href="#" onClick={(e) => e.preventDefault()}>Link com aparência de botão</a>
|
|
65
60
|
</Button>
|
|
66
61
|
```
|
|
67
62
|
|
|
68
63
|
```tsx
|
|
69
|
-
//
|
|
64
|
+
// Partes: só o que a tela pede.
|
|
70
65
|
<Card>
|
|
71
66
|
<CardHeader><CardTitle>Workspace</CardTitle></CardHeader>
|
|
72
67
|
<CardBody>…</CardBody>
|
|
73
68
|
</Card>
|
|
74
69
|
|
|
75
|
-
// asChild: o
|
|
70
|
+
// asChild: o link assume o estilo e o comportamento do botão.
|
|
76
71
|
<Button asChild><a href="/docs">Abrir documentação</a></Button>
|
|
77
72
|
|
|
78
|
-
// buttonVariants:
|
|
73
|
+
// buttonVariants: o visual do botão em outro elemento.
|
|
79
74
|
<a className={buttonVariants({ variant: 'outline' })}>Link estilizado</a>
|
|
80
75
|
```
|
|
81
76
|
|
|
82
77
|
## 4 · Comportamento por props
|
|
83
78
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
79
|
+
Tudo o que abre ou seleciona aceita modo controlado (`open` e `onOpenChange`, `value` e
|
|
80
|
+
`onValueChange`). Posicionamento (`align`, `side`, `sideOffset`) e tempos (`delayDuration`) também
|
|
81
|
+
são props. A orquestração fica com a aplicação; o visual continua o dos componentes.
|
|
87
82
|
|
|
88
83
|
```tsx
|
|
89
84
|
const [open, setOpen] = useState(false)
|
|
90
85
|
|
|
91
86
|
<Dialog open={open} onOpenChange={setOpen}>…</Dialog>
|
|
92
|
-
<
|
|
87
|
+
<Dialog mode="alert">…</Dialog> {/* exige uma resposta explícita */}
|
|
93
88
|
<PopoverContent align="start" sideOffset={8} />
|
|
94
89
|
<TooltipProvider delayDuration={200}>…
|
|
95
90
|
```
|
|
96
91
|
|
|
92
|
+
Para exigir uma resposta, use `Dialog mode="alert"`: ele não fecha pelo clique externo e não mostra
|
|
93
|
+
o botão de fechar. Apenas esconder o botão com `showCloseButton={false}` não impede o fechamento por
|
|
94
|
+
Esc nem pelo clique externo. Veja [Dialog](/ui/dialog).
|
|
95
|
+
|
|
97
96
|
## 5 · Nos patterns, o contrato é a customização
|
|
98
97
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
98
|
+
Nos patterns — [`ActionForm`](/ui/action-form), [`ActionList`](/ui/action-list),
|
|
99
|
+
[`ActionView`](/ui/action-view) e [`ActionTrigger`](/ui/action-trigger) —, o contrato da action
|
|
100
|
+
determina campos, colunas e estados. A tela ajusta o que é dela: `fieldOptions` no formulário,
|
|
101
|
+
`columns` na lista, a função de render no `ActionView`. Para ir além, envolva o pattern em um
|
|
102
|
+
componente da feature em vez de copiá-lo.
|
|
102
103
|
|
|
103
104
|
```tsx
|
|
104
|
-
// O
|
|
105
|
+
// O formulário vem do contrato; a tela só fornece as opções que conhece.
|
|
105
106
|
<ActionForm
|
|
106
107
|
action={workspaceCreateContract}
|
|
107
|
-
fieldOptions={{
|
|
108
|
+
fieldOptions={{ ownerId: workspaceMembers }}
|
|
108
109
|
onSuccess={(d) => navigate(`/workspaces/${d.id}`)}
|
|
109
110
|
/>
|
|
110
111
|
|
|
111
|
-
//
|
|
112
|
+
// A lista recebe as colunas; a célula é render livre.
|
|
112
113
|
<ActionList action={listAction} input={{ q }} columns={[
|
|
113
114
|
{ key: 'name', header: 'Workspace', cell: (w) => w.name },
|
|
114
115
|
]} />
|
|
115
116
|
```
|
|
116
117
|
|
|
117
|
-
##
|
|
118
|
+
## Largura mínima e densidade
|
|
118
119
|
|
|
119
|
-
|
|
120
|
-
|
|
120
|
+
O Opus oferece suporte a partir de `64rem` e não muda a composição conforme a largura da viewport.
|
|
121
|
+
Props de tamanho, como `size="sm"`, escolhem a densidade do componente; não representam
|
|
122
|
+
breakpoints. Overflow e limites contra a janela continuam protegendo o conteúdo.
|
|
121
123
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
124
|
+
Não adicione em `className` uma adaptação isolada por viewport ou container. Uma responsividade
|
|
125
|
+
futura precisa começar pelo shell e formar um contrato comum para navegação, splits, tabelas e
|
|
126
|
+
componentes.
|
|
127
|
+
|
|
128
|
+
## O que fica fora das alavancas
|
|
129
|
+
|
|
130
|
+
Alguns detalhes fazem parte da identidade visual do Opus e são iguais em todo projeto: a faixa do
|
|
131
|
+
rodapé do dialog, a elevação no tema escuro, o escurecimento do botão ao ser pressionado
|
|
132
|
+
(`active:brightness-90`) e a seta do tooltip. Eles não são configuráveis pela aplicação.
|
|
125
133
|
|
|
126
|
-
Se uma necessidade real não cabe nas alavancas (tokens
|
|
127
|
-
contrato),
|
|
128
|
-
declarada
|
|
134
|
+
Se uma necessidade real não cabe nas alavancas (tokens, `className`, partes e `asChild`, props e
|
|
135
|
+
contrato), não crie uma variação local. Proponha a evolução do componente no Opus, com a
|
|
136
|
+
divergência declarada conforme a skill `build-opus-ui`; assim a mudança vale para todos os projetos
|
|
137
|
+
e passa a aparecer nesta documentação.
|