@softize/opus 12.5.4 → 12.6.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.
@@ -106,6 +106,8 @@ export interface IconPickerProps {
106
106
  disabled?: boolean
107
107
  className?: string
108
108
  id?: string
109
+ 'aria-invalid'?: boolean
110
+ 'aria-describedby'?: string
109
111
  }
110
112
 
111
113
  /**
@@ -122,6 +124,8 @@ export function IconPicker({
122
124
  disabled,
123
125
  className,
124
126
  id,
127
+ 'aria-invalid': ariaInvalid,
128
+ 'aria-describedby': ariaDescribedBy,
125
129
  }: IconPickerProps): React.ReactElement {
126
130
  const [open, setOpen] = React.useState(false)
127
131
  const Current = value !== '' ? icons[value] : undefined
@@ -133,6 +137,8 @@ export function IconPicker({
133
137
  type="button"
134
138
  id={id}
135
139
  disabled={disabled}
140
+ aria-invalid={ariaInvalid}
141
+ aria-describedby={ariaDescribedBy}
136
142
  data-slot="icon-picker"
137
143
  className={cn(
138
144
  'flex h-10 w-full items-center gap-2 rounded-md border border-input bg-transparent px-3 text-sm outline-none transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-input/30',
@@ -5,14 +5,36 @@ import { Slot } from "radix-ui"
5
5
  import { cn } from '../../lib/cn.ts'
6
6
  import { Separator } from './separator.tsx'
7
7
 
8
- function ItemGroup({ className, ...props }: React.ComponentProps<"div">) {
8
+ const itemGroupVariants = cva("group/item-group @container/item-group flex flex-col", {
9
+ variants: {
10
+ variant: {
11
+ plain: "",
12
+ framed:
13
+ "overflow-hidden rounded-xl border border-border bg-card text-card-foreground [&>[data-slot=item]]:rounded-none [&>[data-slot=item-container]>[data-slot=item]]:rounded-none",
14
+ },
15
+ },
16
+ defaultVariants: {
17
+ variant: "plain",
18
+ },
19
+ })
20
+
21
+ const ItemGroupContext = React.createContext(false)
22
+
23
+ function ItemGroup({
24
+ className,
25
+ variant = "plain",
26
+ ...props
27
+ }: React.ComponentProps<"div"> & VariantProps<typeof itemGroupVariants>) {
9
28
  return (
10
- <div
11
- role="list"
12
- data-slot="item-group"
13
- className={cn("group/item-group flex flex-col", className)}
14
- {...props}
15
- />
29
+ <ItemGroupContext.Provider value>
30
+ <div
31
+ role="list"
32
+ data-slot="item-group"
33
+ data-variant={variant}
34
+ className={cn(itemGroupVariants({ variant, className }))}
35
+ {...props}
36
+ />
37
+ </ItemGroupContext.Provider>
16
38
  )
17
39
  }
18
40
 
@@ -56,12 +78,16 @@ function Item({
56
78
  variant = "default",
57
79
  size = "default",
58
80
  asChild = false,
81
+ role,
59
82
  ...props
60
83
  }: React.ComponentProps<"div"> &
61
84
  VariantProps<typeof itemVariants> & { asChild?: boolean }) {
85
+ const grouped = React.useContext(ItemGroupContext)
62
86
  const Comp = asChild ? Slot.Root : "div"
63
- return (
87
+
88
+ const content = (
64
89
  <Comp
90
+ role={role ?? (!asChild && grouped ? "listitem" : undefined)}
65
91
  data-slot="item"
66
92
  data-variant={variant}
67
93
  data-size={size}
@@ -69,6 +95,18 @@ function Item({
69
95
  {...props}
70
96
  />
71
97
  )
98
+
99
+ if (asChild && grouped) {
100
+ return (
101
+ <div role="listitem" data-slot="item-container">
102
+ {content}
103
+ </div>
104
+ )
105
+ }
106
+
107
+ return (
108
+ content
109
+ )
72
110
  }
73
111
 
74
112
  const itemMediaVariants = cva(
@@ -57,6 +57,8 @@ interface SelectBaseProps {
57
57
  id?: string
58
58
  /** Ícone LEADING dentro do campo — identifica o filtro sem jogar o ícone ao lado. */
59
59
  icon?: React.ReactNode
60
+ 'aria-invalid'?: boolean
61
+ 'aria-describedby'?: string
60
62
  /** Altura: `default` (h-9, a do Input/Button) ou `sm` (h-8) pra toolbar densa. */
61
63
  size?: 'sm' | 'default'
62
64
  /** Geometria do controle; `pill` preserva a variante visual escolhida. */
@@ -155,6 +157,8 @@ function NativeSelect({
155
157
  disabled={disabled}
156
158
  value={value}
157
159
  aria-label={rest['aria-label']}
160
+ aria-invalid={rest['aria-invalid']}
161
+ aria-describedby={rest['aria-describedby']}
158
162
  onChange={(e) => onChange(e.target.value)}
159
163
  className={cn(
160
164
  "h-9 w-full min-w-0 appearance-none rounded-md border border-input bg-transparent px-3 py-2 pr-9 text-sm transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground disabled:pointer-events-none disabled:cursor-not-allowed data-[size=sm]:h-8 data-[size=sm]:py-1 data-[shape=pill]:rounded-full dark:bg-input/30 dark:hover:bg-input/50",
@@ -345,6 +349,7 @@ function CustomSelect(props: Exclude<SelectProps, SelectNativeProps>): React.Rea
345
349
  data-size={size}
346
350
  data-shape={shape}
347
351
  data-variant={variant}
352
+ aria-invalid={props['aria-invalid']}
348
353
  onClick={() => {
349
354
  if (disabled !== true) {
350
355
  setOpen(true)
@@ -417,6 +422,8 @@ function CustomSelect(props: Exclude<SelectProps, SelectNativeProps>): React.Rea
417
422
  role="combobox"
418
423
  aria-expanded={open}
419
424
  aria-label={props['aria-label']}
425
+ aria-invalid={props['aria-invalid']}
426
+ aria-describedby={props['aria-describedby']}
420
427
  value={props.multiple ? '' : triggerText}
421
428
  onChange={() => {}}
422
429
  onFocus={() => {
@@ -12,6 +12,25 @@ render(
12
12
  )
13
13
  ```
14
14
 
15
+ ## Barra de filtros compartilhada
16
+
17
+ `ActionFilterBar` expõe a mesma linguagem declarativa de busca, filtros e período para
18
+ superfícies que não são listagens, como relatórios. O estado é controlado pelo consumidor;
19
+ `onRefresh` refaz sua consulta sem alterar o recorte vigente.
20
+
21
+ ```tsx
22
+ <ActionFilterBar
23
+ action={{ text, filters, periods }}
24
+ state={filterState}
25
+ onStateChange={setFilterState}
26
+ onRefresh={refetch}
27
+ refreshing={isLoading}
28
+ />
29
+ ```
30
+
31
+ Busca e filtros são renderizados somente quando declarados. Períodos incluem os presets do
32
+ contrato e o intervalo personalizado no mesmo calendário usado pela `ActionList`.
33
+
15
34
  ## Células custom (cells)
16
35
 
17
36
  As colunas do contrato descrevem DADOS; apresentação especial entra por cima com `cells` (chave = key da coluna). É onde vivem chips coloridos por dict, links e a coluna de ações.
@@ -49,7 +68,7 @@ periods: [
49
68
 
50
69
  ## Paginação e loading
51
70
 
52
- O pattern manda `limit` (= `pageSize`, default 50) e `page` no input; o handler implementa o OFFSET e devolve `total` no Paginated. O rodapé (Página X de Y · páginas numeradas com reticências · "N itens no total") aparece sempre que há total; mudar filtro/busca/sort/período volta pra página 1 (a página vive na URL: `?page=2`). No refetch com a lista já na tela, o corpo esmaece (`aria-busy`) até os dados chegarem — o skeleton é só do primeiro load.
71
+ O pattern manda `limit` (= `pageSize`, default 50) e `page` no input; o handler implementa o OFFSET e devolve `total` no Paginated. O rodapé (Página X de Y · páginas numeradas com reticências · "N itens no total") aparece sempre que há total; mudar filtro/busca/sort/período volta pra página 1 (a página vive na URL: `?page=2`). No primeiro carregamento, `DataState` centraliza o spinner; no refetch com a lista já na tela, o corpo esmaece (`aria-busy`) até os dados chegarem.
53
72
 
54
73
  ## Multi-seleção com can (batch)
55
74
 
@@ -17,12 +17,12 @@ A composição completa: ItemMedia à esquerda, ItemContent (ItemTitle + ItemDes
17
17
  </Item>
18
18
  ```
19
19
 
20
- ## Lista
20
+ ## Lista emoldurada
21
21
 
22
- Empilhe vários Item num ItemGroup e separe com ItemSeparator — o padrão pra listas de repositórios, agentes ou sessões.
22
+ `ItemGroup variant="framed"` aplica a superfície canônica. Os divisores continuam explícitos com `ItemSeparator`, tanto no modo `framed` quanto no `plain` — o padrão pra listas de repositórios, agentes ou sessões.
23
23
 
24
24
  ```tsx preview col
25
- <ItemGroup>
25
+ <ItemGroup variant="framed">
26
26
  <Item>
27
27
  <ItemMedia variant="icon">
28
28
  <GitBranch />
@@ -78,6 +78,7 @@ asChild funde o Item num <a> — a linha inteira vira alvo (hover no fundo). siz
78
78
 
79
79
  | Prop | Tipo | Default | Descrição |
80
80
  |---|---|---|---|
81
+ | `variant` (ItemGroup) | `'plain' \| 'framed'` | `'plain'` | `framed` aplica moldura e superfície; `plain` mantém a composição livre. Os divisores são explícitos nos dois modos. |
81
82
  | `variant` (Item) | `'default' \| 'outline' \| 'muted'` | `'default'` | O fundo da linha — default transparente, outline com borda, muted levemente tingido. |
82
83
  | `size` (Item) | `'default' \| 'sm'` | `'default'` | O respiro interno — sm aperta o padding pra listas densas. |
83
84
  | `asChild` (Item) | `boolean` | `false` | Funde o Item no filho (ex.: <a> ou <button>) — a linha inteira vira o alvo. |
package/src/ui/meta.ts CHANGED
@@ -83,6 +83,12 @@ export const componentMeta = {
83
83
  whenToUse:
84
84
  'A caixa de escrever da casa (o composer do Chat, extraído): textarea numa pílula elevada, Enter envia / Shift+Enter quebra linha, enviar dentro. Use SOZINHO quando há entrada de texto mas não um chat — ex.: o composer de criação de sessão do Maestro (sem histórico). Com `actions`, ganha uma barra embaixo pra seletores discretos à esquerda (app, agente, contexto…) — o mesmo lugar onde o Maestro põe app/task e a GB poria o agente. Sem `actions`, é a linha única de sempre. Controlado (`value`/`onChange`/`onSubmit`); `submitDisabled` gateia além de vazio/busy. Pra um chat completo (mensagens + este composer), use Chat.',
85
85
  },
86
+ 'content-header': {
87
+ name: 'content-header',
88
+ ancestry: 'opus',
89
+ whenToUse:
90
+ 'Cabeçalho semântico de uma seção de conteúdo: título, descrição, meta e ações na mesma composição. Use `variant="section"` dentro de páginas e painéis; `Page` já fornece o cabeçalho principal e não deve receber outro ContentHeader equivalente. O `level` controla a hierarquia do heading sem alterar a hierarquia visual.',
91
+ },
86
92
  'copyable': {
87
93
  name: 'copyable',
88
94
  ancestry: 'opus',
@@ -269,7 +275,7 @@ export const componentMeta = {
269
275
  name: 'item',
270
276
  ancestry: 'shadcn',
271
277
  whenToUse:
272
- 'Linha de conteúdo composta — mídia + título/descrição + ações numa linha clicável ou estática. Item é o container (`variant` default/outline/muted, `size` default/sm, `asChild` pra virar link/botão); componha ItemMedia (`variant` icon/image), ItemContent (ItemTitle + ItemDescription), ItemActions e, na borda, ItemHeader/ItemFooter. Empilhe vários num ItemGroup separados por ItemSeparator o padrão pra listas de workspaces, agentes, repositórios e sessões.',
278
+ 'Linha de conteúdo composta — mídia + título/descrição + ações numa linha clicável ou estática. Item é o container (`variant` default/outline/muted, `size` default/sm, `asChild` pra virar link/botão); componha ItemMedia (`variant` icon/image), ItemContent (ItemTitle + ItemDescription), ItemActions e, na borda, ItemHeader/ItemFooter. Empilhe vários num ItemGroup: `variant="framed"` aplica a moldura e a superfície; ItemSeparator declara os divisores internos tanto no modo framed quanto no plain. É o padrão pra listas de workspaces, agentes, repositórios e sessões.',
273
279
  },
274
280
  'kbd': {
275
281
  name: 'kbd',
package/src/ui/react.tsx CHANGED
@@ -187,8 +187,8 @@ export type { ActionFormDialogProps } from './components/patterns/form-dialog.ts
187
187
  export { ActionFormCard } from './components/patterns/action-form-card.tsx'
188
188
  export type { ActionFormCardProps } from './components/patterns/action-form-card.tsx'
189
189
 
190
- export { ActionList, emptyListState, presetRange, listParamsToState, listStateToParams } from './components/patterns/list.tsx'
191
- export type { ActionListProps, ActionListColumn, ActionListState, ActionListView, ActionListBatchAction, ListActionLike } from './components/patterns/list.tsx'
190
+ export { ActionFilterBar, ActionList, emptyListState, presetRange, listParamsToState, listStateToParams } from './components/patterns/list.tsx'
191
+ export type { ActionFilterBarProps, ActionFilterState, ActionListProps, ActionListColumn, ActionListState, ActionListView, ActionListBatchAction, ListActionLike } from './components/patterns/list.tsx'
192
192
 
193
193
  export { ActionView } from './components/patterns/view.tsx'
194
194
  export type { ActionViewProps } from './components/patterns/view.tsx'
@@ -210,6 +210,14 @@ export type { DataStateProps } from './components/patterns/data-state.tsx'
210
210
  export { Page } from './components/patterns/page.tsx'
211
211
  export type { PageProps } from './components/patterns/page.tsx'
212
212
 
213
+ // Cabeçalho de conteúdo em duas hierarquias visuais, sem impor espaçamento externo.
214
+ export { ContentHeader } from './components/patterns/content-header.tsx'
215
+ export type {
216
+ ContentHeaderProps,
217
+ ContentHeaderLevel,
218
+ ContentHeaderVariant,
219
+ } from './components/patterns/content-header.tsx'
220
+
213
221
  // Layout composicional: Split decide a relação espacial; Pane carrega conteúdo com inset.
214
222
  export { Split, Pane } from './components/patterns/split.tsx'
215
223
  export type { SplitProps, PaneProps, PaneSize, SplitLayout, SplitLayoutChange } from './components/patterns/split.tsx'