@softize/opus 18.4.0 → 19.0.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 +94 -0
- package/docs/adr/0020-icon-tiles-are-explicitly-composed.md +46 -0
- package/docs/adr/0021-content-carries-structure-and-semantic-element.md +65 -0
- package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +293 -0
- package/package.json +1 -1
- package/src/ui/components/patterns/content-header.tsx +169 -50
- package/src/ui/components/patterns/heading-level.ts +16 -0
- package/src/ui/components/patterns/list.tsx +76 -27
- package/src/ui/components/patterns/pane.tsx +294 -0
- package/src/ui/components/patterns/presentation.tsx +29 -17
- package/src/ui/components/patterns/sidebar.tsx +13 -72
- package/src/ui/components/patterns/split.tsx +8 -35
- package/src/ui/components/patterns/surface-anatomy.ts +219 -0
- package/src/ui/components/patterns/surface-header.tsx +51 -6
- package/src/ui/components/patterns/surface-media.tsx +69 -0
- package/src/ui/components/primitives/card.tsx +91 -29
- package/src/ui/components/primitives/command.tsx +12 -4
- package/src/ui/components/primitives/detail.tsx +10 -13
- package/src/ui/components/primitives/dialog.tsx +138 -35
- package/src/ui/components/primitives/drawer.tsx +122 -23
- package/src/ui/components/primitives/empty.tsx +2 -1
- package/src/ui/components/primitives/field.tsx +38 -16
- package/src/ui/components/primitives/icon-tile.tsx +91 -0
- package/src/ui/components/primitives/item.tsx +2 -1
- package/src/ui/components/primitives/metric-card.tsx +98 -39
- package/src/ui/components/primitives/table.tsx +1 -1
- package/src/ui/docs/content/action-list.md +61 -52
- package/src/ui/docs/content/card.md +221 -30
- package/src/ui/docs/content/content.md +90 -8
- package/src/ui/docs/content/dialog.md +115 -7
- package/src/ui/docs/content/drawer.md +136 -9
- package/src/ui/docs/content/field.md +78 -30
- package/src/ui/docs/content/icon-tile.md +47 -0
- package/src/ui/docs/content/metric-card.md +57 -18
- package/src/ui/docs/content/page.md +8 -0
- package/src/ui/docs/content/pane.md +250 -0
- package/src/ui/docs/content/sidebar.md +22 -43
- package/src/ui/docs/content/skeleton.md +7 -3
- package/src/ui/docs/content/split.md +16 -21
- package/src/ui/docs/registry.tsx +14 -2
- package/src/ui/meta.ts +17 -5
- package/src/ui/react.tsx +31 -0
|
@@ -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:
|
|
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
|
},
|
|
@@ -42,7 +42,25 @@ interface MetricCardBaseProps extends Omit<React.ComponentProps<typeof Card>, 'c
|
|
|
42
42
|
secondaryMetric?: MetricCardSecondaryMetric
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
type MetricCardActivationProps =
|
|
46
|
+
| {
|
|
47
|
+
/** Indica se a medida controla o recorte atualmente aplicado. */
|
|
48
|
+
active: boolean
|
|
49
|
+
/** Ativa a medida como recorte ou seleção. */
|
|
50
|
+
onActivate: () => void
|
|
51
|
+
/** Nome acessível da ativação, como “Filtrar por Agendados”. */
|
|
52
|
+
activationLabel: string
|
|
53
|
+
/** Use `onActivate`; o clique genérico conflita com os controles internos do card. */
|
|
54
|
+
onClick?: never
|
|
55
|
+
}
|
|
56
|
+
| {
|
|
57
|
+
active?: never
|
|
58
|
+
onActivate?: never
|
|
59
|
+
activationLabel?: never
|
|
60
|
+
}
|
|
61
|
+
|
|
45
62
|
export type MetricCardProps = MetricCardBaseProps &
|
|
63
|
+
MetricCardActivationProps &
|
|
46
64
|
(
|
|
47
65
|
| {
|
|
48
66
|
/** Nome curto da medida apresentada. */
|
|
@@ -75,6 +93,9 @@ export function MetricCard({
|
|
|
75
93
|
tone,
|
|
76
94
|
action,
|
|
77
95
|
secondaryMetric,
|
|
96
|
+
active,
|
|
97
|
+
onActivate,
|
|
98
|
+
activationLabel,
|
|
78
99
|
loading = false,
|
|
79
100
|
className,
|
|
80
101
|
...props
|
|
@@ -84,6 +105,7 @@ export function MetricCard({
|
|
|
84
105
|
}
|
|
85
106
|
const hasIcon = icon !== undefined && icon !== null
|
|
86
107
|
const resolvedContext = context ?? (tone === 'warning' ? 'warning' : 'neutral')
|
|
108
|
+
const interactive = onActivate !== undefined
|
|
87
109
|
|
|
88
110
|
const primaryCard = (
|
|
89
111
|
<Card
|
|
@@ -92,9 +114,9 @@ export function MetricCard({
|
|
|
92
114
|
className={cn(
|
|
93
115
|
'min-w-0 p-4',
|
|
94
116
|
secondaryMetric !== undefined && 'relative z-10',
|
|
95
|
-
secondaryMetric === undefined && className,
|
|
117
|
+
secondaryMetric === undefined && !interactive && className,
|
|
96
118
|
)}
|
|
97
|
-
{...(secondaryMetric === undefined ? props : {})}
|
|
119
|
+
{...(secondaryMetric === undefined && !interactive ? props : {})}
|
|
98
120
|
>
|
|
99
121
|
{loading ? (
|
|
100
122
|
<div className="space-y-3">
|
|
@@ -106,7 +128,10 @@ export function MetricCard({
|
|
|
106
128
|
<>
|
|
107
129
|
<div className="flex items-start justify-between gap-3">
|
|
108
130
|
<div className="flex min-w-0 items-center gap-1.5">
|
|
109
|
-
<div
|
|
131
|
+
<div
|
|
132
|
+
data-slot="metric-card-label"
|
|
133
|
+
className="min-w-0 truncate text-sm font-medium text-muted-foreground"
|
|
134
|
+
>
|
|
110
135
|
{label}
|
|
111
136
|
</div>
|
|
112
137
|
{labelAction !== undefined && (
|
|
@@ -129,7 +154,10 @@ export function MetricCard({
|
|
|
129
154
|
</span>
|
|
130
155
|
)}
|
|
131
156
|
</div>
|
|
132
|
-
<div
|
|
157
|
+
<div
|
|
158
|
+
data-slot="metric-card-value"
|
|
159
|
+
className="mt-3 text-2xl font-semibold tracking-tight tabular-nums"
|
|
160
|
+
>
|
|
133
161
|
{value}
|
|
134
162
|
</div>
|
|
135
163
|
{description !== undefined && (
|
|
@@ -147,46 +175,77 @@ export function MetricCard({
|
|
|
147
175
|
</Card>
|
|
148
176
|
)
|
|
149
177
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
{primaryCard}
|
|
160
|
-
<Card
|
|
161
|
-
data-slot="metric-card-secondary"
|
|
162
|
-
className="relative z-0 -mt-8 flex w-full min-w-0 items-center gap-1.5 bg-[color-mix(in_oklab,var(--card)_88%,black)] px-4 pt-9 pb-1"
|
|
178
|
+
const metric =
|
|
179
|
+
secondaryMetric === undefined ? (
|
|
180
|
+
primaryCard
|
|
181
|
+
) : (
|
|
182
|
+
<div
|
|
183
|
+
{...(!interactive ? props : {})}
|
|
184
|
+
data-slot="metric-card-stack"
|
|
185
|
+
aria-busy={loading || undefined}
|
|
186
|
+
className={cn('relative isolate min-w-0', !interactive && className)}
|
|
163
187
|
>
|
|
164
|
-
{
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
</div>
|
|
174
|
-
<div className="flex min-w-0 items-center gap-1.5">
|
|
188
|
+
{primaryCard}
|
|
189
|
+
<Card
|
|
190
|
+
data-slot="metric-card-secondary"
|
|
191
|
+
className="relative z-0 -mt-8 flex w-full min-w-0 items-center gap-1.5 bg-[color-mix(in_oklab,var(--card)_88%,black)] px-4 pt-9 pb-1"
|
|
192
|
+
>
|
|
193
|
+
{loading ? (
|
|
194
|
+
<Skeleton className="h-5 w-full" />
|
|
195
|
+
) : (
|
|
196
|
+
<>
|
|
175
197
|
<div
|
|
176
|
-
data-slot="metric-card-secondary-
|
|
177
|
-
className="
|
|
198
|
+
data-slot="metric-card-secondary-value"
|
|
199
|
+
className="shrink-0 text-xs font-medium text-muted-foreground tabular-nums"
|
|
178
200
|
>
|
|
179
|
-
{secondaryMetric.
|
|
201
|
+
{secondaryMetric.value}
|
|
180
202
|
</div>
|
|
181
|
-
|
|
182
|
-
<div
|
|
183
|
-
|
|
203
|
+
<div className="flex min-w-0 items-center gap-1.5">
|
|
204
|
+
<div
|
|
205
|
+
data-slot="metric-card-secondary-label"
|
|
206
|
+
className="min-w-0 truncate text-xs font-medium text-muted-foreground"
|
|
207
|
+
>
|
|
208
|
+
{secondaryMetric.label}
|
|
184
209
|
</div>
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
210
|
+
{secondaryMetric.labelAction !== undefined && (
|
|
211
|
+
<div data-slot="metric-card-secondary-label-action" className="shrink-0">
|
|
212
|
+
{secondaryMetric.labelAction}
|
|
213
|
+
</div>
|
|
214
|
+
)}
|
|
215
|
+
</div>
|
|
216
|
+
</>
|
|
217
|
+
)}
|
|
218
|
+
</Card>
|
|
219
|
+
</div>
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
if (!interactive) return metric
|
|
223
|
+
|
|
224
|
+
return (
|
|
225
|
+
<div
|
|
226
|
+
{...props}
|
|
227
|
+
data-slot="metric-card-activation"
|
|
228
|
+
data-active={active}
|
|
229
|
+
className={cn(
|
|
230
|
+
// Ativa = a borda na cor primária, e só. Anel somaria uma segunda linha de
|
|
231
|
+
// outra cor à borda e, pintado para fora, sumiria no primeiro ancestral com
|
|
232
|
+
// `overflow-hidden`.
|
|
233
|
+
'relative isolate min-w-0 data-[active=true]:[&_[data-slot=metric-card]]:border-primary',
|
|
234
|
+
className,
|
|
235
|
+
)}
|
|
236
|
+
>
|
|
237
|
+
<button
|
|
238
|
+
type="button"
|
|
239
|
+
data-slot="metric-card-activation-control"
|
|
240
|
+
className="peer absolute inset-0 z-0 cursor-pointer rounded-xl outline-none"
|
|
241
|
+
aria-label={activationLabel}
|
|
242
|
+
aria-pressed={active}
|
|
243
|
+
disabled={loading}
|
|
244
|
+
onClick={onActivate}
|
|
245
|
+
/>
|
|
246
|
+
<div className="pointer-events-none relative z-10 h-full [&>[data-slot=metric-card]]:h-full [&_[data-slot=metric-card]]:transition-colors [&_[data-slot=metric-card-action]]:pointer-events-auto [&_[data-slot=metric-card-label-action]]:pointer-events-auto [&_[data-slot=metric-card-secondary-label-action]]:pointer-events-auto peer-hover:[&_[data-slot=metric-card]]:border-foreground/40 peer-focus-visible:[&_[data-slot=metric-card]]:ring-2 peer-focus-visible:[&_[data-slot=metric-card]]:ring-inset peer-focus-visible:[&_[data-slot=metric-card]]:ring-ring">
|
|
247
|
+
{metric}
|
|
248
|
+
</div>
|
|
190
249
|
</div>
|
|
191
250
|
)
|
|
192
251
|
}
|
|
@@ -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
|
|
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: <
|
|
128
|
-
Datas: <
|
|
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
|
|
360
|
-
| ----------------- |
|
|
361
|
-
| `action` | `Pick<ListAction, 'filters' \| 'text' \| 'periods'>`
|
|
362
|
-
| `state` | `ActionFilterState`
|
|
363
|
-
| `onStateChange` | `(next) => void`
|
|
364
|
-
| `filterOptions` | `Record<string, SelectOption[]>`
|
|
365
|
-
| `filterDefaults` | `Record<string, string>`
|
|
366
|
-
| `filterIcons` | `Record<string, ReactNode>`
|
|
367
|
-
| `advancedFilters` | `{ columns?: 1 \| 2 \| 3; sectionIcons?: Record<string, ReactNode
|
|
368
|
-
| `periodPosition` | `'before' \| 'after' \| { afterFilter: string }`
|
|
369
|
-
| `period` | `{ basisFilter: string }`
|
|
370
|
-
| `onRefresh` | `() => Promise<void> \| void`
|
|
371
|
-
| `refreshing` | `boolean`
|
|
372
|
-
| `controls` | `ReactNode`
|
|
373
|
-
| `actions` | `ReactNode`
|
|
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
|
|
396
|
-
| ----------------------- |
|
|
397
|
-
| `action` | `ListAction<TInput, TItem>`
|
|
398
|
-
| `input` | `TInput`
|
|
399
|
-
| `layout` | `'flow' \| 'fill'`
|
|
400
|
-
| `cells` | `Record<string, (item) => ReactNode>`
|
|
401
|
-
| `filterOptions` | `Record<string, SelectOption[]>`
|
|
402
|
-
| `filterDefaults` | `Record<string, string>`
|
|
403
|
-
| `filterIcons` | `Record<string, ReactNode>`
|
|
404
|
-
| `advancedFilters` | `{ columns?: 1 \| 2 \| 3; sectionIcons?: Record<string, ReactNode
|
|
405
|
-
| `periodPosition` | `'before' \| 'after' \| { afterFilter: string }`
|
|
406
|
-
| `period` | `{ basisFilter: string }`
|
|
407
|
-
| `views` | `Record<string, ActionListView<TItem>>`
|
|
408
|
-
| `beforeContent` | `ReactNode`
|
|
409
|
-
| `columns` | `ActionListColumn<TItem>[]`
|
|
410
|
-
| `children` | `(items, refetch) => ReactNode`
|
|
411
|
-
| `batch` | `ActionListBatchAction[]`
|
|
412
|
-
| `rowId` | `(item) => string`
|
|
413
|
-
| `pageSize` | `number`
|
|
414
|
-
| `state / onStateChange` | `ActionListState / (next) => void`
|
|
415
|
-
| `empty` | `(items) => boolean`
|
|
416
|
-
| `loading` | `boolean`
|
|
417
|
-
| `emptyMessage` | `string`
|
|
418
|
-
| `errorMessage` | `string`
|
|
419
|
-
| `retryLabel` | `string`
|
|
420
|
-
| `onRowClick` | `(item: TItem) => void`
|
|
421
|
-
| `toolbarActions` | `ReactNode`
|
|
422
|
-
| `rowActions` | `(item: TItem) => ReactNode`
|
|
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`. |
|