@softize/opus 18.4.0 → 18.5.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.
@@ -1,21 +1,41 @@
1
- import { useMemo } from "react"
1
+ import { createContext, useContext, useMemo } from "react"
2
2
  import { cva, type VariantProps } from "class-variance-authority"
3
3
 
4
4
  import { cn } from '../../lib/cn.ts'
5
5
  import { Label } from './label.tsx'
6
6
  import { Separator } from './separator.tsx'
7
7
 
8
- function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
8
+ export type FieldSetVariant = "plain" | "framed"
9
+
10
+ export interface FieldSetProps extends React.ComponentProps<"fieldset"> {
11
+ /** `framed` envolve o conjunto com a moldura canônica e integra a legenda à borda. */
12
+ variant?: FieldSetVariant
13
+ }
14
+
15
+ const FieldSetVariantContext = createContext<FieldSetVariant>("plain")
16
+
17
+ function FieldSet({
18
+ className,
19
+ children,
20
+ variant = "plain",
21
+ ...props
22
+ }: FieldSetProps) {
9
23
  return (
10
- <fieldset
11
- data-slot="field-set"
12
- className={cn(
13
- "flex flex-col gap-6",
14
- "has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
15
- className
16
- )}
17
- {...props}
18
- />
24
+ <FieldSetVariantContext.Provider value={variant}>
25
+ <fieldset
26
+ data-slot="field-set"
27
+ data-variant={variant}
28
+ className={cn(
29
+ "flex flex-col gap-6",
30
+ "has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
31
+ variant === "framed" && "rounded-lg border border-border px-4 pb-4",
32
+ className
33
+ )}
34
+ {...props}
35
+ >
36
+ {children}
37
+ </fieldset>
38
+ </FieldSetVariantContext.Provider>
19
39
  )
20
40
  }
21
41
 
@@ -29,6 +49,8 @@ function FieldLegend({
29
49
  variant?: "legend" | "label" | "section"
30
50
  icon?: React.ReactNode
31
51
  }) {
52
+ const fieldSetVariant = useContext(FieldSetVariantContext)
53
+
32
54
  return (
33
55
  <legend
34
56
  data-slot="field-legend"
@@ -38,7 +60,10 @@ function FieldLegend({
38
60
  icon !== undefined && "flex items-center gap-2",
39
61
  "data-[variant=legend]:text-base",
40
62
  "data-[variant=label]:text-sm",
41
- "data-[variant=section]:w-full data-[variant=section]:border-b data-[variant=section]:border-border/70 data-[variant=section]:pb-2 data-[variant=section]:text-sm data-[variant=section]:font-semibold",
63
+ fieldSetVariant === "plain" &&
64
+ "data-[variant=section]:w-full data-[variant=section]:border-b data-[variant=section]:border-border/70 data-[variant=section]:pb-2",
65
+ "data-[variant=section]:text-sm data-[variant=section]:font-semibold",
66
+ fieldSetVariant === "framed" && "-ml-2 w-auto px-2",
42
67
  className
43
68
  )}
44
69
  {...props}
@@ -47,10 +72,7 @@ function FieldLegend({
47
72
  <span
48
73
  data-slot="field-legend-icon"
49
74
  aria-hidden="true"
50
- className={cn(
51
- "inline-flex shrink-0 items-center justify-center text-muted-foreground [&_svg]:size-4",
52
- variant === "section" && "size-7 rounded-md bg-muted",
53
- )}
75
+ className="inline-flex shrink-0 items-center justify-center text-muted-foreground [&_svg]:size-4"
54
76
  >
55
77
  {icon}
56
78
  </span>
@@ -0,0 +1,91 @@
1
+ import { cva } from 'class-variance-authority'
2
+
3
+ import type { UiContext } from '../../../core/ui-context.ts'
4
+ import { cn } from '../../lib/cn.ts'
5
+ import {
6
+ semanticOutlineClasses,
7
+ semanticSubtleClasses,
8
+ } from '../../lib/semantic-context.ts'
9
+ import { controlGlyphClass, type ControlSize } from './control.ts'
10
+
11
+ export type IconTileContext = UiContext
12
+ export type IconTileSize = ControlSize
13
+ export type IconTileVariant = 'subtle' | 'outline'
14
+
15
+ const sizeClasses: Record<IconTileSize, string> = {
16
+ xs: 'size-6 rounded-md',
17
+ sm: 'size-8 rounded-md',
18
+ default: 'size-9 rounded-md',
19
+ lg: 'size-10 rounded-lg',
20
+ }
21
+
22
+ const iconTileSizeVariants = cva(
23
+ 'inline-flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0',
24
+ {
25
+ variants: {
26
+ size: sizeClasses,
27
+ },
28
+ defaultVariants: {
29
+ size: 'default',
30
+ },
31
+ },
32
+ )
33
+
34
+ export interface IconTileStyleOptions {
35
+ context?: IconTileContext | null
36
+ variant?: IconTileVariant | null
37
+ size?: IconTileSize | null
38
+ }
39
+
40
+ export interface IconTileProps
41
+ extends React.ComponentProps<'span'>,
42
+ IconTileStyleOptions {}
43
+
44
+ export function resolveIconTileStyle({
45
+ context,
46
+ variant,
47
+ size,
48
+ }: IconTileStyleOptions = {}): {
49
+ context: IconTileContext
50
+ variant: IconTileVariant
51
+ size: IconTileSize
52
+ } {
53
+ return {
54
+ context: context ?? 'neutral',
55
+ variant: variant ?? 'subtle',
56
+ size: size ?? 'default',
57
+ }
58
+ }
59
+
60
+ export function iconTileVariants(options: IconTileStyleOptions = {}): string {
61
+ const resolved = resolveIconTileStyle(options)
62
+ return cn(
63
+ iconTileSizeVariants({ size: resolved.size }),
64
+ resolved.variant === 'outline'
65
+ ? cn('border bg-transparent', semanticOutlineClasses[resolved.context])
66
+ : semanticSubtleClasses[resolved.context],
67
+ controlGlyphClass(resolved.size),
68
+ )
69
+ }
70
+
71
+ function IconTile({
72
+ className,
73
+ context,
74
+ variant,
75
+ size,
76
+ ...props
77
+ }: IconTileProps): React.ReactElement {
78
+ const resolved = resolveIconTileStyle({ context, variant, size })
79
+ return (
80
+ <span
81
+ data-slot="icon-tile"
82
+ data-context={resolved.context}
83
+ data-variant={resolved.variant}
84
+ data-size={resolved.size}
85
+ className={cn(iconTileVariants(resolved), className)}
86
+ {...props}
87
+ />
88
+ )
89
+ }
90
+
91
+ export { IconTile }
@@ -4,6 +4,7 @@ import { Slot } from "radix-ui";
4
4
 
5
5
  import { cn } from "../../lib/cn.ts";
6
6
  import type { ControlSize } from "./control.ts";
7
+ import { iconTileVariants } from "./icon-tile.tsx";
7
8
  import { Separator } from "./separator.tsx";
8
9
 
9
10
  const itemGroupVariants = cva(
@@ -123,7 +124,7 @@ const itemMediaVariants = cva(
123
124
  variants: {
124
125
  variant: {
125
126
  default: "bg-transparent",
126
- icon: "size-8 rounded-sm border bg-muted [&_svg:not([class*='size-'])]:size-4",
127
+ icon: iconTileVariants({ size: "sm", variant: "outline" }),
127
128
  image:
128
129
  "size-10 overflow-hidden rounded-sm [&_img]:size-full [&_img]:object-cover",
129
130
  },
@@ -40,7 +40,7 @@ function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
40
40
  <thead
41
41
  data-slot="table-header"
42
42
  className={cn(
43
- "[&_tr]:border-b",
43
+ "[&_tr]:border-b [&_tr:hover]:bg-transparent",
44
44
  variant === 'framed' && 'bg-muted/30 dark:bg-input/30',
45
45
  className,
46
46
  )}
@@ -5,6 +5,10 @@ define colunas, busca, filtros, período, ordenação e paginação; a interface
5
5
  recursos e envia o estado correspondente no input. Filtros avançados aparecem no modal “Filtros” e
6
6
  os filtros aplicados permanecem visíveis como chips removíveis.
7
7
 
8
+ A tabela derivada não cria uma moldura própria. A superfície que recebe o `ActionList` define os
9
+ limites visuais da coleção; use `Table` diretamente quando a composição precisar da variante
10
+ `framed`.
11
+
8
12
  A barra se adapta ao espaço disponível. A busca cede largura primeiro e filtros que deixam de caber
9
13
  migram para o modal; a linha só quebra quando nenhum controle restante puder ceder espaço.
10
14
  `toolbarActions` acrescenta operações ligadas ao recorte atual no fim da barra. Recarregar e
@@ -86,7 +90,11 @@ ao modal “Filtros”; `external` mantém o filtro no estado navegável e no in
86
90
  apresentação para o consumidor, como cards de status acima da lista. No modal, filtros com a mesma
87
91
  `section` formam um grupo, e `advancedFilters.columns` distribui os grupos em até três colunas.
88
92
  Use `advancedFilters.sectionIcons` quando os grupos representarem dimensões recorrentes; a chave é
89
- o mesmo rótulo de `section`, e a legenda mantém o agrupamento leve com ícone e divisor.
93
+ o mesmo rótulo de `section`, e cada grupo usa `Content as="fieldset"` para manter uma legenda
94
+ nativa. Quando os grupos precisarem de limites mais explícitos,
95
+ `advancedFilters.sectionVariant: 'framed'` envolve cada seção e pede ao `Content` o acabamento
96
+ `frameVariant="legend"`, integrando visualmente a legenda à borda sem atribuir esse design à
97
+ semântica de `fieldset`.
90
98
  Um filtro `select` pode declarar `searchable: true` quando a lista fechada for longa; a opção geral
91
99
  continua disponível e participa da busca. Campos de data avançados usam calendário. A antiga `advanced: true` continua aceita durante a
92
100
  migração e equivale a `placement: 'advanced'`.
@@ -123,9 +131,10 @@ filters: {
123
131
  input={{}}
124
132
  advancedFilters={{
125
133
  columns: 2,
134
+ sectionVariant: 'framed',
126
135
  sectionIcons: {
127
- Relacionamento: <UserRound aria-hidden="true" />,
128
- Datas: <CalendarDays aria-hidden="true" />,
136
+ Relacionamento: <IconTile size="xs"><UserRound /></IconTile>,
137
+ Datas: <IconTile size="xs"><CalendarDays /></IconTile>,
129
138
  },
130
139
  }}
131
140
  />
@@ -356,31 +365,31 @@ render(
356
365
 
357
366
  ## Propriedades de ActionFilterBar
358
367
 
359
- | Propriedade | Tipo | Padrão | Descrição |
360
- | ----------------- | ---------------------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------ |
361
- | `action` | `Pick<ListAction, 'filters' \| 'text' \| 'periods'>` | | Declara busca, filtros e períodos disponíveis. |
362
- | `state` | `ActionFilterState` | | Estado atual da barra. |
363
- | `onStateChange` | `(next) => void` | | Recebe o estado completo depois de cada alteração. |
364
- | `filterOptions` | `Record<string, SelectOption[]>` | | Fornece opções de runtime para filtros select e lookup. |
365
- | `filterDefaults` | `Record<string, string>` | | Define o valor de repouso de filtros simples e o destino da ação de limpar. |
366
- | `filterIcons` | `Record<string, ReactNode>` | | Associa um ícone leading ao controle de cada filtro. |
367
- | `advancedFilters` | `{ columns?: 1 \| 2 \| 3; sectionIcons?: Record<string, ReactNode> }` | `{ columns: 1 }` | Define as colunas do diálogo e os ícones opcionais das seções; o Opus deriva a largura correspondente. |
368
- | `periodPosition` | `'before' \| 'after' \| { afterFilter: string }` | `'before'` | Posiciona o período antes, depois ou imediatamente após um filtro inline. |
369
- | `period` | `{ basisFilter: string }` | | Reúne o período e o filtro inline indicado em um único controle. |
370
- | `onRefresh` | `() => Promise<void> \| void` | | Exibe a ação de recarregar e executa a consulta do consumidor. |
371
- | `refreshing` | `boolean` | `false` | Desabilita e anima a ação de recarregar durante a consulta. |
372
- | `controls` | `ReactNode` | | Controles auxiliares agrupados com recarregar em um `ButtonGroup` espaçado. |
373
- | `actions` | `ReactNode` | | Ações do consumidor exibidas depois do grupo de controles, com um intervalo maior. |
368
+ | Propriedade | Tipo | Padrão | Descrição |
369
+ | ----------------- | ----------------------------------------------------------------------------------------------------------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------- |
370
+ | `action` | `Pick<ListAction, 'filters' \| 'text' \| 'periods'>` | | Declara busca, filtros e períodos disponíveis. |
371
+ | `state` | `ActionFilterState` | | Estado atual da barra. |
372
+ | `onStateChange` | `(next) => void` | | Recebe o estado completo depois de cada alteração. |
373
+ | `filterOptions` | `Record<string, SelectOption[]>` | | Fornece opções de runtime para filtros select e lookup. |
374
+ | `filterDefaults` | `Record<string, string>` | | Define o valor de repouso de filtros simples e o destino da ação de limpar. |
375
+ | `filterIcons` | `Record<string, ReactNode>` | | Associa um ícone leading ao controle de cada filtro. |
376
+ | `advancedFilters` | `{ columns?: 1 \| 2 \| 3; sectionIcons?: Record<string, ReactNode>; sectionVariant?: 'plain' \| 'framed' }` | `{ columns: 1, sectionVariant: 'plain' }` | Define as colunas, os ícones e o tratamento das seções; o Opus deriva a largura correspondente. |
377
+ | `periodPosition` | `'before' \| 'after' \| { afterFilter: string }` | `'before'` | Posiciona o período antes, depois ou imediatamente após um filtro inline. |
378
+ | `period` | `{ basisFilter: string }` | | Reúne o período e o filtro inline indicado em um único controle. |
379
+ | `onRefresh` | `() => Promise<void> \| void` | | Exibe a ação de recarregar e executa a consulta do consumidor. |
380
+ | `refreshing` | `boolean` | `false` | Desabilita e anima a ação de recarregar durante a consulta. |
381
+ | `controls` | `ReactNode` | | Controles auxiliares agrupados com recarregar em um `ButtonGroup` espaçado. |
382
+ | `actions` | `ReactNode` | | Ações do consumidor exibidas depois do grupo de controles, com um intervalo maior. |
374
383
 
375
384
  ## Declaração na ListAction
376
385
 
377
- | Chave | O que declara |
378
- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
379
- | `columns` | `{ key, label, type?, sortable?, fit?, hidden?, dateFormat?, dictionary?, empty? }` — a tabela. `hidden` nasce oculta e pode ser exibida pelo seletor de colunas; `dictionary` nomeia o dicionário do provider; `empty` dá o significado da ausência. |
386
+ | Chave | O que declara |
387
+ | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
388
+ | `columns` | `{ key, label, type?, sortable?, fit?, hidden?, dateFormat?, dictionary?, empty? }` — a tabela. `hidden` nasce oculta e pode ser exibida pelo seletor de colunas; `dictionary` nomeia o dicionário do provider; `empty` dá o significado da ausência. |
380
389
  | `filters` | `{ [nome]: { label, type, options?, multiple?, searchable?, allowEmpty?, 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. `searchable` habilita busca local em `select`; `allowEmpty: false` omite a opção vazia. `depends` desabilita/cascateia; options `kind: 'lookup'` busca em uma action. Valor aplicado entra no input com o mesmo nome. |
381
- | `text` | `{ fields }` — liga a busca; convenção: param `q` no input. Ela aparece depois dos filtros inline e imediatamente antes do acesso aos filtros avançados. A busca participa da contagem de controles inline: com mais de um controle, a barra exibe as labels; quando ela é o único recorte, sua label é omitida. |
382
- | `sort` | `{ fields, default }` — ordenação inicial; header ordenável escreve `sort: 'chave:dir'`. |
383
- | `periods` | `{ value, label, default? }[]` — o controle de período (presets + Personalizado com calendário); `default: true` marca o preset inicial, senão vale o primeiro; materializa em `from`/`to` no input. |
390
+ | `text` | `{ fields }` — liga a busca; convenção: param `q` no input. Ela aparece depois dos filtros inline e imediatamente antes do acesso aos filtros avançados. A busca participa da contagem de controles inline: com mais de um controle, a barra exibe as labels; quando ela é o único recorte, sua label é omitida. |
391
+ | `sort` | `{ fields, default }` — ordenação inicial; header ordenável escreve `sort: 'chave:dir'`. |
392
+ | `periods` | `{ value, label, default? }[]` — o controle de período (presets + Personalizado com calendário); `default: true` marca o preset inicial, senão vale o primeiro; materializa em `from`/`to` no input. |
384
393
 
385
394
  ## useListAction
386
395
 
@@ -392,31 +401,31 @@ chamar a action.
392
401
 
393
402
  ## Propriedades de ActionList
394
403
 
395
- | Propriedade | Tipo | Padrão | Descrição |
396
- | ----------------------- | --------------------------------------- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
397
- | `action` | `ListAction<TInput, TItem>` | | A ListAction do Opus (kind list, output = item). A paginação envia `limit`/`page` e usa o `total` devolvido. |
398
- | `input` | `TInput` | | O ESCOPO BASE (ex.: { workspaceId }) — a toolbar soma por cima, nunca sobrescreve. |
399
- | `layout` | `'flow' \| 'fill'` | `'flow'` | Em `fill`, ocupa a altura disponível e mantém a rolagem dentro da região de resultados. |
400
- | `cells` | `Record<string, (item) => ReactNode>` | | Células custom por cima das colunas do contrato (chave = column.key). |
401
- | `filterOptions` | `Record<string, SelectOption[]>` | | Opções de runtime para os filtros select/lookup (chave = nome do filtro). |
402
- | `filterDefaults` | `Record<string, string>` | | Valores de repouso aplicados à consulta; o `X` só aparece quando o filtro diverge deles e retorna a eles. |
403
- | `filterIcons` | `Record<string, ReactNode>` | | Ícones leading dos filtros, indexados pelo nome declarado no contrato. |
404
- | `advancedFilters` | `{ columns?: 1 \| 2 \| 3; sectionIcons?: Record<string, ReactNode> }` | `{ columns: 1 }` | Define as colunas e os ícones opcionais dos grupos de filtros avançados; o Opus deriva a largura correspondente do diálogo. |
405
- | `periodPosition` | `'before' \| 'after' \| { afterFilter: string }` | `'before'` | Posiciona o período antes, depois ou imediatamente após um filtro inline. |
406
- | `period` | `{ basisFilter: string }` | | Reúne o período e o filtro inline indicado em um único controle. |
407
- | `views` | `Record<string, ActionListView<TItem>>` | | Visualizações alternativas `{ label, icon?, render }`; o seletor alterna entre a tabela e estas. |
408
- | `beforeContent` | `ReactNode` | | Conteúdo entre a toolbar e o corpo, como um resumo que reage ao recorte atual. |
409
- | `columns` | `ActionListColumn<TItem>[]` | | Tabela EXPLÍCITA — sobrepõe as colunas do contrato (escape hatch). |
410
- | `children` | `(items, refetch) => ReactNode` | | Modo composição: layout livre; a toolbar segue. Tem precedência sobre columns. |
411
- | `batch` | `ActionListBatchAction[]` | | Ações em lote: `{ label, can?, run, confirm?: { title, message? }, destructive? }` — liga a multi-seleção. |
412
- | `rowId` | `(item) => string` | `item.id` | Identidade da linha para seleção. |
413
- | `pageSize` | `number` | `50` | Itens por página padrão (vira `limit`/`page` no input; handler devolve `total`). O usuário troca no popover de exibição. |
414
- | `state / onStateChange` | `ActionListState / (next) => void` | interno | Estado da toolbar controlado — para quem embala sincronizar com a URL. |
415
- | `empty` | `(items) => boolean` | `items.length === 0` | Sobrepõe o vazio derivado, por exemplo quando um formulário inline aberto conta como conteúdo. |
416
- | `loading` | `boolean` | | Carga extra agregada à da consulta, como uma query irmã de que os `children` dependem. |
417
- | `emptyMessage` | `string` | `'Nenhum resultado.'` | Frase do estado vazio (o `Empty` do `DataState`). |
418
- | `errorMessage` | `string` | `'Não foi possível carregar.'` | Título do aviso de erro; a ação de tentar de novo refaz a consulta. |
419
- | `retryLabel` | `string` | `'Tentar de novo'` | Nome acessível e tooltip da ação de recuperação. |
420
- | `onRowClick` | `(item: TItem) => void` | | Clique na linha (ex.: navegar para o detalhe) — só na tabela. |
421
- | `toolbarActions` | `ReactNode` | | Operações ligadas ao recorte atual, no fim da barra e separadas do grupo de controles. |
422
- | `rowActions` | `(item: TItem) => ReactNode` | | Ações por linha (coluna final, à direita). A partir de duas, entram num grupo com intervalo compacto; uma só fica solta, sem grupo. Cliques ali não disparam o `onRowClick`. |
404
+ | Propriedade | Tipo | Padrão | Descrição |
405
+ | ----------------------- | ----------------------------------------------------------------------------------------------------------- | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
406
+ | `action` | `ListAction<TInput, TItem>` | | A ListAction do Opus (kind list, output = item). A paginação envia `limit`/`page` e usa o `total` devolvido. |
407
+ | `input` | `TInput` | | O ESCOPO BASE (ex.: { workspaceId }) — a toolbar soma por cima, nunca sobrescreve. |
408
+ | `layout` | `'flow' \| 'fill'` | `'flow'` | Em `fill`, ocupa a altura disponível e mantém a rolagem dentro da região de resultados. |
409
+ | `cells` | `Record<string, (item) => ReactNode>` | | Células custom por cima das colunas do contrato (chave = column.key). |
410
+ | `filterOptions` | `Record<string, SelectOption[]>` | | Opções de runtime para os filtros select/lookup (chave = nome do filtro). |
411
+ | `filterDefaults` | `Record<string, string>` | | Valores de repouso aplicados à consulta; o `X` só aparece quando o filtro diverge deles e retorna a eles. |
412
+ | `filterIcons` | `Record<string, ReactNode>` | | Ícones leading dos filtros, indexados pelo nome declarado no contrato. |
413
+ | `advancedFilters` | `{ columns?: 1 \| 2 \| 3; sectionIcons?: Record<string, ReactNode>; sectionVariant?: 'plain' \| 'framed' }` | `{ columns: 1, sectionVariant: 'plain' }` | Define as colunas, os ícones e o tratamento dos grupos; o Opus deriva a largura correspondente do diálogo. |
414
+ | `periodPosition` | `'before' \| 'after' \| { afterFilter: string }` | `'before'` | Posiciona o período antes, depois ou imediatamente após um filtro inline. |
415
+ | `period` | `{ basisFilter: string }` | | Reúne o período e o filtro inline indicado em um único controle. |
416
+ | `views` | `Record<string, ActionListView<TItem>>` | | Visualizações alternativas `{ label, icon?, render }`; o seletor alterna entre a tabela e estas. |
417
+ | `beforeContent` | `ReactNode` | | Conteúdo entre a toolbar e o corpo, como um resumo que reage ao recorte atual. |
418
+ | `columns` | `ActionListColumn<TItem>[]` | | Tabela EXPLÍCITA — sobrepõe as colunas do contrato (escape hatch). |
419
+ | `children` | `(items, refetch) => ReactNode` | | Modo composição: layout livre; a toolbar segue. Tem precedência sobre columns. |
420
+ | `batch` | `ActionListBatchAction[]` | | Ações em lote: `{ label, can?, run, confirm?: { title, message? }, destructive? }` — liga a multi-seleção. |
421
+ | `rowId` | `(item) => string` | `item.id` | Identidade da linha para seleção. |
422
+ | `pageSize` | `number` | `50` | Itens por página padrão (vira `limit`/`page` no input; handler devolve `total`). O usuário troca no popover de exibição. |
423
+ | `state / onStateChange` | `ActionListState / (next) => void` | interno | Estado da toolbar controlado — para quem embala sincronizar com a URL. |
424
+ | `empty` | `(items) => boolean` | `items.length === 0` | Sobrepõe o vazio derivado, por exemplo quando um formulário inline aberto conta como conteúdo. |
425
+ | `loading` | `boolean` | | Carga extra agregada à da consulta, como uma query irmã de que os `children` dependem. |
426
+ | `emptyMessage` | `string` | `'Nenhum resultado.'` | Frase do estado vazio (o `Empty` do `DataState`). |
427
+ | `errorMessage` | `string` | `'Não foi possível carregar.'` | Título do aviso de erro; a ação de tentar de novo refaz a consulta. |
428
+ | `retryLabel` | `string` | `'Tentar de novo'` | Nome acessível e tooltip da ação de recuperação. |
429
+ | `onRowClick` | `(item: TItem) => void` | | Clique na linha (ex.: navegar para o detalhe) — só na tabela. |
430
+ | `toolbarActions` | `ReactNode` | | Operações ligadas ao recorte atual, no fim da barra e separadas do grupo de controles. |
431
+ | `rowActions` | `(item: TItem) => ReactNode` | | Ações por linha (coluna final, à direita). A partir de duas, entram num grupo com intervalo compacto; uma só fica solta, sem grupo. Cliques ali não disparam o `onRowClick`. |
@@ -20,6 +20,79 @@ render(
20
20
  );
21
21
  ```
22
22
 
23
+ ## Região com moldura
24
+
25
+ Use `framed` quando título e corpo precisarem formar uma única região delimitada. A moldura pertence
26
+ ao `Content`; componentes internos, como `DetailGroup`, continuam responsáveis somente pelo layout
27
+ e pela semântica dos próprios dados.
28
+
29
+ ```tsx preview col
30
+ render(
31
+ <Content
32
+ framed
33
+ title={
34
+ <span className="flex items-center gap-2">
35
+ <UserRound
36
+ className="size-4 text-muted-foreground"
37
+ aria-hidden="true"
38
+ />
39
+ Cliente
40
+ </span>
41
+ }
42
+ >
43
+ <DetailGroup columns={2}>
44
+ <DetailField label="Nome" value="Carla Souza" />
45
+ <DetailField label="Telefone" value="+55 11 97333-4455" />
46
+ </DetailGroup>
47
+ </Content>,
48
+ );
49
+ ```
50
+
51
+ ## Grupo de campos
52
+
53
+ Quando a região nomear um conjunto de campos relacionados, use `as="fieldset"`. O `Content`
54
+ renderiza o título como `legend`, preserva as props nativas do `fieldset` e mantém o mesmo contrato
55
+ de título e corpo. Essa escolha altera somente a semântica e o comportamento nativo; com `framed`,
56
+ a moldura permanece contínua. Nesse modo, contador, descrição e ações não entram na legenda.
57
+
58
+ ```tsx preview col
59
+ render(
60
+ <Content as="fieldset" framed title="Proprietário">
61
+ <FieldGroup>
62
+ <Field>
63
+ <FieldLabel htmlFor="owner-name">Nome</FieldLabel>
64
+ <Input id="owner-name" />
65
+ </Field>
66
+ <Field>
67
+ <FieldLabel htmlFor="owner-document">CPF/CNPJ</FieldLabel>
68
+ <Input id="owner-document" />
69
+ </Field>
70
+ </FieldGroup>
71
+ </Content>,
72
+ );
73
+ ```
74
+
75
+ Quando a legenda precisar integrar visualmente a borda, declare essa escolha separadamente. Use
76
+ `frameVariant="legend"` em agrupamentos compactos, como as seções de filtros avançados.
77
+
78
+ ```tsx preview col
79
+ render(
80
+ <Content
81
+ as="fieldset"
82
+ framed
83
+ frameVariant="legend"
84
+ title="Dados do veículo"
85
+ >
86
+ <FieldGroup>
87
+ <Field>
88
+ <FieldLabel htmlFor="vehicle-plate">Placa</FieldLabel>
89
+ <Input id="vehicle-plate" />
90
+ </Field>
91
+ </FieldGroup>
92
+ </Content>,
93
+ );
94
+ ```
95
+
23
96
  ## Composição explícita
24
97
 
25
98
  Use os slots quando o header precisar de composição própria. A árvore aceita exatamente um
@@ -63,14 +136,17 @@ gatilho repete esse título e a edição usa `Editar recurso`.
63
136
 
64
137
  ## Propriedades de Content
65
138
 
66
- | Propriedade | Tipo | Padrão | Descrição |
67
- | ------------- | ---------------------------- | ----------- | -------------------------------------------------------------------------------------------------------- |
68
- | `title` | `ReactNode` | | Forma curta: título da região (vira o heading ligado à `section`). |
69
- | `count` | `number` | | Forma curta: total de itens ao lado do título da região. |
70
- | `description` | `ReactNode` | | Forma curta: frase de apoio sob o título. |
71
- | `actions` | `ReactNode` | | Forma curta: ações sobre a região inteira; em `page`, ficam no extremo oposto ao título. |
72
- | `level` | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | `2` | Nível semântico do heading, independente do destaque visual. |
73
- | `variant` | `'page' \| 'section'` | `'section'` | Hierarquia visual: `page` reproduz o cabeçalho de `Page`; `section` é a região dentro de uma superfície. |
139
+ | Propriedade | Tipo | Padrão | Descrição |
140
+ | -------------- | ---------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------- |
141
+ | `title` | `ReactNode` | | Forma curta: título da região (vira o heading ligado à `section`). |
142
+ | `count` | `number` | | Forma curta: total de itens ao lado do título da região. |
143
+ | `description` | `ReactNode` | | Forma curta: frase de apoio sob o título. |
144
+ | `actions` | `ReactNode` | | Forma curta: ações sobre a região inteira; em `page`, ficam no extremo oposto ao título. |
145
+ | `level` | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | `2` | Nível semântico do heading, independente do destaque visual. |
146
+ | `variant` | `'page' \| 'section'` | `'section'` | Hierarquia visual: `page` reproduz o cabeçalho de `Page`; `section` é a região dentro de uma superfície. |
147
+ | `as` | `'section' \| 'fieldset'` | `'section'` | Usa uma região com heading ou um grupo de campos cujo título é a legenda nativa. |
148
+ | `framed` | `boolean` | `false` | Envolve o header e o body com a moldura estrutural da região. |
149
+ | `frameVariant` | `'default' \| 'legend'` | `'default'` | Em `fieldset` com moldura, mantém a borda contínua ou integra visualmente a legenda à borda. Não altera a semântica. |
74
150
 
75
151
  Na composição explícita, `ContentHeader` recebe `ContentTitle`, `ContentMeta`, `ContentDescription` e
76
152
  `ContentActions`, e `ContentBody` recebe o conteúdo; nenhum desses slots aceita `title` ou `level`