@softize/opus 18.0.1 → 18.1.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 +47 -0
- package/docs/adr/0016-productive-surfaces-use-compact-density.md +62 -0
- package/docs/adr/0017-ui-assumes-a-fixed-desktop-layout.md +54 -0
- package/docs/relative-unit-scale.md +9 -2
- package/package.json +1 -1
- package/registry/skills/build-opus-ui/references/evaluations.md +3 -1
- package/registry/skills/build-opus-ui/references/ui-patterns.md +9 -3
- package/src/core/runtime.ts +7 -1
- package/src/core/types.ts +12 -3
- package/src/mcp/index.ts +4 -1
- package/src/ui/components/patterns/action-list-dialog.tsx +2 -2
- package/src/ui/components/patterns/content-header.tsx +1 -1
- package/src/ui/components/patterns/form-dialog.tsx +7 -2
- package/src/ui/components/patterns/list.tsx +239 -47
- package/src/ui/components/patterns/presentation.tsx +7 -5
- package/src/ui/components/patterns/state-surface.tsx +2 -2
- package/src/ui/components/patterns/surface-header.tsx +4 -4
- package/src/ui/components/primitives/alert.tsx +2 -2
- package/src/ui/components/primitives/breadcrumb.tsx +1 -1
- package/src/ui/components/primitives/button-group.tsx +1 -1
- package/src/ui/components/primitives/button.tsx +3 -3
- package/src/ui/components/primitives/calendar.tsx +1 -1
- package/src/ui/components/primitives/close-button.tsx +40 -0
- package/src/ui/components/primitives/detail.tsx +66 -28
- package/src/ui/components/primitives/dialog.tsx +36 -21
- package/src/ui/components/primitives/drawer.tsx +26 -19
- package/src/ui/components/primitives/empty-value.tsx +3 -3
- package/src/ui/components/primitives/empty.tsx +1 -1
- package/src/ui/components/primitives/field.tsx +12 -12
- package/src/ui/components/primitives/icon-picker.tsx +1 -1
- package/src/ui/components/primitives/input-group.tsx +1 -1
- package/src/ui/components/primitives/input.tsx +2 -2
- package/src/ui/components/primitives/item.tsx +5 -5
- package/src/ui/components/primitives/pagination.tsx +4 -4
- package/src/ui/components/primitives/select.tsx +2 -2
- package/src/ui/components/primitives/table.tsx +26 -17
- package/src/ui/components/primitives/tabs.tsx +80 -23
- package/src/ui/components/primitives/textarea.tsx +1 -1
- package/src/ui/components/primitives/toggle-group.tsx +9 -2
- package/src/ui/docs/content/action-form.md +19 -0
- package/src/ui/docs/content/action-list-dialog.md +1 -1
- package/src/ui/docs/content/action-list.md +7 -1
- package/src/ui/docs/content/alert.md +2 -0
- package/src/ui/docs/content/button.md +2 -1
- package/src/ui/docs/content/customization.md +11 -1
- package/src/ui/docs/content/detail.md +9 -8
- package/src/ui/docs/content/dialog.md +12 -5
- package/src/ui/docs/content/drawer.md +6 -3
- package/src/ui/docs/content/empty-value.md +4 -4
- package/src/ui/docs/content/field.md +1 -1
- package/src/ui/docs/content/item.md +2 -0
- package/src/ui/docs/content/page.md +1 -1
- package/src/ui/docs/content/presentation.md +2 -2
- package/src/ui/docs/content/tabs.md +16 -6
- package/src/ui/docs/content/toast.md +2 -1
- package/src/ui/docs/content/tokens.md +45 -2
- package/src/ui/react.tsx +1 -0
- package/src/ui/theme.css +3 -0
|
@@ -2,36 +2,45 @@ import * as React from "react"
|
|
|
2
2
|
|
|
3
3
|
import { cn } from '../../lib/cn.ts'
|
|
4
4
|
|
|
5
|
+
type TableVariant = 'plain' | 'framed'
|
|
6
|
+
const TableVariantContext = React.createContext<TableVariant>('plain')
|
|
7
|
+
|
|
5
8
|
export interface TableProps extends React.ComponentProps<"table"> {
|
|
6
9
|
/** `framed` aplica a moldura canônica de datagrid no contêiner da tabela. */
|
|
7
|
-
variant?:
|
|
10
|
+
variant?: TableVariant
|
|
8
11
|
}
|
|
9
12
|
|
|
10
13
|
function Table({ className, variant = 'plain', ...props }: TableProps) {
|
|
11
14
|
return (
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
'rounded-lg border border-border
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
15
|
+
<TableVariantContext.Provider value={variant}>
|
|
16
|
+
<div
|
|
17
|
+
data-slot="table-container"
|
|
18
|
+
data-variant={variant}
|
|
19
|
+
className={cn(
|
|
20
|
+
"relative w-full overflow-x-auto",
|
|
21
|
+
variant === 'framed' && 'rounded-lg border border-border',
|
|
22
|
+
)}
|
|
23
|
+
>
|
|
24
|
+
<table
|
|
25
|
+
data-slot="table"
|
|
26
|
+
className={cn("w-full caption-bottom text-sm", className)}
|
|
27
|
+
{...props}
|
|
28
|
+
/>
|
|
29
|
+
</div>
|
|
30
|
+
</TableVariantContext.Provider>
|
|
27
31
|
)
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
|
|
35
|
+
const variant = React.useContext(TableVariantContext)
|
|
31
36
|
return (
|
|
32
37
|
<thead
|
|
33
38
|
data-slot="table-header"
|
|
34
|
-
className={cn(
|
|
39
|
+
className={cn(
|
|
40
|
+
"[&_tr]:border-b",
|
|
41
|
+
variant === 'framed' && 'bg-muted/30 dark:bg-input/30',
|
|
42
|
+
className,
|
|
43
|
+
)}
|
|
35
44
|
{...props}
|
|
36
45
|
/>
|
|
37
46
|
)
|
|
@@ -11,6 +11,9 @@ import { controlHeight, type ControlSize } from './control.ts'
|
|
|
11
11
|
// da escala de control.ts.
|
|
12
12
|
type TabsSize = Extract<ControlSize, "default" | "sm">
|
|
13
13
|
const TabsSizeContext = React.createContext<TabsSize>("default")
|
|
14
|
+
type TabsOrientation = "horizontal" | "vertical"
|
|
15
|
+
const TabsOrientationContext = React.createContext<TabsOrientation>("horizontal")
|
|
16
|
+
const TabsListVariantContext = React.createContext<"default" | "line">("default")
|
|
14
17
|
|
|
15
18
|
function Tabs({
|
|
16
19
|
className,
|
|
@@ -19,19 +22,21 @@ function Tabs({
|
|
|
19
22
|
...props
|
|
20
23
|
}: React.ComponentProps<typeof TabsPrimitive.Root> & { size?: TabsSize }) {
|
|
21
24
|
return (
|
|
22
|
-
<
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
25
|
+
<TabsOrientationContext.Provider value={orientation}>
|
|
26
|
+
<TabsSizeContext.Provider value={size}>
|
|
27
|
+
<TabsPrimitive.Root
|
|
28
|
+
data-slot="tabs"
|
|
29
|
+
data-orientation={orientation}
|
|
30
|
+
data-size={size}
|
|
31
|
+
orientation={orientation}
|
|
32
|
+
className={cn(
|
|
33
|
+
"group/tabs flex gap-2 data-[orientation=horizontal]:flex-col",
|
|
34
|
+
className
|
|
35
|
+
)}
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
</TabsSizeContext.Provider>
|
|
39
|
+
</TabsOrientationContext.Provider>
|
|
35
40
|
)
|
|
36
41
|
}
|
|
37
42
|
|
|
@@ -44,7 +49,7 @@ const tabsListVariants = cva(
|
|
|
44
49
|
variants: {
|
|
45
50
|
variant: {
|
|
46
51
|
default: "bg-muted",
|
|
47
|
-
line: "gap-
|
|
52
|
+
line: "gap-5 bg-transparent",
|
|
48
53
|
},
|
|
49
54
|
},
|
|
50
55
|
defaultVariants: {
|
|
@@ -55,27 +60,66 @@ const tabsListVariants = cva(
|
|
|
55
60
|
|
|
56
61
|
function TabsList({
|
|
57
62
|
className,
|
|
63
|
+
children,
|
|
64
|
+
style,
|
|
58
65
|
variant = "default",
|
|
59
66
|
...props
|
|
60
67
|
}: React.ComponentProps<typeof TabsPrimitive.List> &
|
|
61
68
|
VariantProps<typeof tabsListVariants>) {
|
|
62
69
|
// O size vem do <Tabs> (contexto) — a altura mora aqui, na list.
|
|
63
70
|
const size = React.useContext(TabsSizeContext)
|
|
71
|
+
const orientation = React.useContext(TabsOrientationContext)
|
|
72
|
+
const normalizedVariant = variant ?? "default"
|
|
73
|
+
const hasHorizontalLine = normalizedVariant === "line" && orientation === "horizontal"
|
|
64
74
|
return (
|
|
65
|
-
<
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
75
|
+
<TabsListVariantContext.Provider value={normalizedVariant}>
|
|
76
|
+
<TabsPrimitive.List
|
|
77
|
+
data-slot="tabs-list"
|
|
78
|
+
data-variant={normalizedVariant}
|
|
79
|
+
data-size={size}
|
|
80
|
+
className={cn(tabsListVariants({ variant: normalizedVariant }), controlHeight[size], className)}
|
|
81
|
+
style={{
|
|
82
|
+
...(hasHorizontalLine
|
|
83
|
+
? { position: "relative", width: "100%", height: "auto", justifyContent: "flex-start" }
|
|
84
|
+
: {}),
|
|
85
|
+
...style,
|
|
86
|
+
}}
|
|
87
|
+
{...props}
|
|
88
|
+
>
|
|
89
|
+
{hasHorizontalLine && (
|
|
90
|
+
<span
|
|
91
|
+
aria-hidden="true"
|
|
92
|
+
data-slot="tabs-line"
|
|
93
|
+
style={{
|
|
94
|
+
position: "absolute",
|
|
95
|
+
insetInline: "calc(var(--opus-inline-gutter, 0rem) * -1)",
|
|
96
|
+
bottom: 0,
|
|
97
|
+
borderBottomWidth: "0.0625rem",
|
|
98
|
+
borderBottomStyle: "solid",
|
|
99
|
+
borderBottomColor: "var(--border)",
|
|
100
|
+
pointerEvents: "none",
|
|
101
|
+
}}
|
|
102
|
+
/>
|
|
103
|
+
)}
|
|
104
|
+
{children}
|
|
105
|
+
</TabsPrimitive.List>
|
|
106
|
+
</TabsListVariantContext.Provider>
|
|
72
107
|
)
|
|
73
108
|
}
|
|
74
109
|
|
|
75
110
|
function TabsTrigger({
|
|
76
111
|
className,
|
|
112
|
+
style,
|
|
113
|
+
icon,
|
|
114
|
+
children,
|
|
77
115
|
...props
|
|
78
|
-
}: React.ComponentProps<typeof TabsPrimitive.Trigger>
|
|
116
|
+
}: React.ComponentProps<typeof TabsPrimitive.Trigger> & {
|
|
117
|
+
/** Ícone decorativo antes do rótulo. Em uma aba sem texto, declare também `aria-label`. */
|
|
118
|
+
icon?: React.ReactNode
|
|
119
|
+
}) {
|
|
120
|
+
const variant = React.useContext(TabsListVariantContext)
|
|
121
|
+
const orientation = React.useContext(TabsOrientationContext)
|
|
122
|
+
const hasHorizontalLine = variant === "line" && orientation === "horizontal"
|
|
79
123
|
return (
|
|
80
124
|
<TabsPrimitive.Trigger
|
|
81
125
|
data-slot="tabs-trigger"
|
|
@@ -85,10 +129,23 @@ function TabsTrigger({
|
|
|
85
129
|
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent",
|
|
86
130
|
"data-[state=active]:bg-background data-[state=active]:text-foreground dark:data-[state=active]:border-input dark:data-[state=active]:text-foreground",
|
|
87
131
|
"after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-1px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100",
|
|
132
|
+
hasHorizontalLine && "px-0 py-3",
|
|
88
133
|
className
|
|
89
134
|
)}
|
|
135
|
+
style={{ ...(hasHorizontalLine ? { flex: "none", height: "auto" } : {}), ...style }}
|
|
90
136
|
{...props}
|
|
91
|
-
|
|
137
|
+
>
|
|
138
|
+
{icon !== undefined && (
|
|
139
|
+
<span
|
|
140
|
+
data-slot="tabs-trigger-icon"
|
|
141
|
+
aria-hidden="true"
|
|
142
|
+
className="flex size-4 shrink-0 items-center justify-center [&_svg]:size-4"
|
|
143
|
+
>
|
|
144
|
+
{icon}
|
|
145
|
+
</span>
|
|
146
|
+
)}
|
|
147
|
+
{children}
|
|
148
|
+
</TabsPrimitive.Trigger>
|
|
92
149
|
)
|
|
93
150
|
}
|
|
94
151
|
|
|
@@ -7,7 +7,7 @@ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
|
|
7
7
|
<textarea
|
|
8
8
|
data-slot="textarea"
|
|
9
9
|
className={cn(
|
|
10
|
-
"flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-
|
|
10
|
+
"flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-muted/30 px-3 py-2 text-sm transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[0.1875rem] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-context-danger aria-invalid:ring-context-danger/20 dark:bg-input/30 dark:aria-invalid:ring-context-danger/40",
|
|
11
11
|
className
|
|
12
12
|
)}
|
|
13
13
|
{...props}
|
|
@@ -18,6 +18,12 @@ const ToggleGroupContext = React.createContext<
|
|
|
18
18
|
shape: "default",
|
|
19
19
|
})
|
|
20
20
|
|
|
21
|
+
type ToggleGroupItemProps = React.ComponentProps<typeof ToggleGroupPrimitive.Item> &
|
|
22
|
+
VariantProps<typeof toggleVariants> & {
|
|
23
|
+
'data-slot'?: string
|
|
24
|
+
'data-state'?: string
|
|
25
|
+
}
|
|
26
|
+
|
|
21
27
|
function ToggleGroup({
|
|
22
28
|
className,
|
|
23
29
|
variant,
|
|
@@ -58,9 +64,10 @@ function ToggleGroupItem({
|
|
|
58
64
|
variant,
|
|
59
65
|
size,
|
|
60
66
|
shape,
|
|
67
|
+
'data-slot': _composedSlot,
|
|
68
|
+
'data-state': _composedState,
|
|
61
69
|
...props
|
|
62
|
-
}:
|
|
63
|
-
VariantProps<typeof toggleVariants>) {
|
|
70
|
+
}: ToggleGroupItemProps) {
|
|
64
71
|
const context = React.useContext(ToggleGroupContext)
|
|
65
72
|
|
|
66
73
|
return (
|
|
@@ -48,6 +48,13 @@ invalidação continuam sob responsabilidade de `ActionForm`.
|
|
|
48
48
|
</DocBrowserActionProvider>
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
+
## Formulário modal
|
|
52
|
+
|
|
53
|
+
`ActionFormDialog` acrescenta a moldura, o corpo rolável e o footer ao formulário. Por padrão, as
|
|
54
|
+
ações ficam alinhadas no fim da faixa e preservam a largura do conteúdo; `Cancelar` usa `ghost`.
|
|
55
|
+
Use `footerDistribution="equal"` somente quando as duas decisões precisarem do mesmo peso visual.
|
|
56
|
+
Nesse caso, o cancelamento muda para `outline`, salvo escolha explícita em `cancelVariant`.
|
|
57
|
+
|
|
51
58
|
## Pré-requisitos
|
|
52
59
|
|
|
53
60
|
Monte os providers de consulta e execução uma vez na raiz do aplicativo:
|
|
@@ -88,6 +95,18 @@ ciclo de vida que o pattern não cobre; validação e execução continuam iguai
|
|
|
88
95
|
| `options` | `SelectOption[]` | Opções por id de runtime — sobrepõe o fieldOptions do form e o z.enum. |
|
|
89
96
|
| `className` | `string` | Classes do invólucro (ex.: `col-span-2` em uma grid). |
|
|
90
97
|
|
|
98
|
+
## Propriedades de ActionFormDialog
|
|
99
|
+
|
|
100
|
+
Além das propriedades de `ActionForm`, o wrapper modal aceita:
|
|
101
|
+
|
|
102
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
103
|
+
|---|---|---|---|
|
|
104
|
+
| `open` | `boolean` | | Estado visível do diálogo. |
|
|
105
|
+
| `onOpenChange` | `(open: boolean) => void` | | Recebe abertura e fechamento. |
|
|
106
|
+
| `title` | `string` | | Nomeia a tarefa modal. |
|
|
107
|
+
| `intro` | `ReactNode` | | Contexto relevante apresentado antes dos campos. |
|
|
108
|
+
| `footerDistribution` | `'content' \| 'equal'` | `'content'` | Mantém a largura das ações pelo conteúdo ou divide a faixa igualmente. |
|
|
109
|
+
|
|
91
110
|
## Ajuda na label
|
|
92
111
|
|
|
93
112
|
`FieldSpec.help` aparece como um ícone junto à label, com o texto num tooltip. Escreva ali o
|
|
@@ -75,4 +75,4 @@ corpo do modal.
|
|
|
75
75
|
| `empty` | `(items) => boolean` | `items.length === 0` | Sobrepõe o vazio derivado. |
|
|
76
76
|
| `loading` | `boolean` | | Carga extra agregada à do fetch (query irmã). |
|
|
77
77
|
| `emptyMessage / errorMessage / retryLabel` | `string` | | Textos dos estados; `retryLabel` nomeia a ação somente com ícone e seu tooltip. |
|
|
78
|
-
| `className` | `string` | `
|
|
78
|
+
| `className` | `string` | `max-w-3xl` | Largura do DialogContent. |
|
|
@@ -88,6 +88,10 @@ Renderer customizado reutiliza `EmptyValue`.
|
|
|
88
88
|
|
|
89
89
|
Use `cells` somente quando uma coluna precisar de apresentação própria, como link, composição ou
|
|
90
90
|
ação. A chave corresponde à `key` da coluna. Valores de dicionário não precisam desse override.
|
|
91
|
+
O valor principal preserva o `text-sm` e o foreground da tabela; use
|
|
92
|
+
`text-xs text-muted-foreground` apenas em metadado subordinado a outro valor na mesma célula.
|
|
93
|
+
Cabeçalhos e `EmptyValue` continuam muted. O fato de uma coluna ser técnica, temporal ou menos
|
|
94
|
+
destacada não reduz nem atenua seu valor principal.
|
|
91
95
|
|
|
92
96
|
```tsx preview col
|
|
93
97
|
render(
|
|
@@ -291,6 +295,7 @@ render(
|
|
|
291
295
|
| `state` | `ActionFilterState` | | Estado atual da barra. |
|
|
292
296
|
| `onStateChange` | `(next) => void` | | Recebe o estado completo depois de cada alteração. |
|
|
293
297
|
| `filterOptions` | `Record<string, SelectOption[]>` | | Fornece opções de runtime para filtros select e lookup. |
|
|
298
|
+
| `advancedFilters` | `{ columns?: 1 \| 2 \| 3 }` | `{ columns: 1 }` | Define as colunas do diálogo de filtros avançados; o Opus deriva a largura correspondente. |
|
|
294
299
|
| `onRefresh` | `() => Promise<void> \| void` | | Exibe a ação de recarregar e executa a consulta do consumidor. |
|
|
295
300
|
| `refreshing` | `boolean` | `false` | Desabilita e anima a ação de recarregar durante a consulta. |
|
|
296
301
|
| `controls` | `ReactNode` | | Controles auxiliares agrupados com recarregar em um `ButtonGroup` espaçado. |
|
|
@@ -301,7 +306,7 @@ render(
|
|
|
301
306
|
| Chave | O que declara |
|
|
302
307
|
| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
303
308
|
| `columns` | `{ key, label, type?, sortable?, fit?, hidden?, dateFormat?, dictionary?, empty? }` — a tabela. `hidden` fica fora (base do futuro column picker); `dictionary` nomeia o dicionário do provider; `empty` dá o significado da ausência. |
|
|
304
|
-
| `filters` | `{ [nome]: { label, type, options?, multiple?,
|
|
309
|
+
| `filters` | `{ [nome]: { label, type, options?, multiple?, placement?, section?, depends?, … } }` — a toolbar. `placement` classifica o controle como `inline`, `advanced` ou `external`; filtros avançados com `section` são agrupados no diálogo; `external` mantém estado e input, mas delega a apresentação ao consumidor. `depends` desabilita/cascateia; options `kind: 'lookup'` busca em uma action. Valor aplicado entra no input com o mesmo nome. |
|
|
305
310
|
| `text` | `{ fields }` — liga a busca; convenção: param `q` no input. Com período/filtros no contrato ela fica à direita; sendo a ÚNICA forma de recorte, abre a linha. |
|
|
306
311
|
| `sort` | `{ fields, default }` — ordenação inicial; header ordenável escreve `sort: 'chave:dir'`. |
|
|
307
312
|
| `periods` | `{ value, label }[]` — o controle de período (presets + Personalizado com calendário); materializa em `from`/`to` no input. |
|
|
@@ -322,6 +327,7 @@ chamar a action.
|
|
|
322
327
|
| `input` | `TInput` | | O ESCOPO BASE (ex.: { workspaceId }) — a toolbar soma por cima, nunca sobrescreve. |
|
|
323
328
|
| `cells` | `Record<string, (item) => ReactNode>` | | Células custom por cima das colunas do contrato (chave = column.key). |
|
|
324
329
|
| `filterOptions` | `Record<string, SelectOption[]>` | | Opções de runtime para os filtros select/lookup (chave = nome do filtro). |
|
|
330
|
+
| `advancedFilters` | `{ columns?: 1 \| 2 \| 3 }` | `{ columns: 1 }` | Define as colunas dos grupos de filtros avançados; o Opus deriva a largura correspondente do diálogo. |
|
|
325
331
|
| `columns` | `ActionListColumn<TItem>[]` | | Tabela EXPLÍCITA — sobrepõe as colunas do contrato (escape hatch). |
|
|
326
332
|
| `children` | `(items, refetch) => ReactNode` | | Modo composição: layout livre; a toolbar segue. Tem precedência sobre columns. |
|
|
327
333
|
| `batch` | `ActionListBatchAction[]` | | Ações em lote: `{ label, can?, run, confirm?, destructive? }` — liga a multi-seleção. |
|
|
@@ -29,6 +29,8 @@ Use a forma curta quando o aviso tiver ícone, título e uma frase. Declare `tit
|
|
|
29
29
|
|
|
30
30
|
Título é opcional — o aviso de uma linha dispensa. O contexto continua visível por superfície,
|
|
31
31
|
borda e texto; o conteúdo comunica o significado sem depender somente da cor.
|
|
32
|
+
O Alert usa o corpo produtivo e `0.75rem` de padding; não reduza cada uso localmente para obter a
|
|
33
|
+
densidade comum.
|
|
32
34
|
|
|
33
35
|
```tsx preview col
|
|
34
36
|
<Alert description="Nenhuma sessão aberta neste repositório." />
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
`context` declara a hierarquia ou o risco da ação; `variant` escolhe o tratamento visual. Use
|
|
4
4
|
`primary` para a ação principal, `neutral` para ações de apoio e `danger` quando a ação tiver uma
|
|
5
|
-
consequência perigosa.
|
|
5
|
+
consequência perigosa. A variante `outline` mantém o fundo transparente em repouso e usa uma
|
|
6
|
+
superfície apenas no hover, preservando o fundo da região onde o botão está inserido.
|
|
6
7
|
|
|
7
8
|
```tsx preview
|
|
8
9
|
<Button>Criar workspace</Button>
|
|
@@ -33,6 +33,16 @@ Quando o produto precisa de outra densidade, declare essa escolha no CSS do app.
|
|
|
33
33
|
`html { font-size: 93.75%; }` conserva a proporção que uma raiz de 15 teria sobre a base usual
|
|
34
34
|
de 16, sem transformar esse valor em uma regra da biblioteca.
|
|
35
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.
|
|
45
|
+
|
|
36
46
|
## 2 · className em tudo
|
|
37
47
|
|
|
38
48
|
> Todo componente termina em `cn(base, className)` com tailwind-merge: o utilitário do consumidor
|
|
@@ -41,7 +51,7 @@ de 16, sem transformar esse valor em uma regra da biblioteca.
|
|
|
41
51
|
```tsx
|
|
42
52
|
<Button className="w-full">Continuar</Button>
|
|
43
53
|
<Card className="max-w-sm" />
|
|
44
|
-
<DialogContent className="
|
|
54
|
+
<DialogContent className="max-w-2xl" />
|
|
45
55
|
```
|
|
46
56
|
|
|
47
57
|
## 3 · Recomposição estrutural
|
|
@@ -15,7 +15,7 @@ validação, use `Field`.
|
|
|
15
15
|
}
|
|
16
16
|
/>
|
|
17
17
|
<DetailField
|
|
18
|
-
className="
|
|
18
|
+
className="col-span-2"
|
|
19
19
|
label="E-mail"
|
|
20
20
|
value="joao.silva@example.com"
|
|
21
21
|
icon={<Mail />}
|
|
@@ -25,8 +25,9 @@ validação, use `Field`.
|
|
|
25
25
|
|
|
26
26
|
## Valor ausente
|
|
27
27
|
|
|
28
|
-
`null`, `undefined`, string vazia ou só com espaços mostram
|
|
29
|
-
|
|
28
|
+
`null`, `undefined`, string vazia ou só com espaços mostram um travessão; a leitura assistiva
|
|
29
|
+
recebe “Não informado”. Uma string em `empty` troca esse significado no domínio, enquanto um nó
|
|
30
|
+
React substitui a apresentação. `0` e `false` seguem como valores. Ver `EmptyValue`.
|
|
30
31
|
|
|
31
32
|
```tsx preview col
|
|
32
33
|
<DetailGroup columns={2}>
|
|
@@ -40,8 +41,8 @@ significado no domínio. `0` e `false` seguem como valores. Ver `EmptyValue`.
|
|
|
40
41
|
`variant="framed"` adiciona a superfície e a borda externa. `dividers` desenha apenas as
|
|
41
42
|
divisórias internas; as duas opções são independentes e podem ser combinadas. `orientation`
|
|
42
43
|
define se a chave fica sobre o valor ou ao lado dele. Na orientação horizontal, todos os valores
|
|
43
|
-
começam depois da mesma coluna de rótulo, com largura padrão de `7rem
|
|
44
|
-
mais espaço para os rótulos, ajuste a variável no grupo, por exemplo com
|
|
44
|
+
começam depois da mesma coluna de rótulo, com largura padrão de `7rem` em grupos simples e
|
|
45
|
+
`8.5rem` na moldura horizontal. Quando a superfície exigir mais espaço para os rótulos, ajuste a variável no grupo, por exemplo com
|
|
45
46
|
`className="[--detail-label-width:9rem]"`.
|
|
46
47
|
|
|
47
48
|
```tsx preview col
|
|
@@ -49,7 +50,7 @@ mais espaço para os rótulos, ajuste a variável no grupo, por exemplo com
|
|
|
49
50
|
<DetailField label="Documento" value="Sem documento" />
|
|
50
51
|
<DetailField label="Fontes" value="NBS e Followize" />
|
|
51
52
|
<DetailField
|
|
52
|
-
className="
|
|
53
|
+
className="col-span-2"
|
|
53
54
|
label="E-mail"
|
|
54
55
|
value="cliente@example.com"
|
|
55
56
|
icon={<Mail />}
|
|
@@ -63,7 +64,7 @@ mais espaço para os rótulos, ajuste a variável no grupo, por exemplo com
|
|
|
63
64
|
|---|---|---|---|
|
|
64
65
|
| `variant` | `'plain' \| 'framed'` | `'plain'` | `framed` aplica a superfície e a moldura canônicas ao conjunto. |
|
|
65
66
|
| `dividers` | `boolean` | `false` | Hairlines somente entre os campos, sem exigir moldura externa. |
|
|
66
|
-
| `columns` | `1 \| 2 \| 3 \| 4 \| 'auto'` | `1` |
|
|
67
|
+
| `columns` | `1 \| 2 \| 3 \| 4 \| 'auto'` | `1` | Número fixo de colunas ou distribuição automática por largura mínima. |
|
|
67
68
|
| `orientation` | `'vertical' \| 'horizontal'` | `'vertical'` | Chave sobre o valor ou ao lado dele em cada campo. |
|
|
68
69
|
|
|
69
70
|
## Propriedades de DetailField
|
|
@@ -73,4 +74,4 @@ mais espaço para os rótulos, ajuste a variável no grupo, por exemplo com
|
|
|
73
74
|
| `label` | `ReactNode` | | A chave do par. |
|
|
74
75
|
| `value` | `ReactNode` | | O valor; `null`, `undefined` e string vazia renderizam a ausência, `0` e `false` seguem como valores. |
|
|
75
76
|
| `icon` | `ReactNode` | | Ícone decorativo antes do par chave/valor. |
|
|
76
|
-
| `empty` | `ReactNode` | `“Não informado”` |
|
|
77
|
+
| `empty` | `ReactNode` | `“Não informado”` | Uma string dá significado acessível ao travessão; um nó próprio substitui a apresentação. |
|
|
@@ -16,7 +16,8 @@ Para conteúdo ancorado e não modal, use `Popover`. Para uma lista de ações,
|
|
|
16
16
|
|
|
17
17
|
`DialogContent` delimita a superfície. `DialogHeader`, `DialogBody` e `DialogFooter` organizam
|
|
18
18
|
título, conteúdo rolável e ações com o espaçamento da família. A superfície usa o fundo base,
|
|
19
|
-
borda
|
|
19
|
+
borda transparente no tema claro, borda sutil no escuro, raio `xl` e elevação para permanecer
|
|
20
|
+
distinta da página.
|
|
20
21
|
|
|
21
22
|
```tsx preview
|
|
22
23
|
<Dialog>
|
|
@@ -32,9 +33,9 @@ borda semântica, raio `xl` e elevação para permanecer distinta da página.
|
|
|
32
33
|
<Input id="workspace-name" placeholder="Ex.: Empresa X" />
|
|
33
34
|
</DialogBody>
|
|
34
35
|
<DialogFooter>
|
|
35
|
-
<ButtonGroup mode="spaced"
|
|
36
|
+
<ButtonGroup mode="spaced">
|
|
36
37
|
<DialogClose asChild>
|
|
37
|
-
<Button variant="
|
|
38
|
+
<Button variant="ghost">Cancelar</Button>
|
|
38
39
|
</DialogClose>
|
|
39
40
|
<Button>Criar workspace</Button>
|
|
40
41
|
</ButtonGroup>
|
|
@@ -44,8 +45,12 @@ borda semântica, raio `xl` e elevação para permanecer distinta da página.
|
|
|
44
45
|
```
|
|
45
46
|
|
|
46
47
|
O corpo cresce até o limite da janela e passa a rolar; cabeçalho e rodapé permanecem visíveis.
|
|
47
|
-
O
|
|
48
|
-
|
|
48
|
+
O backdrop escurece a página sem aplicar desfoque.
|
|
49
|
+
O fechamento do cabeçalho usa uma ação circular neutra e sutil. `closeSize` escolhe entre `xs` e
|
|
50
|
+
`sm`; o padrão é `sm`.
|
|
51
|
+
O `ButtonGroup` preserva a largura natural das ações por padrão. Use `distribution="equal"`
|
|
52
|
+
somente para decisões deliberadamente equivalentes. O `DialogFooter` organiza a faixa, mas não
|
|
53
|
+
decide a distribuição nem a aparência dos botões.
|
|
49
54
|
`DialogClose` encerra o modal sem exigir estado controlado. Use `open` e `onOpenChange` quando outra
|
|
50
55
|
parte da interface também precisar controlar a abertura.
|
|
51
56
|
|
|
@@ -273,6 +278,8 @@ botão. O texto informa o efeito real, como `Excluir`, `Revogar acesso` ou `Ence
|
|
|
273
278
|
| Propriedade | Tipo | Padrão | Descrição |
|
|
274
279
|
| ----------------- | ----------- | ------ | --------------------------------------------------------------------------------------------------------- |
|
|
275
280
|
| `showCloseButton` | `boolean` | `true` | No modo padrão, inclui a ação de fechamento no header; o modo de alerta exige uma resposta identificável. |
|
|
281
|
+
| `closeDisabled` | `boolean` | `false` | Mantém a ação visível e impede o fechamento enquanto a tarefa está bloqueada. |
|
|
282
|
+
| `closeSize` | `"xs" \| "sm"` | `"sm"` | Define o tamanho da ação circular de fechar. |
|
|
276
283
|
| `className` | `string` | | Ajusta a superfície. |
|
|
277
284
|
| `children` | `ReactNode` | | Cabeçalho, corpo, rodapé ou conteúdo próprio. |
|
|
278
285
|
|
|
@@ -26,7 +26,8 @@ entra pela direita e pode ser fechado por Esc, pelo overlay ou pelo botão de fe
|
|
|
26
26
|
`side` aceita `top`, `right`, `bottom` e `left`. `DrawerFooter` mantém as ações no rodapé;
|
|
27
27
|
`DrawerClose` fecha o painel sem exigir controle manual de estado. Como no `Dialog`, o cabeçalho
|
|
28
28
|
recebe um divisor inferior e o rodapé usa um divisor superior. O corpo preserva o mesmo alinhamento
|
|
29
|
-
horizontal entre título, conteúdo e ações.
|
|
29
|
+
horizontal entre título, conteúdo e ações. O fechamento do cabeçalho usa uma ação circular neutra
|
|
30
|
+
e sutil; `closeSize` escolhe entre `xs` e `sm`, com `sm` como padrão.
|
|
30
31
|
|
|
31
32
|
```tsx preview
|
|
32
33
|
<Drawer>
|
|
@@ -42,9 +43,9 @@ horizontal entre título, conteúdo e ações.
|
|
|
42
43
|
<Input id="ws-nome" defaultValue="Empresa X" />
|
|
43
44
|
</DrawerBody>
|
|
44
45
|
<DrawerFooter>
|
|
45
|
-
<ButtonGroup mode="spaced"
|
|
46
|
+
<ButtonGroup mode="spaced">
|
|
46
47
|
<DrawerClose asChild>
|
|
47
|
-
<Button variant="
|
|
48
|
+
<Button variant="ghost">Cancelar</Button>
|
|
48
49
|
</DrawerClose>
|
|
49
50
|
<Button>Salvar</Button>
|
|
50
51
|
</ButtonGroup>
|
|
@@ -66,6 +67,8 @@ horizontal entre título, conteúdo e ações.
|
|
|
66
67
|
| ----------------- | ---------------------------------------- | --------- | ------------------------------------------------------------------------------------------ |
|
|
67
68
|
| `side` | `'top' \| 'right' \| 'bottom' \| 'left'` | `'right'` | Borda de onde o painel aparece. |
|
|
68
69
|
| `showCloseButton` | `boolean` | `true` | Inclui a ação de fechamento no header. Desative quando o painel exigir uma ação explícita. |
|
|
70
|
+
| `closeDisabled` | `boolean` | `false` | Mantém a ação visível e impede fechar enquanto o painel está bloqueado. |
|
|
71
|
+
| `closeSize` | `"xs" \| "sm"` | `"sm"` | Define o tamanho da ação circular de fechar. |
|
|
69
72
|
|
|
70
73
|
## Propriedades de DrawerBody
|
|
71
74
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
Valor ausente tem uma representação padrão.
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
Valor ausente tem uma representação padrão. Nas células de tabela e nos pares de `DetailField`
|
|
2
|
+
aparece o travessão, enquanto a leitura assistiva recebe “Não informado”. As colunas de
|
|
3
|
+
`ActionList` e o `DetailField` já fazem isso sozinhos; um renderer customizado reutiliza a
|
|
4
|
+
primitiva em vez de repetir a condicional.
|
|
5
5
|
|
|
6
6
|
```tsx preview
|
|
7
7
|
<div className="flex items-center gap-6 text-sm">
|
|
@@ -55,7 +55,7 @@ descrição; `FieldTitle` nomeia o campo quando o texto não puder ser um `<labe
|
|
|
55
55
|
|
|
56
56
|
| Propriedade | Tipo | Padrão | Descrição |
|
|
57
57
|
|---|---|---|---|
|
|
58
|
-
| `orientation` | `'vertical' \| 'horizontal' \| 'responsive'` | `'vertical'` | Direção do campo
|
|
58
|
+
| `orientation` | `'vertical' \| 'horizontal' \| 'responsive'` | `'vertical'` | Direção fixa do campo. `responsive` permanece como alias compatível de `horizontal`. |
|
|
59
59
|
|
|
60
60
|
## Propriedades de FieldLegend
|
|
61
61
|
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
A composição completa usa `ItemMedia` à esquerda, `ItemHeader` (`ItemTitle` +
|
|
4
4
|
`ItemDescription`) no meio e `ItemActions` à direita. Ícone e imagem mantêm uma moldura quadrada
|
|
5
5
|
alinhada ao topo, mesmo quando a descrição ocupa mais linhas. `variant="outline"` desenha a borda.
|
|
6
|
+
O tamanho padrão usa corpo produtivo, `0.75rem` de padding e `0.75rem` entre regiões. `size="sm"`
|
|
7
|
+
mantém o alinhamento horizontal e reduz o padding vertical para `0.5rem` em listas densas.
|
|
6
8
|
|
|
7
9
|
```tsx preview col
|
|
8
10
|
<Item variant="outline">
|
|
@@ -185,7 +185,7 @@ estado na própria seção com `DataState`, `ActionView`, `ActionList` ou `Alert
|
|
|
185
185
|
| Propriedade | Tipo | Padrão | Descrição |
|
|
186
186
|
| ----------- | ----------- | ----------- | ----------------------------------------------------------------------------------------------- |
|
|
187
187
|
| `title` | `ReactNode` | | O h1 da página. |
|
|
188
|
-
| `actions` | `ReactNode` | | Ações contextuais no extremo oposto do cabeçalho
|
|
188
|
+
| `actions` | `ReactNode` | | Ações contextuais no extremo oposto do cabeçalho. |
|
|
189
189
|
| `className` | `string` | `max-w-7xl` | Classes do container para substituir o teto padrão de `80rem`. |
|
|
190
190
|
| `children` | `ReactNode` | | O body da página — espaçamento e diagramação são seus. |
|
|
191
191
|
|
|
@@ -5,8 +5,8 @@ cabeçalho, body e rodapé.
|
|
|
5
5
|
Dialog e Drawer mantêm no cabeçalho a ordem horizontal de navegação ou retorno, título e ações.
|
|
6
6
|
O retorno aparece em uma surface modal somente
|
|
7
7
|
quando ela foi aberta sobre outro Dialog ou Drawer; a Page ao fundo sustenta o modal, mas não cria
|
|
8
|
-
uma etapa de navegação. Formulários modais
|
|
9
|
-
|
|
8
|
+
uma etapa de navegação. Formulários modais dimensionam o footer pelo conteúdo, com `Cancelar` em
|
|
9
|
+
`ghost` e a ação principal sólida.
|
|
10
10
|
Em Page hospedada por `PageShell`, a barra concentra navegação e ações globais. Uma Presentation
|
|
11
11
|
de listagem materializa título e comandos de header no `Content` que envolve o `ActionList`; assim
|
|
12
12
|
a criação permanece próxima da coleção. Esses comandos usam `default`. Nas demais Presentations, o
|
|
@@ -4,12 +4,15 @@ Use `Tabs` para alternar painéis relacionados no mesmo contexto. O `value` de c
|
|
|
4
4
|
corresponde ao `TabsContent` que ele abre. `defaultValue` define a aba inicial no modo não
|
|
5
5
|
controlado.
|
|
6
6
|
|
|
7
|
+
`icon` posiciona um ícone decorativo antes do rótulo e normaliza SVGs para `1rem`. Quando a aba
|
|
8
|
+
mostrar somente o ícone, declare `aria-label` para preservar seu nome acessível.
|
|
9
|
+
|
|
7
10
|
```tsx preview col
|
|
8
11
|
<Tabs defaultValue="sessions">
|
|
9
12
|
<TabsList>
|
|
10
|
-
<TabsTrigger value="overview">Visão geral</TabsTrigger>
|
|
11
|
-
<TabsTrigger value="sessions">Sessões</TabsTrigger>
|
|
12
|
-
<TabsTrigger value="skills">Skills</TabsTrigger>
|
|
13
|
+
<TabsTrigger value="overview" icon={<LayoutDashboard />}>Visão geral</TabsTrigger>
|
|
14
|
+
<TabsTrigger value="sessions" icon={<History />}>Sessões</TabsTrigger>
|
|
15
|
+
<TabsTrigger value="skills" icon={<Settings />}>Skills</TabsTrigger>
|
|
13
16
|
</TabsList>
|
|
14
17
|
<TabsContent value="overview">
|
|
15
18
|
<p className="text-sm text-muted-foreground">Resumo do workspace Empresa X.</p>
|
|
@@ -25,8 +28,14 @@ controlado.
|
|
|
25
28
|
|
|
26
29
|
## Variante line
|
|
27
30
|
|
|
28
|
-
Use `variant="line"` em `TabsList` quando a
|
|
29
|
-
cabeçalho.
|
|
31
|
+
Use `variant="line"` em `TabsList` quando a navegação precisar de uma linha-base, como em um
|
|
32
|
+
cabeçalho. Na orientação horizontal, a lista ocupa toda a largura disponível e a aba ativa é
|
|
33
|
+
marcada sobre essa linha, em vez de usar uma superfície preenchida. Dentro de `DrawerBody`, a
|
|
34
|
+
linha atravessa o gutter até as bordas do drawer sem deslocar os rótulos. O mesmo comportamento
|
|
35
|
+
vale para `DialogBody`.
|
|
36
|
+
Os itens removem o padding horizontal, usam `0.75rem` de padding vertical e preservam `1.25rem`
|
|
37
|
+
de espaço entre si. Nessa orientação, a altura é definida pelo conteúdo para que o padding
|
|
38
|
+
vertical amplie a área interativa.
|
|
30
39
|
|
|
31
40
|
```tsx preview col
|
|
32
41
|
<Tabs defaultValue="agents">
|
|
@@ -99,10 +108,11 @@ lateral do gatilho.
|
|
|
99
108
|
|
|
100
109
|
| Propriedade | Tipo | Padrão | Descrição |
|
|
101
110
|
|---|---|---|---|
|
|
102
|
-
| `variant` | `'default' \| 'line'` | `'default'` | `default` usa uma superfície preenchida; `line` marca a aba ativa
|
|
111
|
+
| `variant` | `'default' \| 'line'` | `'default'` | `default` usa uma superfície preenchida; `line` cria uma linha-base horizontal de largura total e marca nela a aba ativa. |
|
|
103
112
|
|
|
104
113
|
## Propriedades de TabsTrigger e TabsContent
|
|
105
114
|
|
|
106
115
|
| Propriedade | Tipo | Padrão | Descrição |
|
|
107
116
|
|---|---|---|---|
|
|
108
117
|
| `value` | `string` | | Identificador que associa o gatilho ao painel correspondente. |
|
|
118
|
+
| `icon` | `ReactNode` | | Ícone decorativo antes do rótulo; SVGs usam `1rem`. |
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
Use `toast` para informar o resultado temporário de uma ação sem interromper o fluxo. Cada tipo usa o
|
|
4
4
|
ícone correspondente do Opus. Escreva o título como rótulo, sem ponto final, e a descrição como uma
|
|
5
5
|
frase. A moldura do ícone permanece quadrada e alinhada à primeira linha mesmo quando a descrição
|
|
6
|
-
ocupa várias linhas.
|
|
6
|
+
ocupa várias linhas. Corpo e padding seguem os mesmos `0.875rem` e `0.75rem` do Alert; os defaults
|
|
7
|
+
internos do Sonner não definem a densidade da casa.
|
|
7
8
|
|
|
8
9
|
```tsx preview
|
|
9
10
|
<Button variant="outline" onClick={() => toast.success('Workspace criado')}>Sucesso</Button>
|