@softize/opus 18.0.0 → 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.
Files changed (63) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/docs/adr/0010-page-header-owns-page-chrome.md +2 -2
  3. package/docs/adr/0015-action-size-follows-interaction-density.md +1 -1
  4. package/docs/adr/0016-productive-surfaces-use-compact-density.md +62 -0
  5. package/docs/adr/0017-ui-assumes-a-fixed-desktop-layout.md +54 -0
  6. package/docs/relative-unit-scale.md +9 -2
  7. package/package.json +1 -1
  8. package/registry/skills/build-opus-ui/references/evaluations.md +3 -1
  9. package/registry/skills/build-opus-ui/references/ui-patterns.md +10 -4
  10. package/src/core/runtime.ts +7 -1
  11. package/src/core/types.ts +12 -3
  12. package/src/mcp/index.ts +4 -1
  13. package/src/ui/components/patterns/action-list-dialog.tsx +2 -2
  14. package/src/ui/components/patterns/content-header.tsx +1 -1
  15. package/src/ui/components/patterns/form-dialog.tsx +7 -2
  16. package/src/ui/components/patterns/list.tsx +239 -47
  17. package/src/ui/components/patterns/page.tsx +1 -1
  18. package/src/ui/components/patterns/presentation.tsx +8 -6
  19. package/src/ui/components/patterns/state-surface.tsx +2 -2
  20. package/src/ui/components/patterns/surface-header.tsx +4 -4
  21. package/src/ui/components/patterns/trigger.tsx +1 -1
  22. package/src/ui/components/primitives/alert.tsx +2 -2
  23. package/src/ui/components/primitives/breadcrumb.tsx +1 -1
  24. package/src/ui/components/primitives/button-group.tsx +1 -1
  25. package/src/ui/components/primitives/button.tsx +3 -3
  26. package/src/ui/components/primitives/calendar.tsx +1 -1
  27. package/src/ui/components/primitives/close-button.tsx +40 -0
  28. package/src/ui/components/primitives/detail.tsx +66 -28
  29. package/src/ui/components/primitives/dialog.tsx +36 -21
  30. package/src/ui/components/primitives/drawer.tsx +26 -19
  31. package/src/ui/components/primitives/empty-value.tsx +3 -3
  32. package/src/ui/components/primitives/empty.tsx +1 -1
  33. package/src/ui/components/primitives/field.tsx +12 -12
  34. package/src/ui/components/primitives/icon-picker.tsx +1 -1
  35. package/src/ui/components/primitives/input-group.tsx +1 -1
  36. package/src/ui/components/primitives/input.tsx +2 -2
  37. package/src/ui/components/primitives/item.tsx +5 -5
  38. package/src/ui/components/primitives/pagination.tsx +4 -4
  39. package/src/ui/components/primitives/select.tsx +2 -2
  40. package/src/ui/components/primitives/table.tsx +26 -17
  41. package/src/ui/components/primitives/tabs.tsx +80 -23
  42. package/src/ui/components/primitives/textarea.tsx +1 -1
  43. package/src/ui/components/primitives/toggle-group.tsx +9 -2
  44. package/src/ui/docs/content/action-form.md +19 -0
  45. package/src/ui/docs/content/action-list-dialog.md +1 -1
  46. package/src/ui/docs/content/action-list.md +7 -1
  47. package/src/ui/docs/content/action-trigger.md +2 -2
  48. package/src/ui/docs/content/alert.md +2 -0
  49. package/src/ui/docs/content/button.md +3 -2
  50. package/src/ui/docs/content/customization.md +11 -1
  51. package/src/ui/docs/content/detail.md +9 -8
  52. package/src/ui/docs/content/dialog.md +12 -5
  53. package/src/ui/docs/content/drawer.md +6 -3
  54. package/src/ui/docs/content/empty-value.md +4 -4
  55. package/src/ui/docs/content/field.md +1 -1
  56. package/src/ui/docs/content/item.md +2 -0
  57. package/src/ui/docs/content/page.md +1 -1
  58. package/src/ui/docs/content/presentation.md +2 -2
  59. package/src/ui/docs/content/tabs.md +16 -6
  60. package/src/ui/docs/content/toast.md +2 -1
  61. package/src/ui/docs/content/tokens.md +45 -2
  62. package/src/ui/react.tsx +1 -0
  63. 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?: 'plain' | 'framed'
10
+ variant?: TableVariant
8
11
  }
9
12
 
10
13
  function Table({ className, variant = 'plain', ...props }: TableProps) {
11
14
  return (
12
- <div
13
- data-slot="table-container"
14
- data-variant={variant}
15
- className={cn(
16
- "relative w-full overflow-x-auto",
17
- variant === 'framed' &&
18
- 'rounded-lg border border-border [&_[data-slot=table-header]]:bg-muted/20',
19
- )}
20
- >
21
- <table
22
- data-slot="table"
23
- className={cn("w-full caption-bottom text-sm", className)}
24
- {...props}
25
- />
26
- </div>
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("[&_tr]:border-b", className)}
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
- <TabsSizeContext.Provider value={size}>
23
- <TabsPrimitive.Root
24
- data-slot="tabs"
25
- data-orientation={orientation}
26
- data-size={size}
27
- orientation={orientation}
28
- className={cn(
29
- "group/tabs flex gap-2 data-[orientation=horizontal]:flex-col",
30
- className
31
- )}
32
- {...props}
33
- />
34
- </TabsSizeContext.Provider>
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-1 bg-transparent",
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
- <TabsPrimitive.List
66
- data-slot="tabs-list"
67
- data-variant={variant}
68
- data-size={size}
69
- className={cn(tabsListVariants({ variant }), controlHeight[size], className)}
70
- {...props}
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-transparent px-3 py-2 text-base 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 md:text-sm dark:bg-input/30 dark:aria-invalid:ring-context-danger/40",
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
- }: React.ComponentProps<typeof ToggleGroupPrimitive.Item> &
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` | `sm:max-w-3xl` | Largura do DialogContent. |
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?, advanced?, depends?, … } }` — a toolbar. `advanced` vai para o modal; `depends` desabilita/cascateia; options `kind: 'lookup'` busca em uma action. Valor aplicado entra no input com o mesmo nome. |
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. |
@@ -39,7 +39,7 @@ confirm: {
39
39
 
40
40
  Com `icon`, o botão exibe somente o ícone no quadrado `icon-xs` da escala (1.5rem, a ação que mora
41
41
  dentro de uma linha ou card) e usa `label`, ou `action.label`, no tooltip e no nome acessível. Uma
42
- composição que peça mais presença, como a barra do `PageHeader`, declara `size="icon-sm"`. O clique
42
+ composição que peça mais presença, como a barra do `PageHeader`, declara `size="icon"`. O clique
43
43
  não aciona o item clicável ao redor. `itemLabel` identifica o registro na mensagem de confirmação.
44
44
 
45
45
  ```tsx
@@ -79,7 +79,7 @@ um atalho, um arrastar, um item de menu.
79
79
  | `action` | `SimpleContract<TInput, TData>` | | A SimpleAction do Opus — label, messages e confirm vêm do contrato. |
80
80
  | `input` | `TInput` | | O que a action recebe — geralmente { id }. |
81
81
  | `label` | `string` | `action.label` | Sobrepõe o texto do botão. |
82
- | `context / variant / size` | `do Button` | gatilho: `primary`/`solid`; `danger` quando a action é `destructive`; no modo ícone, `ghost` e `neutral` (ou `danger` se destrutiva) / `default` | Visual do gatilho; `context` explícito vence. O botão de confirmar é sempre `solid`: `danger` quando a action é `destructive`, senão a prop `context` do gatilho (ou `primary`). |
82
+ | `context / variant / size` | `do Button` | gatilho: `primary`/`solid`; `danger` quando a action é `destructive`; no modo ícone, `ghost` e `neutral` (ou `danger` se destrutiva) / `icon-xs` | Visual do gatilho; `context` explícito vence. O botão de confirmar é sempre `solid`: `danger` quando a action é `destructive`, senão a prop `context` do gatilho (ou `primary`). |
83
83
  | `confirm` | `{ title, description?, actionLabel?, cancelLabel? }` | | Confirmação via prop — sobrepõe o ConfirmSpec do contrato. |
84
84
  | `onSuccess` | `(data: TData) => void` | | Pós-sucesso (cache já invalidado pelo action.invalidates). |
85
85
  | `icon` | `React.ReactNode` | | Torna o botão icon-only: rótulo no tooltip e no `aria-label`, clique que não vaza para o item. |
@@ -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>
@@ -35,7 +36,7 @@ há texto visível. O glifo dentro do controle acompanha o tamanho (0.875rem em
35
36
  Escolha o tamanho pela região, não pela importância visual: `variant` e `context` resolvem a
36
37
  hierarquia da ação. Headers de Page e footers de Dialog/Drawer usam `default`; ações operacionais
37
38
  de seção, toolbar e coleção usam `sm`; ações dentro de linha ou célula usam `xs` ou `icon-xs`.
38
- Controles de chrome, como voltar e fechar, usam `icon-sm`. Assim a mesma decisão mantém a mesma
39
+ Controles de chrome, como voltar e fechar, usam `icon`. Assim a mesma decisão mantém a mesma
39
40
  altura mesmo quando uma superfície troca uma ação secundária por uma primária.
40
41
 
41
42
  ```tsx preview
@@ -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="sm:max-w-2xl" />
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="sm:col-span-2"
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 “Não informado”; `empty` troca o
29
- significado no domínio. `0` e `false` seguem como valores. Ver `EmptyValue`.
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`. Quando a superfície exigir
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="sm:col-span-2"
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` | Colunas responsivas ou distribuição automática por largura mínima. |
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”` | O que a ausência significa neste campo: um rótulo ou um próprio. |
77
+ | `empty` | `ReactNode` | `“Não informado”` | Uma string significado acessível ao travessão; um 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 semântica, raio `xl` e elevação para permanecer distinta da página.
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" distribution="equal">
36
+ <ButtonGroup mode="spaced">
36
37
  <DialogClose asChild>
37
- <Button variant="outline">Cancelar</Button>
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 `ButtonGroup` divide o espaço entre decisões equivalentes no rodapé. O `DialogFooter` organiza
48
- a faixa, mas não decide a distribuição nem a aparência dos botões.
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" distribution="equal">
46
+ <ButtonGroup mode="spaced">
46
47
  <DrawerClose asChild>
47
- <Button variant="outline">Cancelar</Button>
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. Em célula compacta de tabela aparece o travessão,
2
- e a leitura assistiva recebe “Não informado”; em texto corrido, como no `DetailField`, aparece o
3
- próprio rótulo. As colunas de `ActionList` e o `DetailField` já fazem isso sozinhos; um renderer
4
- customizado reutiliza a primitiva em vez de repetir a condicional.
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; `responsive` se torna horizontal a partir do container médio. |
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; em telas estreitas, ficam abaixo do contexto. |
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 encerram o footer dividido igualmente entre `Cancelar`
9
- em `outline` e a ação principal.
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