@softize/opus 23.0.0 → 24.0.1
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 +99 -0
- package/bin/cli.mjs +2 -0
- package/bin/lib/check.mjs +241 -42
- package/bin/lib/copy.mjs +16 -34
- package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +6 -0
- package/docs/adr/0039-filter-presentation-is-independent-from-filter-type.md +3 -3
- package/docs/adr/0040-tabs-use-a-single-navigation-presentation.md +5 -6
- package/docs/adr/0042-toggle-group-shares-the-grouped-control-grammar.md +79 -0
- package/docs/adr/0043-neutral-surfaces-and-interactions-use-opaque-role-tokens.md +73 -0
- package/docs/adr/0044-neutral-appearance-derives-from-bounded-axes-per-mode.md +133 -0
- package/docs/adr/0045-surface-replaces-content-and-card.md +230 -0
- package/docs/adr/0046-structural-body-owns-content-rhythm.md +103 -0
- package/docs/adr/0047-pane-is-only-a-split-layout-unit.md +71 -0
- package/docs/adr/0048-modal-intro-separates-content-from-chrome.md +34 -0
- package/docs/adr/0049-inline-navigation-groups-with-the-heading.md +95 -0
- package/package.json +5 -1
- package/registry/skills/build-opus-ui/SKILL.md +11 -11
- package/registry/skills/build-opus-ui/references/evaluations.md +10 -9
- package/registry/skills/build-opus-ui/references/ui-patterns.md +17 -16
- package/registry/templates/app/src/App.tsx +24 -12
- package/src/ui/appearance.ts +149 -0
- package/src/ui/components/patterns/action-form-card.tsx +46 -33
- package/src/ui/components/patterns/action-list-dialog.tsx +2 -4
- package/src/ui/components/patterns/confirm.tsx +3 -1
- package/src/ui/components/patterns/fill-region.ts +8 -7
- package/src/ui/components/patterns/form-dialog.tsx +1 -3
- package/src/ui/components/patterns/form.tsx +8 -7
- package/src/ui/components/patterns/list.tsx +15 -19
- package/src/ui/components/patterns/page.tsx +15 -11
- package/src/ui/components/patterns/pane.tsx +9 -240
- package/src/ui/components/patterns/presentation.tsx +14 -14
- package/src/ui/components/patterns/sidebar.tsx +7 -17
- package/src/ui/components/patterns/split.tsx +111 -59
- package/src/ui/components/patterns/{surface-header.tsx → structural-header.tsx} +90 -27
- package/src/ui/components/patterns/structural-media.tsx +26 -0
- package/src/ui/components/patterns/surface-anatomy.ts +71 -61
- package/src/ui/components/patterns/surface-assistant.tsx +2 -4
- package/src/ui/components/patterns/surface.tsx +620 -0
- package/src/ui/components/patterns/trigger.tsx +11 -9
- package/src/ui/components/primitives/ask.tsx +155 -83
- package/src/ui/components/primitives/button.tsx +11 -9
- package/src/ui/components/primitives/checkbox.tsx +8 -8
- package/src/ui/components/primitives/command.tsx +1 -1
- package/src/ui/components/primitives/composer.tsx +47 -45
- package/src/ui/components/primitives/detail.tsx +9 -2
- package/src/ui/components/primitives/dialog.tsx +60 -26
- package/src/ui/components/primitives/drawer.tsx +58 -12
- package/src/ui/components/primitives/icon-picker.tsx +90 -73
- package/src/ui/components/primitives/input-group.tsx +35 -28
- package/src/ui/components/primitives/input-otp.tsx +16 -16
- package/src/ui/components/primitives/input.tsx +20 -10
- package/src/ui/components/primitives/item.tsx +9 -12
- package/src/ui/components/primitives/menu.tsx +44 -65
- package/src/ui/components/primitives/metric-card.tsx +75 -59
- package/src/ui/components/primitives/radio-group.tsx +9 -9
- package/src/ui/components/primitives/select.tsx +344 -209
- package/src/ui/components/primitives/switch.tsx +10 -10
- package/src/ui/components/primitives/table.tsx +29 -24
- package/src/ui/components/primitives/tabs.tsx +4 -2
- package/src/ui/components/primitives/textarea.tsx +6 -6
- package/src/ui/components/primitives/toggle-group-context.ts +24 -0
- package/src/ui/components/primitives/toggle-group.tsx +116 -74
- package/src/ui/components/primitives/toggle.tsx +82 -16
- package/src/ui/docs/DocBrowser.tsx +77 -69
- package/src/ui/docs/content/action-form-card.md +9 -9
- package/src/ui/docs/content/action-list.md +76 -70
- package/src/ui/docs/content/action-view.md +33 -18
- package/src/ui/docs/content/button.md +1 -1
- package/src/ui/docs/content/communication.md +1 -1
- package/src/ui/docs/content/compositions.md +479 -0
- package/src/ui/docs/content/customization.md +64 -11
- package/src/ui/docs/content/detail.md +8 -6
- package/src/ui/docs/content/dialog.md +105 -60
- package/src/ui/docs/content/drawer.md +68 -22
- package/src/ui/docs/content/field.md +14 -15
- package/src/ui/docs/content/icon-tile.md +14 -13
- package/src/ui/docs/content/page.md +44 -34
- package/src/ui/docs/content/pane.md +42 -290
- package/src/ui/docs/content/presentation.md +3 -3
- package/src/ui/docs/content/separator.md +6 -6
- package/src/ui/docs/content/sidebar.md +68 -266
- package/src/ui/docs/content/skeleton.md +20 -18
- package/src/ui/docs/content/split.md +14 -14
- package/src/ui/docs/content/surface.md +513 -0
- package/src/ui/docs/content/tabs.md +20 -20
- package/src/ui/docs/content/toggle.md +117 -65
- package/src/ui/docs/content/tokens.md +124 -40
- package/src/ui/docs/registry.tsx +10 -6
- package/src/ui/index.ts +30 -27
- package/src/ui/meta.ts +15 -27
- package/src/ui/react.tsx +40 -43
- package/src/ui/theme.css +311 -64
- package/src/ui/components/patterns/content-header.tsx +0 -528
- package/src/ui/components/patterns/surface-media.tsx +0 -69
- package/src/ui/components/primitives/card.tsx +0 -191
- package/src/ui/docs/content/card.md +0 -292
- package/src/ui/docs/content/content.md +0 -275
|
@@ -6,17 +6,27 @@ erro. O consumidor compõe somente o conteúdo disponível, aqui com [`Detail`](
|
|
|
6
6
|
coleção, use [`ActionList`](/ui/action-list).
|
|
7
7
|
|
|
8
8
|
```tsx preview col
|
|
9
|
-
const [id, setId] = useState(
|
|
9
|
+
const [id, setId] = useState("empresa-x");
|
|
10
10
|
|
|
11
11
|
render(
|
|
12
12
|
<DocBrowserActionProvider>
|
|
13
13
|
<div className="w-full space-y-3">
|
|
14
14
|
<ButtonGroup mode="spaced">
|
|
15
|
-
<Button variant="outline" size="sm" onClick={() => setId(
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
<Button variant="outline" size="sm" onClick={() => setId("empresa-x")}>
|
|
16
|
+
Empresa X
|
|
17
|
+
</Button>
|
|
18
|
+
<Button variant="outline" size="sm" onClick={() => setId("empresa-y")}>
|
|
19
|
+
Empresa Y
|
|
20
|
+
</Button>
|
|
21
|
+
<Button
|
|
22
|
+
variant="outline"
|
|
23
|
+
size="sm"
|
|
24
|
+
onClick={() => setId("sessao-fantasma")}
|
|
25
|
+
>
|
|
26
|
+
Inexistente (erro)
|
|
27
|
+
</Button>
|
|
18
28
|
</ButtonGroup>
|
|
19
|
-
<
|
|
29
|
+
<Surface framed elevation="raised" className="p-4">
|
|
20
30
|
<ActionView action={docWorkspaceView} input={{ id }}>
|
|
21
31
|
{(ws) => (
|
|
22
32
|
<DetailGroup columns={3}>
|
|
@@ -24,15 +34,20 @@ render(
|
|
|
24
34
|
<DetailField label="Cliente" value={ws.client} />
|
|
25
35
|
<DetailField
|
|
26
36
|
label="Status"
|
|
27
|
-
value={
|
|
37
|
+
value={
|
|
38
|
+
<DictionaryValue
|
|
39
|
+
dict={docWorkspaceStatus}
|
|
40
|
+
value={ws.status}
|
|
41
|
+
/>
|
|
42
|
+
}
|
|
28
43
|
/>
|
|
29
44
|
</DetailGroup>
|
|
30
45
|
)}
|
|
31
46
|
</ActionView>
|
|
32
|
-
</
|
|
47
|
+
</Surface>
|
|
33
48
|
</div>
|
|
34
49
|
</DocBrowserActionProvider>,
|
|
35
|
-
)
|
|
50
|
+
);
|
|
36
51
|
```
|
|
37
52
|
|
|
38
53
|
O handler da view deve devolver um valor. Uma consulta que resolve `undefined` é tratada como erro
|
|
@@ -47,13 +62,13 @@ mais de uma região da tela ou quando o carregamento precisa ser orquestrado por
|
|
|
47
62
|
|
|
48
63
|
## Propriedades de ActionView
|
|
49
64
|
|
|
50
|
-
| Propriedade
|
|
51
|
-
|
|
52
|
-
| `action`
|
|
53
|
-
| `input`
|
|
54
|
-
| `children`
|
|
55
|
-
| `render`
|
|
56
|
-
| `loading`
|
|
57
|
-
| `empty`
|
|
58
|
-
| `emptyMessage` | `string`
|
|
59
|
-
| `error`
|
|
65
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
66
|
+
| -------------- | ------------------------------------- | ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
67
|
+
| `action` | `ViewContract<TInput, TData>` | | A ViewAction do Opus (kind view, 1 recurso). |
|
|
68
|
+
| `input` | `TInput` | | Geralmente { id } — mudou, recarrega. |
|
|
69
|
+
| `children` | `(data: TData, refetch) => ReactNode` | | Obrigatório, salvo quando `render` é informado. Conteúdo apresentado quando os dados estão disponíveis. `refetch` permite recarregar por código. |
|
|
70
|
+
| `render` | `(data: TData, refetch) => ReactNode` | | Alias de compatibilidade de `children`; `children` tem precedência. Sem nenhum dos dois, o componente lança erro. |
|
|
71
|
+
| `loading` | `ReactNode \| boolean` | `3 skeletons` | Sobrescreve o carregamento: um nó próprio, `true` para o padrão ou `false` para não renderizar nada enquanto carrega. |
|
|
72
|
+
| `empty` | `ReactNode` | `emptyMessage` ou nada | Estado reservado a `data` indefinido. Como o react-query converte esse resultado em erro, ele não aparece na prática; devolva um valor ou o erro `not_found`. |
|
|
73
|
+
| `emptyMessage` | `string` | | Atalho do vazio, sujeito à mesma limitação de `empty`: a frase na mesma superfície de `DataState` (`Empty` com moldura sólida). |
|
|
74
|
+
| `error` | `(err, retry) => ReactNode` | `"Não foi possível carregar" + Tentar de novo` | Sobrescreve o estado de erro padrão. Sem ele, a frase do servidor só aparece quando é legível pela pessoa (`conflict`, `validation`, `not_found`, `authorization`, `authentication`); código técnico nunca vira título, e o botão de tentar de novo some quando o problema é de permissão ou sessão. |
|
|
@@ -43,7 +43,7 @@ há texto visível. O glifo dentro do controle acompanha o tamanho (0.875rem em
|
|
|
43
43
|
Escolha o tamanho pela região, não pela importância visual: `variant` e `context` resolvem a
|
|
44
44
|
hierarquia da ação. Headers de Page, a criação no cabeçalho de uma coleção e footers de
|
|
45
45
|
[`Dialog`](/ui/dialog) e [`Drawer`](/ui/drawer) usam `default`; a criação de uma coleção é um botão textual, sem
|
|
46
|
-
ícone, como mostra [`
|
|
46
|
+
ícone, como mostra [`Surface`](/ui/surface). Operações densas de toolbar ou de seção usam `sm`;
|
|
47
47
|
ações dentro de linha ou célula usam `xs` ou `icon-xs`. Assim a mesma decisão mantém a mesma altura
|
|
48
48
|
mesmo quando uma superfície troca uma ação secundária por uma primária.
|
|
49
49
|
|
|
@@ -42,7 +42,7 @@ frase. Não copie as regras da Base para cá.
|
|
|
42
42
|
| Superfície Opus | Papel enviado à Base |
|
|
43
43
|
| ------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
|
|
44
44
|
| `label`, botão de confirmação e filhos de `Button` | `button` |
|
|
45
|
-
| `title`, `
|
|
45
|
+
| `title`, `SurfaceTitle`, `DialogTitle` | `title` |
|
|
46
46
|
| `description`, `hint`, `help` | `description` ou `helper-text` |
|
|
47
47
|
| `messages.success` e `messages.error` | `success` e `error` |
|
|
48
48
|
| `confirm.message`, `dialog.*().body` e texto relevante no `DialogBody` | `dialog-body` |
|
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Composições
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Composições
|
|
6
|
+
|
|
7
|
+
Use esta página para decidir como montar uma tela antes de escolher as propriedades de cada
|
|
8
|
+
componente. `Page`, `Dialog`, `Drawer` e `Surface` se organizam da mesma forma: um cabeçalho, um
|
|
9
|
+
corpo e um rodapé com a decisão da etapa. Cada família admite as ações e a navegação indicadas na
|
|
10
|
+
tabela abaixo. Em `Page`, `Dialog` e `Drawer`, o corpo também pode começar por uma intro que leva o
|
|
11
|
+
título. `Pane` fica fora dessa organização: ele só reserva, dentro de um `Split`, o espaço em que
|
|
12
|
+
uma `Surface` aparece.
|
|
13
|
+
|
|
14
|
+
Todos os exemplos mostram a mesma tarefa, editar uma ordem de serviço, em superfícies diferentes.
|
|
15
|
+
As propriedades de cada componente ficam nas páginas da família: [`Page`](/ui/page),
|
|
16
|
+
[`Dialog`](/ui/dialog), [`Drawer`](/ui/drawer), [`Surface`](/ui/surface) e [`Pane`](/ui/pane).
|
|
17
|
+
|
|
18
|
+
## As regiões de cada superfície
|
|
19
|
+
|
|
20
|
+
Cada família dá um nome próprio a cada região. A tabela mostra a correspondência entre elas.
|
|
21
|
+
|
|
22
|
+
| Região | Page | Dialog | Drawer | Surface |
|
|
23
|
+
| -------------------- | ------------------------------------------- | ----------------------------- | ----------------------------- | ----------------------------- |
|
|
24
|
+
| Navegação | `PageNavigation` ou `PageBack` | `DialogNavigation` | `DrawerNavigation` | Não existe |
|
|
25
|
+
| Título no cabeçalho | `PageTitle` | `DialogTitle` | `DrawerTitle` | `SurfaceTitle` |
|
|
26
|
+
| Ações do cabeçalho | `PageActions` | `DialogActions` | `DrawerActions` | `SurfaceActions` |
|
|
27
|
+
| Intro | `Surface level={1}` no início do `PageBody` | `DialogIntro` | `DrawerIntro` | Não existe |
|
|
28
|
+
| Ações da intro | Ações da `Surface level={1}` | `DialogIntroActions` | `DrawerIntroActions` | Não existe |
|
|
29
|
+
| Corpo | `PageBody` | `DialogBody` | `DrawerBody` | `SurfaceBody` |
|
|
30
|
+
| Rodapé | `PageFooter` | `DialogFooter` | `DrawerFooter` | `SurfaceFooter` |
|
|
31
|
+
| Divisores | Antes do `PageFooter` | `sectioned` (padrão) | `sectioned` (padrão) | `sectioned` |
|
|
32
|
+
| Sem divisores | Cabeçalho e corpo | `continuous` | `continuous` | `continuous` (padrão) |
|
|
33
|
+
|
|
34
|
+
`layout` escolhe entre as duas anatomias em `DialogContent`, `DrawerContent` e `Surface`. Em
|
|
35
|
+
`sectioned`, uma linha separa as regiões vizinhas; em `continuous`, o espaço faz essa separação. Na
|
|
36
|
+
`Surface`, `sectioned` exige `framed` ou `elevation="raised"`, para que a linha tenha um limite. A
|
|
37
|
+
`Page` não oferece anatomia `sectioned`: cabeçalho e corpo são contínuos, enquanto o `PageFooter`
|
|
38
|
+
é uma faixa própria, separada por sua borda superior. O `layout` da página decide apenas a ocupação
|
|
39
|
+
de altura.
|
|
40
|
+
|
|
41
|
+
## Onde cada ação fica
|
|
42
|
+
|
|
43
|
+
A posição de uma ação informa a que ela se refere. Escolha o lugar pelo alcance da ação, não pelo
|
|
44
|
+
espaço livre na tela.
|
|
45
|
+
|
|
46
|
+
| Lugar | Use para |
|
|
47
|
+
| ---------- | ---------------------------------------------------------------------------------------------------------- |
|
|
48
|
+
| Navegação | Sair ou voltar na estrutura: a trilha da página, a etapa anterior de um fluxo modal ou recolher o painel. |
|
|
49
|
+
| Cabeçalho | Ações que valem para a superfície inteira, como abrir uma conversa sobre o registro. |
|
|
50
|
+
| Intro | Ações ligadas ao título e ao conteúdo, como ver o histórico do registro. |
|
|
51
|
+
| Rodapé | A decisão da etapa: **Cancelar** em `ghost` e a ação principal sólida, alinhadas à direita. |
|
|
52
|
+
|
|
53
|
+
Em `Dialog` e `Drawer`, a navegação abre a linha do cabeçalho e fica junto do título, a `0.5rem`
|
|
54
|
+
dele. As ações ficam no extremo oposto, antes do botão de fechar, e mantêm a distância maior da
|
|
55
|
+
família até o título. Na `Page`, a navegação fica acima do título; quando o título está na intro,
|
|
56
|
+
ela divide a linha com as ações da página.
|
|
57
|
+
|
|
58
|
+
Em `Dialog` e `Drawer`, cabeçalho e rodapé permanecem visíveis enquanto o corpo rola; a intro rola
|
|
59
|
+
junto com o conteúdo.
|
|
60
|
+
|
|
61
|
+
## Título no cabeçalho ou na intro
|
|
62
|
+
|
|
63
|
+
O título pode ficar em dois lugares, e essa escolha define a composição:
|
|
64
|
+
|
|
65
|
+
- **Com intro:** o cabeçalho guarda só a navegação, as ações da superfície e o fechamento. O título
|
|
66
|
+
abre o corpo junto das ações do conteúdo. Use quando esses controles da superfície precisam
|
|
67
|
+
continuar estáveis e separados do registro aberto.
|
|
68
|
+
- **Compacta:** título, ações do conteúdo e ações da superfície dividem a linha do cabeçalho, e a
|
|
69
|
+
intro é omitida. Use quando uma linha basta para nomear a tarefa e agir sobre ela.
|
|
70
|
+
|
|
71
|
+
Na composição compacta, coloque as ações do conteúdo antes das ações da superfície, no mesmo slot de
|
|
72
|
+
ações. Em `Dialog` e `Drawer`, o título no cabeçalho trunca quando não cabe na linha; na intro, ele
|
|
73
|
+
usa uma escala maior e quebra em mais de uma linha.
|
|
74
|
+
|
|
75
|
+
## Dialog
|
|
76
|
+
|
|
77
|
+
Abra cada combinação para comparar. As linhas trocam o `layout` de `DialogContent`; as colunas
|
|
78
|
+
trocam o lugar do título. O campo e o rodapé são os mesmos nos quatro casos.
|
|
79
|
+
|
|
80
|
+
```tsx preview
|
|
81
|
+
function OrderField() {
|
|
82
|
+
const id = useId();
|
|
83
|
+
return (
|
|
84
|
+
<div className="space-y-2">
|
|
85
|
+
<Label htmlFor={id}>Responsável</Label>
|
|
86
|
+
<Input id={id} defaultValue="Marina Alves" />
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function DialogWithIntro({ layout, label }) {
|
|
92
|
+
return (
|
|
93
|
+
<Dialog>
|
|
94
|
+
<DialogTrigger asChild>
|
|
95
|
+
<Button variant="outline">{label}</Button>
|
|
96
|
+
</DialogTrigger>
|
|
97
|
+
<DialogContent layout={layout}>
|
|
98
|
+
<DialogHeader>
|
|
99
|
+
<DialogActions>
|
|
100
|
+
<Button variant="ghost" size="icon" aria-label="Abrir conversa">
|
|
101
|
+
<MessageCircle />
|
|
102
|
+
</Button>
|
|
103
|
+
</DialogActions>
|
|
104
|
+
</DialogHeader>
|
|
105
|
+
<DialogBody>
|
|
106
|
+
<DialogIntro>
|
|
107
|
+
<DialogTitle>Editar ordem de serviço</DialogTitle>
|
|
108
|
+
<DialogIntroActions>
|
|
109
|
+
<Button variant="outline" size="sm">
|
|
110
|
+
Ver histórico
|
|
111
|
+
</Button>
|
|
112
|
+
</DialogIntroActions>
|
|
113
|
+
</DialogIntro>
|
|
114
|
+
<OrderField />
|
|
115
|
+
</DialogBody>
|
|
116
|
+
<DialogFooter>
|
|
117
|
+
<ButtonGroup mode="spaced">
|
|
118
|
+
<DialogClose asChild>
|
|
119
|
+
<Button variant="ghost">Cancelar</Button>
|
|
120
|
+
</DialogClose>
|
|
121
|
+
<Button>Salvar</Button>
|
|
122
|
+
</ButtonGroup>
|
|
123
|
+
</DialogFooter>
|
|
124
|
+
</DialogContent>
|
|
125
|
+
</Dialog>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function CompactDialog({ layout, label }) {
|
|
130
|
+
return (
|
|
131
|
+
<Dialog>
|
|
132
|
+
<DialogTrigger asChild>
|
|
133
|
+
<Button variant="outline">{label}</Button>
|
|
134
|
+
</DialogTrigger>
|
|
135
|
+
<DialogContent layout={layout}>
|
|
136
|
+
<DialogHeader>
|
|
137
|
+
<DialogMedia>
|
|
138
|
+
<Wrench />
|
|
139
|
+
</DialogMedia>
|
|
140
|
+
<DialogTitle>Editar ordem de serviço</DialogTitle>
|
|
141
|
+
<DialogActions>
|
|
142
|
+
<Button variant="outline" size="sm">
|
|
143
|
+
Ver histórico
|
|
144
|
+
</Button>
|
|
145
|
+
<Button variant="ghost" size="icon" aria-label="Abrir conversa">
|
|
146
|
+
<MessageCircle />
|
|
147
|
+
</Button>
|
|
148
|
+
</DialogActions>
|
|
149
|
+
</DialogHeader>
|
|
150
|
+
<DialogBody>
|
|
151
|
+
<OrderField />
|
|
152
|
+
</DialogBody>
|
|
153
|
+
<DialogFooter>
|
|
154
|
+
<ButtonGroup mode="spaced">
|
|
155
|
+
<DialogClose asChild>
|
|
156
|
+
<Button variant="ghost">Cancelar</Button>
|
|
157
|
+
</DialogClose>
|
|
158
|
+
<Button>Salvar</Button>
|
|
159
|
+
</ButtonGroup>
|
|
160
|
+
</DialogFooter>
|
|
161
|
+
</DialogContent>
|
|
162
|
+
</Dialog>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
render(
|
|
167
|
+
<div className="grid grid-cols-2 gap-3">
|
|
168
|
+
<DialogWithIntro layout="sectioned" label="Seccionado com intro" />
|
|
169
|
+
<CompactDialog layout="sectioned" label="Seccionado compacto" />
|
|
170
|
+
<DialogWithIntro layout="continuous" label="Contínuo com intro" />
|
|
171
|
+
<CompactDialog layout="continuous" label="Contínuo compacto" />
|
|
172
|
+
</div>,
|
|
173
|
+
);
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Prefira `continuous` para uma tarefa curta, em que a linha entre as regiões seria ruído. O `Dialog`
|
|
177
|
+
não recebe navegação neste exemplo: use `DialogNavigation` quando o modal trocar de etapa e a pessoa
|
|
178
|
+
precisar voltar à anterior.
|
|
179
|
+
|
|
180
|
+
## Drawer
|
|
181
|
+
|
|
182
|
+
O `Drawer` segue a mesma grade. A navegação aparece aqui como o controle de recolher o painel: um
|
|
183
|
+
`DrawerClose` dentro de `DrawerNavigation`, com `showCloseButton={false}` para que o painel não tenha
|
|
184
|
+
dois controles de fechamento.
|
|
185
|
+
|
|
186
|
+
```tsx preview
|
|
187
|
+
function OrderField() {
|
|
188
|
+
const id = useId();
|
|
189
|
+
return (
|
|
190
|
+
<div className="space-y-2">
|
|
191
|
+
<Label htmlFor={id}>Responsável</Label>
|
|
192
|
+
<Input id={id} defaultValue="Marina Alves" />
|
|
193
|
+
</div>
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function CollapseButton() {
|
|
198
|
+
return (
|
|
199
|
+
<DrawerClose asChild>
|
|
200
|
+
<Button variant="ghost" size="icon" aria-label="Recolher painel">
|
|
201
|
+
<ChevronsRight />
|
|
202
|
+
</Button>
|
|
203
|
+
</DrawerClose>
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function DrawerWithIntro({ layout, label }) {
|
|
208
|
+
return (
|
|
209
|
+
<Drawer>
|
|
210
|
+
<DrawerTrigger asChild>
|
|
211
|
+
<Button variant="outline">{label}</Button>
|
|
212
|
+
</DrawerTrigger>
|
|
213
|
+
<DrawerContent layout={layout} showCloseButton={false}>
|
|
214
|
+
<DrawerHeader>
|
|
215
|
+
<DrawerNavigation>
|
|
216
|
+
<CollapseButton />
|
|
217
|
+
</DrawerNavigation>
|
|
218
|
+
<DrawerActions>
|
|
219
|
+
<Button variant="ghost" size="icon" aria-label="Abrir conversa">
|
|
220
|
+
<MessageCircle />
|
|
221
|
+
</Button>
|
|
222
|
+
</DrawerActions>
|
|
223
|
+
</DrawerHeader>
|
|
224
|
+
<DrawerBody>
|
|
225
|
+
<DrawerIntro>
|
|
226
|
+
<DrawerTitle>Editar ordem de serviço</DrawerTitle>
|
|
227
|
+
<DrawerIntroActions>
|
|
228
|
+
<Button variant="outline" size="sm">
|
|
229
|
+
Ver histórico
|
|
230
|
+
</Button>
|
|
231
|
+
</DrawerIntroActions>
|
|
232
|
+
</DrawerIntro>
|
|
233
|
+
<OrderField />
|
|
234
|
+
</DrawerBody>
|
|
235
|
+
<DrawerFooter>
|
|
236
|
+
<ButtonGroup mode="spaced">
|
|
237
|
+
<DrawerClose asChild>
|
|
238
|
+
<Button variant="ghost">Cancelar</Button>
|
|
239
|
+
</DrawerClose>
|
|
240
|
+
<Button>Salvar</Button>
|
|
241
|
+
</ButtonGroup>
|
|
242
|
+
</DrawerFooter>
|
|
243
|
+
</DrawerContent>
|
|
244
|
+
</Drawer>
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function CompactDrawer({ layout, label }) {
|
|
249
|
+
return (
|
|
250
|
+
<Drawer>
|
|
251
|
+
<DrawerTrigger asChild>
|
|
252
|
+
<Button variant="outline">{label}</Button>
|
|
253
|
+
</DrawerTrigger>
|
|
254
|
+
<DrawerContent layout={layout} showCloseButton={false}>
|
|
255
|
+
<DrawerHeader>
|
|
256
|
+
<DrawerNavigation>
|
|
257
|
+
<CollapseButton />
|
|
258
|
+
</DrawerNavigation>
|
|
259
|
+
<DrawerTitle>Editar ordem de serviço</DrawerTitle>
|
|
260
|
+
<DrawerActions>
|
|
261
|
+
<Button variant="outline" size="sm">
|
|
262
|
+
Ver histórico
|
|
263
|
+
</Button>
|
|
264
|
+
<Button variant="ghost" size="icon" aria-label="Abrir conversa">
|
|
265
|
+
<MessageCircle />
|
|
266
|
+
</Button>
|
|
267
|
+
</DrawerActions>
|
|
268
|
+
</DrawerHeader>
|
|
269
|
+
<DrawerBody>
|
|
270
|
+
<OrderField />
|
|
271
|
+
</DrawerBody>
|
|
272
|
+
<DrawerFooter>
|
|
273
|
+
<ButtonGroup mode="spaced">
|
|
274
|
+
<DrawerClose asChild>
|
|
275
|
+
<Button variant="ghost">Cancelar</Button>
|
|
276
|
+
</DrawerClose>
|
|
277
|
+
<Button>Salvar</Button>
|
|
278
|
+
</ButtonGroup>
|
|
279
|
+
</DrawerFooter>
|
|
280
|
+
</DrawerContent>
|
|
281
|
+
</Drawer>
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
render(
|
|
286
|
+
<div className="grid grid-cols-2 gap-3">
|
|
287
|
+
<DrawerWithIntro layout="sectioned" label="Seccionado com intro" />
|
|
288
|
+
<CompactDrawer layout="sectioned" label="Seccionado compacto" />
|
|
289
|
+
<DrawerWithIntro layout="continuous" label="Contínuo com intro" />
|
|
290
|
+
<CompactDrawer layout="continuous" label="Contínuo compacto" />
|
|
291
|
+
</div>,
|
|
292
|
+
);
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Mantenha `DrawerNavigation`, `DrawerTitle` e `DrawerActions` como filhos diretos de
|
|
296
|
+
`DrawerHeader`: o cabeçalho reconhece os slots pelo componente e interrompe a renderização diante de
|
|
297
|
+
qualquer outro nó, inclusive um componente local que devolva um slot. Por isso, o exemplo extrai
|
|
298
|
+
apenas o botão de recolher. Sem o botão de fechar, Esc e o clique no overlay continuam fechando o
|
|
299
|
+
painel.
|
|
300
|
+
|
|
301
|
+
## Page
|
|
302
|
+
|
|
303
|
+
A `Page` não tem um slot de intro. Com intro, o `PageHeader` guarda a navegação e as ações da
|
|
304
|
+
página, e uma `Surface level={1}` abre o `PageBody` com o título e as ações do conteúdo. A página
|
|
305
|
+
precisa de exatamente um heading principal, por isso o `PageHeader` dessa composição não leva
|
|
306
|
+
`PageTitle`.
|
|
307
|
+
|
|
308
|
+
```tsx preview col
|
|
309
|
+
render(
|
|
310
|
+
<div className="w-full overflow-hidden rounded-lg border border-border">
|
|
311
|
+
<Page>
|
|
312
|
+
<PageHeader>
|
|
313
|
+
<PageNavigation>
|
|
314
|
+
<Breadcrumb>
|
|
315
|
+
<BreadcrumbList>
|
|
316
|
+
<BreadcrumbItem>Oficina</BreadcrumbItem>
|
|
317
|
+
<BreadcrumbSeparator />
|
|
318
|
+
<BreadcrumbItem>Ordens de serviço</BreadcrumbItem>
|
|
319
|
+
<BreadcrumbSeparator />
|
|
320
|
+
<BreadcrumbPage>OS-1842</BreadcrumbPage>
|
|
321
|
+
</BreadcrumbList>
|
|
322
|
+
</Breadcrumb>
|
|
323
|
+
</PageNavigation>
|
|
324
|
+
<PageActions>
|
|
325
|
+
<Button variant="ghost" size="icon" aria-label="Abrir conversa">
|
|
326
|
+
<MessageCircle />
|
|
327
|
+
</Button>
|
|
328
|
+
</PageActions>
|
|
329
|
+
</PageHeader>
|
|
330
|
+
<PageBody>
|
|
331
|
+
<Surface
|
|
332
|
+
as="section"
|
|
333
|
+
level={1}
|
|
334
|
+
title="Editar ordem de serviço"
|
|
335
|
+
actions={
|
|
336
|
+
<Button variant="outline" size="sm">
|
|
337
|
+
Ver histórico
|
|
338
|
+
</Button>
|
|
339
|
+
}
|
|
340
|
+
>
|
|
341
|
+
<div className="space-y-2">
|
|
342
|
+
<Label htmlFor="page-intro-owner">Responsável</Label>
|
|
343
|
+
<Input id="page-intro-owner" defaultValue="Marina Alves" />
|
|
344
|
+
</div>
|
|
345
|
+
</Surface>
|
|
346
|
+
</PageBody>
|
|
347
|
+
<PageFooter>
|
|
348
|
+
<ButtonGroup mode="spaced">
|
|
349
|
+
<Button variant="ghost">Cancelar</Button>
|
|
350
|
+
<Button>Salvar</Button>
|
|
351
|
+
</ButtonGroup>
|
|
352
|
+
</PageFooter>
|
|
353
|
+
</Page>
|
|
354
|
+
</div>,
|
|
355
|
+
);
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Na composição compacta, `PageTitle` entra no `PageHeader` e as ações do conteúdo passam para
|
|
359
|
+
`PageActions`, antes das ações da página. A forma curta, `<Page title actions>`, produz esse mesmo
|
|
360
|
+
cabeçalho; nela, a navegação vem do [`PageShell`](/ui/page).
|
|
361
|
+
|
|
362
|
+
```tsx preview col
|
|
363
|
+
render(
|
|
364
|
+
<div className="w-full overflow-hidden rounded-lg border border-border">
|
|
365
|
+
<Page>
|
|
366
|
+
<PageHeader>
|
|
367
|
+
<PageNavigation>
|
|
368
|
+
<Breadcrumb>
|
|
369
|
+
<BreadcrumbList>
|
|
370
|
+
<BreadcrumbItem>Oficina</BreadcrumbItem>
|
|
371
|
+
<BreadcrumbSeparator />
|
|
372
|
+
<BreadcrumbItem>Ordens de serviço</BreadcrumbItem>
|
|
373
|
+
<BreadcrumbSeparator />
|
|
374
|
+
<BreadcrumbPage>OS-1842</BreadcrumbPage>
|
|
375
|
+
</BreadcrumbList>
|
|
376
|
+
</Breadcrumb>
|
|
377
|
+
</PageNavigation>
|
|
378
|
+
<PageTitle>Editar ordem de serviço</PageTitle>
|
|
379
|
+
<PageActions>
|
|
380
|
+
<Button variant="outline" size="sm">
|
|
381
|
+
Ver histórico
|
|
382
|
+
</Button>
|
|
383
|
+
<Button variant="ghost" size="icon" aria-label="Abrir conversa">
|
|
384
|
+
<MessageCircle />
|
|
385
|
+
</Button>
|
|
386
|
+
</PageActions>
|
|
387
|
+
</PageHeader>
|
|
388
|
+
<PageBody>
|
|
389
|
+
<div className="space-y-2">
|
|
390
|
+
<Label htmlFor="page-compact-owner">Responsável</Label>
|
|
391
|
+
<Input id="page-compact-owner" defaultValue="Marina Alves" />
|
|
392
|
+
</div>
|
|
393
|
+
</PageBody>
|
|
394
|
+
<PageFooter>
|
|
395
|
+
<ButtonGroup mode="spaced">
|
|
396
|
+
<Button variant="ghost">Cancelar</Button>
|
|
397
|
+
<Button>Salvar</Button>
|
|
398
|
+
</ButtonGroup>
|
|
399
|
+
</PageFooter>
|
|
400
|
+
</Page>
|
|
401
|
+
</div>,
|
|
402
|
+
);
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
Use `PageFooter` quando a página for uma etapa com decisão própria, como esta edição.
|
|
406
|
+
|
|
407
|
+
## Pane com Surface
|
|
408
|
+
|
|
409
|
+
`Pane` declara uma coluna de um [`Split`](/ui/split) e não desenha nada: não tem cabeçalho, corpo,
|
|
410
|
+
rodapé, recuo, borda nem fundo. Quando a coluna precisa dessas regiões, componha uma `Surface`
|
|
411
|
+
dentro dela. A `Surface` não tem intro, então o título fica no `SurfaceHeader`, como na composição
|
|
412
|
+
compacta.
|
|
413
|
+
|
|
414
|
+
```tsx preview col
|
|
415
|
+
render(
|
|
416
|
+
<div className="h-80 w-full overflow-hidden rounded-lg border border-border">
|
|
417
|
+
<Split>
|
|
418
|
+
<Pane initialSize="14rem">
|
|
419
|
+
<Sidebar className="w-full">
|
|
420
|
+
<SidebarNav
|
|
421
|
+
navLabel="Ordens de serviço"
|
|
422
|
+
activeId="os-1842"
|
|
423
|
+
groups={[
|
|
424
|
+
{
|
|
425
|
+
items: [
|
|
426
|
+
{ id: "os-1842", label: "OS-1842" },
|
|
427
|
+
{ id: "os-1839", label: "OS-1839" },
|
|
428
|
+
{ id: "os-1835", label: "OS-1835" },
|
|
429
|
+
],
|
|
430
|
+
},
|
|
431
|
+
]}
|
|
432
|
+
/>
|
|
433
|
+
</Sidebar>
|
|
434
|
+
</Pane>
|
|
435
|
+
<Pane grow>
|
|
436
|
+
<Surface
|
|
437
|
+
as="section"
|
|
438
|
+
framed
|
|
439
|
+
elevation="raised"
|
|
440
|
+
layout="sectioned"
|
|
441
|
+
className="flex h-full flex-col"
|
|
442
|
+
>
|
|
443
|
+
<SurfaceHeader>
|
|
444
|
+
<SurfaceMedia>
|
|
445
|
+
<Wrench />
|
|
446
|
+
</SurfaceMedia>
|
|
447
|
+
<SurfaceTitle>Editar ordem de serviço</SurfaceTitle>
|
|
448
|
+
<SurfaceActions>
|
|
449
|
+
<Button variant="outline" size="sm">
|
|
450
|
+
Ver histórico
|
|
451
|
+
</Button>
|
|
452
|
+
<Button variant="ghost" size="icon" aria-label="Abrir conversa">
|
|
453
|
+
<MessageCircle />
|
|
454
|
+
</Button>
|
|
455
|
+
</SurfaceActions>
|
|
456
|
+
</SurfaceHeader>
|
|
457
|
+
<SurfaceBody className="min-h-0 flex-1 overflow-y-auto">
|
|
458
|
+
<div className="space-y-2">
|
|
459
|
+
<Label htmlFor="pane-owner">Responsável</Label>
|
|
460
|
+
<Input id="pane-owner" defaultValue="Marina Alves" />
|
|
461
|
+
</div>
|
|
462
|
+
</SurfaceBody>
|
|
463
|
+
<SurfaceFooter>
|
|
464
|
+
<ButtonGroup mode="spaced">
|
|
465
|
+
<Button variant="ghost">Cancelar</Button>
|
|
466
|
+
<Button>Salvar</Button>
|
|
467
|
+
</ButtonGroup>
|
|
468
|
+
</SurfaceFooter>
|
|
469
|
+
</Surface>
|
|
470
|
+
</Pane>
|
|
471
|
+
</Split>
|
|
472
|
+
</div>,
|
|
473
|
+
);
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
Os dois panes recebem conteúdos diferentes sem mudar de componente: o primeiro hospeda uma
|
|
477
|
+
[`Sidebar`](/ui/sidebar), que já tem anatomia própria, e o segundo, uma `Surface` completa. A
|
|
478
|
+
rolagem também pertence ao conteúdo: a `Surface` ocupa a altura do pane com `flex h-full flex-col`,
|
|
479
|
+
e o `SurfaceBody` declara `min-h-0 flex-1 overflow-y-auto` para rolar entre o cabeçalho e o rodapé.
|