@softize/opus 12.5.5 → 12.6.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 +26 -0
- package/package.json +2 -2
- package/src/ui/components/patterns/content-header.tsx +88 -0
- package/src/ui/components/patterns/data-state.tsx +32 -4
- package/src/ui/components/patterns/form.tsx +62 -46
- package/src/ui/components/patterns/list.tsx +240 -236
- package/src/ui/components/patterns/page.tsx +18 -19
- package/src/ui/components/patterns/trigger.tsx +5 -4
- package/src/ui/components/primitives/alert-dialog.tsx +1 -1
- package/src/ui/components/primitives/dialog.tsx +3 -3
- package/src/ui/components/primitives/empty.tsx +1 -1
- package/src/ui/components/primitives/field.tsx +5 -5
- package/src/ui/components/primitives/icon-picker.tsx +6 -0
- package/src/ui/components/primitives/item.tsx +46 -8
- package/src/ui/components/primitives/select.tsx +7 -0
- package/src/ui/docs/content/action-form-card.md +1 -1
- package/src/ui/docs/content/action-form-dialog.md +2 -0
- package/src/ui/docs/content/action-list.md +20 -1
- package/src/ui/docs/content/alert-dialog.md +1 -1
- package/src/ui/docs/content/button.md +4 -4
- package/src/ui/docs/content/customization.md +1 -1
- package/src/ui/docs/content/data-state.md +6 -3
- package/src/ui/docs/content/dialog.md +2 -2
- package/src/ui/docs/content/drawer.md +2 -2
- package/src/ui/docs/content/empty.md +10 -0
- package/src/ui/docs/content/item.md +4 -3
- package/src/ui/docs/content/page.md +4 -4
- package/src/ui/docs/content/popover.md +1 -1
- package/src/ui/meta.ts +10 -4
- package/src/ui/react.tsx +10 -2
|
@@ -51,10 +51,10 @@ export function DialogContent({
|
|
|
51
51
|
<DialogPrimitive.Content
|
|
52
52
|
data-slot="dialog-content"
|
|
53
53
|
className={cn(
|
|
54
|
-
// Superfície PURA: só a casca (
|
|
54
|
+
// Superfície PURA: só a casca (bg/raio/sombra) + coluna flex com teto de
|
|
55
55
|
// altura. O padding mora nos SLOTS (Header/Body/Footer), não aqui — assim ninguém
|
|
56
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-
|
|
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-clip rounded-xl bg-card text-card-foreground shadow-lg 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
58
|
className,
|
|
59
59
|
)}
|
|
60
60
|
{...props}
|
|
@@ -110,7 +110,7 @@ export function DialogFooter({ className, ...props }: React.ComponentProps<'div'
|
|
|
110
110
|
data-slot="dialog-footer"
|
|
111
111
|
// Faixa destacada (divergência da casa, ao estilo do site do shadcn): borda em cima
|
|
112
112
|
// + fundo muted. Sangra até as bordas sozinha — o content não tem padding, então não
|
|
113
|
-
// há -mx pra compensar; o
|
|
113
|
+
// há -mx pra compensar; o recorte do content arredonda o canto de baixo.
|
|
114
114
|
className={cn(
|
|
115
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
116
|
className,
|
|
@@ -7,7 +7,7 @@ function Empty({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
7
7
|
<div
|
|
8
8
|
data-slot="empty"
|
|
9
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",
|
|
10
|
+
"flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border border-dashed border-border p-6 text-center text-balance md:p-12",
|
|
11
11
|
className
|
|
12
12
|
)}
|
|
13
13
|
{...props}
|
|
@@ -44,7 +44,7 @@ function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
44
44
|
<div
|
|
45
45
|
data-slot="field-group"
|
|
46
46
|
className={cn(
|
|
47
|
-
"group/field-group @container/field-group flex w-full flex-col gap-
|
|
47
|
+
"group/field-group @container/field-group flex w-full flex-col gap-5 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4",
|
|
48
48
|
className
|
|
49
49
|
)}
|
|
50
50
|
{...props}
|
|
@@ -53,18 +53,18 @@ function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
const fieldVariants = cva(
|
|
56
|
-
"group/field flex w-full
|
|
56
|
+
"group/field flex w-full data-[invalid=true]:text-destructive",
|
|
57
57
|
{
|
|
58
58
|
variants: {
|
|
59
59
|
orientation: {
|
|
60
|
-
vertical: ["flex-col [&>*]:w-full [&>.sr-only]:w-auto"],
|
|
60
|
+
vertical: ["flex-col gap-2 [&>*]:w-full [&>.sr-only]:w-auto"],
|
|
61
61
|
horizontal: [
|
|
62
|
-
"flex-row items-center",
|
|
62
|
+
"flex-row items-center gap-3",
|
|
63
63
|
"[&>[data-slot=field-label]]:flex-auto",
|
|
64
64
|
"has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
|
|
65
65
|
],
|
|
66
66
|
responsive: [
|
|
67
|
-
"flex-col @md/field-group:flex-row @md/field-group:items-center [&>*]:w-full @md/field-group:[&>*]:w-auto [&>.sr-only]:w-auto",
|
|
67
|
+
"flex-col gap-2 @md/field-group:flex-row @md/field-group:items-center @md/field-group:gap-3 [&>*]:w-full @md/field-group:[&>*]:w-auto [&>.sr-only]:w-auto",
|
|
68
68
|
"@md/field-group:[&>[data-slot=field-label]]:flex-auto",
|
|
69
69
|
"@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
|
|
70
70
|
],
|
|
@@ -106,6 +106,8 @@ export interface IconPickerProps {
|
|
|
106
106
|
disabled?: boolean
|
|
107
107
|
className?: string
|
|
108
108
|
id?: string
|
|
109
|
+
'aria-invalid'?: boolean
|
|
110
|
+
'aria-describedby'?: string
|
|
109
111
|
}
|
|
110
112
|
|
|
111
113
|
/**
|
|
@@ -122,6 +124,8 @@ export function IconPicker({
|
|
|
122
124
|
disabled,
|
|
123
125
|
className,
|
|
124
126
|
id,
|
|
127
|
+
'aria-invalid': ariaInvalid,
|
|
128
|
+
'aria-describedby': ariaDescribedBy,
|
|
125
129
|
}: IconPickerProps): React.ReactElement {
|
|
126
130
|
const [open, setOpen] = React.useState(false)
|
|
127
131
|
const Current = value !== '' ? icons[value] : undefined
|
|
@@ -133,6 +137,8 @@ export function IconPicker({
|
|
|
133
137
|
type="button"
|
|
134
138
|
id={id}
|
|
135
139
|
disabled={disabled}
|
|
140
|
+
aria-invalid={ariaInvalid}
|
|
141
|
+
aria-describedby={ariaDescribedBy}
|
|
136
142
|
data-slot="icon-picker"
|
|
137
143
|
className={cn(
|
|
138
144
|
'flex h-10 w-full items-center gap-2 rounded-md border border-input bg-transparent px-3 text-sm outline-none transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-input/30',
|
|
@@ -5,14 +5,36 @@ import { Slot } from "radix-ui"
|
|
|
5
5
|
import { cn } from '../../lib/cn.ts'
|
|
6
6
|
import { Separator } from './separator.tsx'
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
const itemGroupVariants = cva("group/item-group @container/item-group flex flex-col", {
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
plain: "",
|
|
12
|
+
framed:
|
|
13
|
+
"overflow-hidden rounded-xl border border-border bg-card text-card-foreground [&>[data-slot=item]]:rounded-none [&>[data-slot=item-container]>[data-slot=item]]:rounded-none",
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
defaultVariants: {
|
|
17
|
+
variant: "plain",
|
|
18
|
+
},
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const ItemGroupContext = React.createContext(false)
|
|
22
|
+
|
|
23
|
+
function ItemGroup({
|
|
24
|
+
className,
|
|
25
|
+
variant = "plain",
|
|
26
|
+
...props
|
|
27
|
+
}: React.ComponentProps<"div"> & VariantProps<typeof itemGroupVariants>) {
|
|
9
28
|
return (
|
|
10
|
-
<
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
29
|
+
<ItemGroupContext.Provider value>
|
|
30
|
+
<div
|
|
31
|
+
role="list"
|
|
32
|
+
data-slot="item-group"
|
|
33
|
+
data-variant={variant}
|
|
34
|
+
className={cn(itemGroupVariants({ variant, className }))}
|
|
35
|
+
{...props}
|
|
36
|
+
/>
|
|
37
|
+
</ItemGroupContext.Provider>
|
|
16
38
|
)
|
|
17
39
|
}
|
|
18
40
|
|
|
@@ -56,12 +78,16 @@ function Item({
|
|
|
56
78
|
variant = "default",
|
|
57
79
|
size = "default",
|
|
58
80
|
asChild = false,
|
|
81
|
+
role,
|
|
59
82
|
...props
|
|
60
83
|
}: React.ComponentProps<"div"> &
|
|
61
84
|
VariantProps<typeof itemVariants> & { asChild?: boolean }) {
|
|
85
|
+
const grouped = React.useContext(ItemGroupContext)
|
|
62
86
|
const Comp = asChild ? Slot.Root : "div"
|
|
63
|
-
|
|
87
|
+
|
|
88
|
+
const content = (
|
|
64
89
|
<Comp
|
|
90
|
+
role={role ?? (!asChild && grouped ? "listitem" : undefined)}
|
|
65
91
|
data-slot="item"
|
|
66
92
|
data-variant={variant}
|
|
67
93
|
data-size={size}
|
|
@@ -69,6 +95,18 @@ function Item({
|
|
|
69
95
|
{...props}
|
|
70
96
|
/>
|
|
71
97
|
)
|
|
98
|
+
|
|
99
|
+
if (asChild && grouped) {
|
|
100
|
+
return (
|
|
101
|
+
<div role="listitem" data-slot="item-container">
|
|
102
|
+
{content}
|
|
103
|
+
</div>
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
content
|
|
109
|
+
)
|
|
72
110
|
}
|
|
73
111
|
|
|
74
112
|
const itemMediaVariants = cva(
|
|
@@ -57,6 +57,8 @@ interface SelectBaseProps {
|
|
|
57
57
|
id?: string
|
|
58
58
|
/** Ícone LEADING dentro do campo — identifica o filtro sem jogar o ícone ao lado. */
|
|
59
59
|
icon?: React.ReactNode
|
|
60
|
+
'aria-invalid'?: boolean
|
|
61
|
+
'aria-describedby'?: string
|
|
60
62
|
/** Altura: `default` (h-9, a do Input/Button) ou `sm` (h-8) pra toolbar densa. */
|
|
61
63
|
size?: 'sm' | 'default'
|
|
62
64
|
/** Geometria do controle; `pill` preserva a variante visual escolhida. */
|
|
@@ -155,6 +157,8 @@ function NativeSelect({
|
|
|
155
157
|
disabled={disabled}
|
|
156
158
|
value={value}
|
|
157
159
|
aria-label={rest['aria-label']}
|
|
160
|
+
aria-invalid={rest['aria-invalid']}
|
|
161
|
+
aria-describedby={rest['aria-describedby']}
|
|
158
162
|
onChange={(e) => onChange(e.target.value)}
|
|
159
163
|
className={cn(
|
|
160
164
|
"h-9 w-full min-w-0 appearance-none rounded-md border border-input bg-transparent px-3 py-2 pr-9 text-sm transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground disabled:pointer-events-none disabled:cursor-not-allowed data-[size=sm]:h-8 data-[size=sm]:py-1 data-[shape=pill]:rounded-full dark:bg-input/30 dark:hover:bg-input/50",
|
|
@@ -345,6 +349,7 @@ function CustomSelect(props: Exclude<SelectProps, SelectNativeProps>): React.Rea
|
|
|
345
349
|
data-size={size}
|
|
346
350
|
data-shape={shape}
|
|
347
351
|
data-variant={variant}
|
|
352
|
+
aria-invalid={props['aria-invalid']}
|
|
348
353
|
onClick={() => {
|
|
349
354
|
if (disabled !== true) {
|
|
350
355
|
setOpen(true)
|
|
@@ -417,6 +422,8 @@ function CustomSelect(props: Exclude<SelectProps, SelectNativeProps>): React.Rea
|
|
|
417
422
|
role="combobox"
|
|
418
423
|
aria-expanded={open}
|
|
419
424
|
aria-label={props['aria-label']}
|
|
425
|
+
aria-invalid={props['aria-invalid']}
|
|
426
|
+
aria-describedby={props['aria-describedby']}
|
|
420
427
|
value={props.multiple ? '' : triggerText}
|
|
421
428
|
onChange={() => {}}
|
|
422
429
|
onFocus={() => {
|
|
@@ -9,7 +9,7 @@ faixa de modal). Pra estruturar uma seção da página como painel. O título é
|
|
|
9
9
|
title="Editar workspace"
|
|
10
10
|
action={docWorkspaceCreate}
|
|
11
11
|
defaultValues={{ name: 'Empresa X', status: 'active' }}
|
|
12
|
-
submitLabel="Salvar"
|
|
12
|
+
submitLabel="Salvar alterações"
|
|
13
13
|
/>
|
|
14
14
|
</DocBrowserActionProvider>
|
|
15
15
|
```
|
|
@@ -16,6 +16,7 @@ render(
|
|
|
16
16
|
title="Novo workspace"
|
|
17
17
|
description="O workspace agrupa os repositórios e agentes do cliente."
|
|
18
18
|
action={docWorkspaceCreate}
|
|
19
|
+
submitLabel="Criar workspace"
|
|
19
20
|
/>
|
|
20
21
|
</DocBrowserActionProvider>,
|
|
21
22
|
)
|
|
@@ -27,4 +28,5 @@ render(
|
|
|
27
28
|
|---|---|---|---|
|
|
28
29
|
| `open / onOpenChange` | `boolean / (open: boolean) => void` | | Controle do modal — o pattern chama onOpenChange(false) no sucesso e no Cancelar. |
|
|
29
30
|
| `title / description` | `string` | | O DialogHeader fixo — description é opcional. |
|
|
31
|
+
| `submitLabel` | `string` | `'Salvar'` | Resultado da ação principal. Em criação, informe `Criar {recurso}`; em edição, `Salvar alterações`. |
|
|
30
32
|
| `…ActionFormProps` | `action, defaultValues, onSuccess, fieldOptions…` | | Todo o resto desce pro ActionForm interno — mesma API da página dele. |
|
|
@@ -12,6 +12,25 @@ render(
|
|
|
12
12
|
)
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
+
## Barra de filtros compartilhada
|
|
16
|
+
|
|
17
|
+
`ActionFilterBar` expõe a mesma linguagem declarativa de busca, filtros e período para
|
|
18
|
+
superfícies que não são listagens, como relatórios. O estado é controlado pelo consumidor;
|
|
19
|
+
`onRefresh` refaz sua consulta sem alterar o recorte vigente.
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
<ActionFilterBar
|
|
23
|
+
action={{ text, filters, periods }}
|
|
24
|
+
state={filterState}
|
|
25
|
+
onStateChange={setFilterState}
|
|
26
|
+
onRefresh={refetch}
|
|
27
|
+
refreshing={isLoading}
|
|
28
|
+
/>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Busca e filtros são renderizados somente quando declarados. Períodos incluem os presets do
|
|
32
|
+
contrato e o intervalo personalizado no mesmo calendário usado pela `ActionList`.
|
|
33
|
+
|
|
15
34
|
## Células custom (cells)
|
|
16
35
|
|
|
17
36
|
As colunas do contrato descrevem DADOS; apresentação especial entra por cima com `cells` (chave = key da coluna). É onde vivem chips coloridos por dict, links e a coluna de ações.
|
|
@@ -49,7 +68,7 @@ periods: [
|
|
|
49
68
|
|
|
50
69
|
## Paginação e loading
|
|
51
70
|
|
|
52
|
-
O pattern manda `limit` (= `pageSize`, default 50) e `page` no input; o handler implementa o OFFSET e devolve `total` no Paginated. O rodapé (Página X de Y · páginas numeradas com reticências · "N itens no total") aparece sempre que há total; mudar filtro/busca/sort/período volta pra página 1 (a página vive na URL: `?page=2`). No refetch com a lista já na tela, o corpo esmaece (`aria-busy`) até os dados chegarem
|
|
71
|
+
O pattern manda `limit` (= `pageSize`, default 50) e `page` no input; o handler implementa o OFFSET e devolve `total` no Paginated. O rodapé (Página X de Y · páginas numeradas com reticências · "N itens no total") aparece sempre que há total; mudar filtro/busca/sort/período volta pra página 1 (a página vive na URL: `?page=2`). No primeiro carregamento, `DataState` centraliza o spinner; no refetch com a lista já na tela, o corpo esmaece (`aria-busy`) até os dados chegarem.
|
|
53
72
|
|
|
54
73
|
## Multi-seleção com can (batch)
|
|
55
74
|
|
|
@@ -70,4 +70,4 @@ composição e onde o `variant` entra:
|
|
|
70
70
|
| `open` (AlertDialog) | `boolean` | | Estado de aberto no modo controlado — pareie com onOpenChange. No padrão, o Trigger cuida disso. |
|
|
71
71
|
| `onOpenChange` (AlertDialog) | `(open: boolean) => void` | | Chamado quando o diálogo abre ou fecha (Trigger, Cancel, Action ou Esc). |
|
|
72
72
|
| `variant` (AlertDialogAction) | `'default' \| 'destructive' \| 'outline' \| 'secondary' \| 'ghost' \| 'link'` | `'default'` | Intenção do botão de confirmar (herdada do Button) — destructive pra ação perigosa. |
|
|
73
|
-
| `variant` (AlertDialogCancel) | `'default' \| 'destructive' \| 'outline' \| 'secondary' \| 'ghost' \| 'link'` | `'
|
|
73
|
+
| `variant` (AlertDialogCancel) | `'default' \| 'destructive' \| 'outline' \| 'secondary' \| 'ghost' \| 'link'` | `'ghost'` | Intenção do botão de cancelar (herdada do Button). |
|
|
@@ -4,10 +4,10 @@ variant é intenção, não decoração: default pra ação principal (uma por t
|
|
|
4
4
|
quando destrói de verdade.
|
|
5
5
|
|
|
6
6
|
```tsx preview
|
|
7
|
-
<Button>
|
|
7
|
+
<Button>Criar workspace</Button>
|
|
8
8
|
<Button variant="secondary">Duplicar</Button>
|
|
9
|
-
<Button variant="outline">
|
|
10
|
-
<Button variant="ghost">
|
|
9
|
+
<Button variant="outline">Ver prévia</Button>
|
|
10
|
+
<Button variant="ghost">Cancelar</Button>
|
|
11
11
|
<Button variant="destructive">Excluir</Button>
|
|
12
12
|
<Button variant="link">Ver documentação</Button>
|
|
13
13
|
```
|
|
@@ -33,7 +33,7 @@ centralizado/Skeleton num nível de página.
|
|
|
33
33
|
|
|
34
34
|
```tsx preview
|
|
35
35
|
<Button disabled>Desabilitado</Button>
|
|
36
|
-
<Button busy>
|
|
36
|
+
<Button busy>Salvando…</Button>
|
|
37
37
|
<Button icon={Plus}>Novo</Button>
|
|
38
38
|
<Button icon={Plus} busy>Novo</Button>
|
|
39
39
|
```
|
|
@@ -39,7 +39,7 @@ de 16, sem transformar esse valor em uma regra da biblioteca.
|
|
|
39
39
|
> vence o conflito. Pra layout local (largura, margem, grid) — não pra repintar o visual da casa.
|
|
40
40
|
|
|
41
41
|
```tsx
|
|
42
|
-
<Button className="w-full">
|
|
42
|
+
<Button className="w-full">Continuar</Button>
|
|
43
43
|
<Card className="max-w-sm" />
|
|
44
44
|
<DialogContent className="sm:max-w-2xl" />
|
|
45
45
|
```
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
## Estados
|
|
2
2
|
|
|
3
3
|
Um lugar só pro erro/carregando/vazio/conteúdo de uma carga. Carregando = Spinner centralizado;
|
|
4
|
-
vazio = texto; erro = aviso calmo (a mensagem técnica não vai
|
|
4
|
+
vazio = texto em uma moldura sólida no modo bloco; erro = aviso calmo (a mensagem técnica não vai
|
|
5
|
+
pra tela). É o "antes" do conteúdo —
|
|
5
6
|
pro "depois" (ação em andamento), use o busy do Button.
|
|
6
7
|
|
|
7
8
|
```tsx preview col
|
|
@@ -20,8 +21,10 @@ pro "depois" (ação em andamento), use o busy do Button.
|
|
|
20
21
|
|
|
21
22
|
## Em tabela (colSpan)
|
|
22
23
|
|
|
23
|
-
Em lista
|
|
24
|
-
que cabe direto no `<tbody>`; o conteúdo são as `<tr>` dos itens.
|
|
24
|
+
Em lista ou tabela já emoldurada, passe colSpan: o estado vira UMA linha de largura cheia
|
|
25
|
+
(`<tr><td colSpan>`) que cabe direto no `<tbody>`; o conteúdo são as `<tr>` dos itens. A tabela
|
|
26
|
+
continua dona da borda, sem uma segunda moldura no vazio. Para uma região disponível para criação
|
|
27
|
+
ou vínculo, com título, descrição ou ação, use `Empty`, cuja moldura é tracejada.
|
|
25
28
|
|
|
26
29
|
```tsx preview col
|
|
27
30
|
<table className="w-full overflow-hidden rounded-lg border border-border text-sm">
|
|
@@ -18,7 +18,7 @@ Content é superfície pura; o espaço mora nos slots (body p-5; header/footer p
|
|
|
18
18
|
</DialogBody>
|
|
19
19
|
<DialogFooter>
|
|
20
20
|
<DialogClose asChild>
|
|
21
|
-
<Button variant="
|
|
21
|
+
<Button variant="ghost" size="sm">Cancelar</Button>
|
|
22
22
|
</DialogClose>
|
|
23
23
|
<Button size="sm">Criar</Button>
|
|
24
24
|
</DialogFooter>
|
|
@@ -42,7 +42,7 @@ showCloseButton={false} esconde o X: o usuário decide pelos botões — pra con
|
|
|
42
42
|
</DialogHeader>
|
|
43
43
|
<DialogFooter>
|
|
44
44
|
<DialogClose asChild>
|
|
45
|
-
<Button variant="
|
|
45
|
+
<Button variant="ghost" size="sm">Cancelar</Button>
|
|
46
46
|
</DialogClose>
|
|
47
47
|
<DialogClose asChild>
|
|
48
48
|
<Button variant="destructive" size="sm">Excluir</Button>
|
|
@@ -38,9 +38,9 @@ side aceita top|right|bottom|left. DrawerFooter ancora as ações no rodapé; Dr
|
|
|
38
38
|
</div>
|
|
39
39
|
<DrawerFooter>
|
|
40
40
|
<DrawerClose asChild>
|
|
41
|
-
<Button variant="
|
|
41
|
+
<Button variant="ghost" size="sm">Cancelar</Button>
|
|
42
42
|
</DrawerClose>
|
|
43
|
-
<Button size="sm">Salvar</Button>
|
|
43
|
+
<Button size="sm">Salvar alterações</Button>
|
|
44
44
|
</DrawerFooter>
|
|
45
45
|
</DrawerContent>
|
|
46
46
|
</Drawer>
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
## Quando usar
|
|
2
|
+
|
|
3
|
+
Use `Empty` quando a própria região está disponível para receber ou criar conteúdo. A moldura
|
|
4
|
+
tracejada diferencia esse espaço de uma estrutura de dados já estabelecida e pode combinar título,
|
|
5
|
+
descrição, mídia e ação.
|
|
6
|
+
|
|
7
|
+
Para uma lista ou tabela carregada, mas sem registros, use `DataState` ou `ActionList`: a moldura
|
|
8
|
+
sólida preserva a estrutura. Se a região já estiver emoldurada pelo pai, mantenha a borda somente
|
|
9
|
+
no pai para evitar uma moldura dupla.
|
|
10
|
+
|
|
1
11
|
## Com ícone
|
|
2
12
|
|
|
3
13
|
EmptyMedia variant=icon desenha o quadrado de fundo muted atrás do ícone; o EmptyHeader agrupa mídia, título e descrição no centro.
|
|
@@ -17,12 +17,12 @@ A composição completa: ItemMedia à esquerda, ItemContent (ItemTitle + ItemDes
|
|
|
17
17
|
</Item>
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
## Lista
|
|
20
|
+
## Lista emoldurada
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
`ItemGroup variant="framed"` aplica a superfície canônica. Os divisores continuam explícitos com `ItemSeparator`, tanto no modo `framed` quanto no `plain` — o padrão pra listas de repositórios, agentes ou sessões.
|
|
23
23
|
|
|
24
24
|
```tsx preview col
|
|
25
|
-
<ItemGroup>
|
|
25
|
+
<ItemGroup variant="framed">
|
|
26
26
|
<Item>
|
|
27
27
|
<ItemMedia variant="icon">
|
|
28
28
|
<GitBranch />
|
|
@@ -78,6 +78,7 @@ asChild funde o Item num <a> — a linha inteira vira alvo (hover no fundo). siz
|
|
|
78
78
|
|
|
79
79
|
| Prop | Tipo | Default | Descrição |
|
|
80
80
|
|---|---|---|---|
|
|
81
|
+
| `variant` (ItemGroup) | `'plain' \| 'framed'` | `'plain'` | `framed` aplica moldura e superfície; `plain` mantém a composição livre. Os divisores são explícitos nos dois modos. |
|
|
81
82
|
| `variant` (Item) | `'default' \| 'outline' \| 'muted'` | `'default'` | O fundo da linha — default transparente, outline com borda, muted levemente tingido. |
|
|
82
83
|
| `size` (Item) | `'default' \| 'sm'` | `'default'` | O respiro interno — sm aperta o padding pra listas densas. |
|
|
83
84
|
| `asChild` (Item) | `boolean` | `false` | Funde o Item no filho (ex.: <a> ou <button>) — a linha inteira vira o alvo. |
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Use `Page` para manter título, contexto, ações e conteúdo no mesmo ritmo visual nas telas do
|
|
4
4
|
back-office. As ações ficam à direita e acompanham a base do conjunto formado pelo título e pela
|
|
5
|
-
descrição. O
|
|
6
|
-
|
|
7
|
-
tabelas, cards ou outras composições.
|
|
5
|
+
descrição. O container é centralizado e ocupa a largura disponível até `72rem` (`max-w-6xl`). Use
|
|
6
|
+
`className` somente quando a composição pedir explicitamente outro teto ou largura total. A área
|
|
7
|
+
abaixo do cabeçalho permanece livre para tabelas, cards ou outras composições.
|
|
8
8
|
|
|
9
9
|
```tsx preview col
|
|
10
10
|
render(
|
|
@@ -35,5 +35,5 @@ render(
|
|
|
35
35
|
| `count` | `number` | | Total de itens ao lado do título (mono, esmaecido). |
|
|
36
36
|
| `description` | `ReactNode` | | Linha de contexto sob o título (ex.: "N no total · X ativos"). |
|
|
37
37
|
| `actions` | `ReactNode` | | Ações à direita do cabeçalho, alinhadas à base do título e da descrição. |
|
|
38
|
-
| `className` | `string` |
|
|
38
|
+
| `className` | `string` | `max-w-6xl` | Classes do container para substituir o teto padrão de `72rem`. |
|
|
39
39
|
| `children` | `ReactNode` | | O conteúdo — espaçamento e diagramação são seus. |
|
|
@@ -15,7 +15,7 @@ PopoverHeader agrupa título e descrição; o corpo é livre (form curto, detalh
|
|
|
15
15
|
<div className="mt-3 space-y-2">
|
|
16
16
|
<Label htmlFor="pop-titulo">Título</Label>
|
|
17
17
|
<Input id="pop-titulo" defaultValue="Onboarding do faturamento" />
|
|
18
|
-
<Button size="sm" className="w-full">
|
|
18
|
+
<Button size="sm" className="w-full">Renomear</Button>
|
|
19
19
|
</div>
|
|
20
20
|
</PopoverContent>
|
|
21
21
|
</Popover>
|
package/src/ui/meta.ts
CHANGED
|
@@ -83,6 +83,12 @@ export const componentMeta = {
|
|
|
83
83
|
whenToUse:
|
|
84
84
|
'A caixa de escrever da casa (o composer do Chat, extraído): textarea numa pílula elevada, Enter envia / Shift+Enter quebra linha, enviar dentro. Use SOZINHO quando há entrada de texto mas não um chat — ex.: o composer de criação de sessão do Maestro (sem histórico). Com `actions`, ganha uma barra embaixo pra seletores discretos à esquerda (app, agente, contexto…) — o mesmo lugar onde o Maestro põe app/task e a GB poria o agente. Sem `actions`, é a linha única de sempre. Controlado (`value`/`onChange`/`onSubmit`); `submitDisabled` gateia além de vazio/busy. Pra um chat completo (mensagens + este composer), use Chat.',
|
|
85
85
|
},
|
|
86
|
+
'content-header': {
|
|
87
|
+
name: 'content-header',
|
|
88
|
+
ancestry: 'opus',
|
|
89
|
+
whenToUse:
|
|
90
|
+
'Cabeçalho semântico de uma seção de conteúdo: título, descrição, meta e ações na mesma composição. Use `variant="section"` dentro de páginas e painéis; `Page` já fornece o cabeçalho principal e não deve receber outro ContentHeader equivalente. O `level` controla a hierarquia do heading sem alterar a hierarquia visual.',
|
|
91
|
+
},
|
|
86
92
|
'copyable': {
|
|
87
93
|
name: 'copyable',
|
|
88
94
|
ancestry: 'opus',
|
|
@@ -245,7 +251,7 @@ export const componentMeta = {
|
|
|
245
251
|
name: 'empty',
|
|
246
252
|
ancestry: 'shadcn',
|
|
247
253
|
whenToUse:
|
|
248
|
-
'
|
|
254
|
+
'Região disponível para receber ou criar conteúdo: moldura tracejada centrada com `EmptyHeader` (mídia + `EmptyTitle` + `EmptyDescription`) e `EmptyContent` pras ações. Para lista ou tabela carregada sem registros, use DataState ou ActionList, que preservam a moldura sólida da estrutura. `EmptyMedia variant` icon (quadrado muted) ou default (sem fundo). Pra erro inline use Alert; pra carregamento, Skeleton.',
|
|
249
255
|
},
|
|
250
256
|
'field': {
|
|
251
257
|
name: 'field',
|
|
@@ -269,7 +275,7 @@ export const componentMeta = {
|
|
|
269
275
|
name: 'item',
|
|
270
276
|
ancestry: 'shadcn',
|
|
271
277
|
whenToUse:
|
|
272
|
-
'Linha de conteúdo composta — mídia + título/descrição + ações numa linha clicável ou estática. Item é o container (`variant` default/outline/muted, `size` default/sm, `asChild` pra virar link/botão); componha ItemMedia (`variant` icon/image), ItemContent (ItemTitle + ItemDescription), ItemActions e, na borda, ItemHeader/ItemFooter. Empilhe vários num ItemGroup
|
|
278
|
+
'Linha de conteúdo composta — mídia + título/descrição + ações numa linha clicável ou estática. Item é o container (`variant` default/outline/muted, `size` default/sm, `asChild` pra virar link/botão); componha ItemMedia (`variant` icon/image), ItemContent (ItemTitle + ItemDescription), ItemActions e, na borda, ItemHeader/ItemFooter. Empilhe vários num ItemGroup: `variant="framed"` aplica a moldura e a superfície; ItemSeparator declara os divisores internos tanto no modo framed quanto no plain. É o padrão pra listas de workspaces, agentes, repositórios e sessões.',
|
|
273
279
|
},
|
|
274
280
|
'kbd': {
|
|
275
281
|
name: 'kbd',
|
|
@@ -384,7 +390,7 @@ export const componentMeta = {
|
|
|
384
390
|
name: 'page',
|
|
385
391
|
ancestry: 'opus',
|
|
386
392
|
whenToUse:
|
|
387
|
-
'O esqueleto de página do back-office: <main> + container de
|
|
393
|
+
'O esqueleto de página do back-office: <main> + container centralizado com teto padrão de 72rem (`max-w-6xl`) + header com título, descrição e ação à direita (em geral o criar). `className` substitui o teto quando a composição pede outra largura. Presentacional (a página agrega N fontes); o action-driven mora dentro. Pra listagem em modal, ActionListDialog.',
|
|
388
394
|
},
|
|
389
395
|
'router': {
|
|
390
396
|
name: 'router',
|
|
@@ -396,7 +402,7 @@ export const componentMeta = {
|
|
|
396
402
|
name: 'data-state',
|
|
397
403
|
ancestry: 'opus',
|
|
398
404
|
whenToUse:
|
|
399
|
-
'O estado "carregando" (ANTES do conteúdo): orquestra erro/carregando/vazio/conteúdo de uma carga assíncrona num só lugar — Spinner centralizado no loading, texto no vazio
|
|
405
|
+
'O estado "carregando" (ANTES do conteúdo): orquestra erro/carregando/vazio/conteúdo de uma carga assíncrona num só lugar — Spinner centralizado no loading, texto em moldura sólida no vazio em bloco e aviso calmo no erro. Em tabela emoldurada, `colSpan` mantém a borda somente no pai. Pra uma região disponível para criação ou vínculo, use Empty, cuja moldura é tracejada. Pra "processando" (ação em andamento DEPOIS do clique), use o `busy` do Button. Pra placeholder com forma, Skeleton.',
|
|
400
406
|
},
|
|
401
407
|
'action-list-dialog': {
|
|
402
408
|
name: 'action-list-dialog',
|
package/src/ui/react.tsx
CHANGED
|
@@ -187,8 +187,8 @@ export type { ActionFormDialogProps } from './components/patterns/form-dialog.ts
|
|
|
187
187
|
export { ActionFormCard } from './components/patterns/action-form-card.tsx'
|
|
188
188
|
export type { ActionFormCardProps } from './components/patterns/action-form-card.tsx'
|
|
189
189
|
|
|
190
|
-
export { ActionList, emptyListState, presetRange, listParamsToState, listStateToParams } from './components/patterns/list.tsx'
|
|
191
|
-
export type { ActionListProps, ActionListColumn, ActionListState, ActionListView, ActionListBatchAction, ListActionLike } from './components/patterns/list.tsx'
|
|
190
|
+
export { ActionFilterBar, ActionList, emptyListState, presetRange, listParamsToState, listStateToParams } from './components/patterns/list.tsx'
|
|
191
|
+
export type { ActionFilterBarProps, ActionFilterState, ActionListProps, ActionListColumn, ActionListState, ActionListView, ActionListBatchAction, ListActionLike } from './components/patterns/list.tsx'
|
|
192
192
|
|
|
193
193
|
export { ActionView } from './components/patterns/view.tsx'
|
|
194
194
|
export type { ActionViewProps } from './components/patterns/view.tsx'
|
|
@@ -210,6 +210,14 @@ export type { DataStateProps } from './components/patterns/data-state.tsx'
|
|
|
210
210
|
export { Page } from './components/patterns/page.tsx'
|
|
211
211
|
export type { PageProps } from './components/patterns/page.tsx'
|
|
212
212
|
|
|
213
|
+
// Cabeçalho de conteúdo em duas hierarquias visuais, sem impor espaçamento externo.
|
|
214
|
+
export { ContentHeader } from './components/patterns/content-header.tsx'
|
|
215
|
+
export type {
|
|
216
|
+
ContentHeaderProps,
|
|
217
|
+
ContentHeaderLevel,
|
|
218
|
+
ContentHeaderVariant,
|
|
219
|
+
} from './components/patterns/content-header.tsx'
|
|
220
|
+
|
|
213
221
|
// Layout composicional: Split decide a relação espacial; Pane carrega conteúdo com inset.
|
|
214
222
|
export { Split, Pane } from './components/patterns/split.tsx'
|
|
215
223
|
export type { SplitProps, PaneProps, PaneSize, SplitLayout, SplitLayoutChange } from './components/patterns/split.tsx'
|