@softize/opus 16.0.0 → 17.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.
Files changed (31) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/bin/lib/check.mjs +9 -13
  3. package/bin/lib/copy.mjs +29 -4
  4. package/docs/adr/0005-structural-surfaces-share-an-explicit-anatomy.md +6 -3
  5. package/docs/adr/0009-page-title-does-not-carry-a-counter.md +5 -2
  6. package/docs/adr/0011-page-shell-coordinates-persistent-page-chrome.md +12 -14
  7. package/docs/adr/0012-modal-header-only-names-the-surface.md +8 -4
  8. package/docs/adr/0013-presentation-is-a-portable-action-oriented-artifact.md +7 -4
  9. package/docs/adr/0014-structural-headers-do-not-carry-description.md +35 -0
  10. package/package.json +2 -2
  11. package/registry/skills/build-opus-ui/references/evaluations.md +1 -1
  12. package/registry/skills/build-opus-ui/references/ui-patterns.md +16 -17
  13. package/registry/templates/app/package.json +1 -1
  14. package/src/core/presentation.ts +142 -13
  15. package/src/ui/components/patterns/form.tsx +29 -10
  16. package/src/ui/components/patterns/page.tsx +93 -48
  17. package/src/ui/components/patterns/presentation.tsx +487 -134
  18. package/src/ui/components/patterns/surface-header.tsx +4 -3
  19. package/src/ui/components/patterns/trigger.tsx +8 -1
  20. package/src/ui/components/patterns/view.tsx +2 -2
  21. package/src/ui/components/primitives/command.tsx +82 -42
  22. package/src/ui/components/primitives/dialog.tsx +180 -97
  23. package/src/ui/components/primitives/drawer.tsx +63 -22
  24. package/src/ui/docs/content/command.md +3 -1
  25. package/src/ui/docs/content/content.md +1 -1
  26. package/src/ui/docs/content/dialog.md +9 -9
  27. package/src/ui/docs/content/drawer.md +4 -4
  28. package/src/ui/docs/content/page.md +14 -29
  29. package/src/ui/docs/content/presentation.md +54 -46
  30. package/src/ui/meta.ts +2 -2
  31. package/src/ui/react.tsx +1 -2
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Cabeçalho de superfície (interno): a anatomia que Page e Content compartilham.
3
3
  *
4
- * Um título, metadata opcional, uma descrição e uma região de ações que vai para o extremo oposto
4
+ * Um título, metadata opcional e uma região de ações que vai para o extremo oposto
5
5
  * em larguras amplas e empilha abaixo do contexto nas estreitas. Cada família escolhe quais slots
6
6
  * expõe; layout, validação e classes vivem aqui, uma vez.
7
7
  */
@@ -42,7 +42,7 @@ interface SurfaceHeaderSlots {
42
42
  leading?: ElementType | readonly ElementType[];
43
43
  title: ElementType;
44
44
  count?: ElementType;
45
- description: ElementType;
45
+ description?: ElementType;
46
46
  actions: ElementType;
47
47
  }
48
48
 
@@ -93,7 +93,8 @@ export function SurfaceHeader({
93
93
  const leading = leadingTypes.flatMap(of);
94
94
  const titles = of(slots.title);
95
95
  const counts = slots.count === undefined ? [] : of(slots.count);
96
- const descriptions = of(slots.description);
96
+ const descriptions =
97
+ slots.description === undefined ? [] : of(slots.description);
97
98
  const actionSlots = of(slots.actions);
98
99
  const recognized =
99
100
  leading.length +
@@ -13,7 +13,7 @@
13
13
  */
14
14
 
15
15
  import type { ReactNode } from 'react'
16
- import { useId, useState } from 'react'
16
+ import { useEffect, useId, useState } from 'react'
17
17
  import type { SimpleContract } from '../../../core/index.ts'
18
18
  import { useTriggerAction } from '../../drivers/react.tsx'
19
19
  import { toast } from '../primitives/sonner.tsx'
@@ -66,6 +66,8 @@ export interface ActionTriggerProps<TInput, TData> {
66
66
  }
67
67
  /** Callback adicional pós-sucesso (cache já foi invalidado). */
68
68
  onSuccess?: (data: TData) => void
69
+ /** Notifica o renderer que coordena bloqueio entre várias actions. */
70
+ onLoadingChange?: (loading: boolean) => void
69
71
  /** Ícone: o botão vira icon-only, com o `label` no tooltip e no aria-label. É a forma
70
72
  * da ação que mora DENTRO de um item (linha, card) — o clique não vaza pro item. */
71
73
  icon?: ReactNode
@@ -85,6 +87,7 @@ export function ActionTrigger<TInput, TData>({
85
87
  disabled = false,
86
88
  confirm: confirmProp,
87
89
  onSuccess,
90
+ onLoadingChange,
88
91
  icon,
89
92
  itemLabel,
90
93
  className,
@@ -105,6 +108,10 @@ export function ActionTrigger<TInput, TData>({
105
108
  )
106
109
  },
107
110
  })
111
+ useEffect(() => {
112
+ onLoadingChange?.(isLoading)
113
+ return () => onLoadingChange?.(false)
114
+ }, [isLoading, onLoadingChange])
108
115
 
109
116
  const buttonLabel = label ?? (typeof action.label === 'string' ? action.label : action.name)
110
117
 
@@ -7,14 +7,14 @@
7
7
  */
8
8
 
9
9
  import type { ReactNode } from 'react'
10
- import type { ViewAction } from '../../../core/index.ts'
10
+ import type { ViewContract } from '../../../core/index.ts'
11
11
  import { useViewAction } from '../../drivers/react.tsx'
12
12
  import { isBusinessError, isRetryable } from '../../lib/action-errors.ts'
13
13
  import { Skeleton } from '../primitives/skeleton.tsx'
14
14
  import { EmptySurface, ErrorSurface } from './state-surface.tsx'
15
15
 
16
16
  export interface ActionViewProps<TInput, TData> {
17
- action: ViewAction<TInput, TData>
17
+ action: ViewContract<TInput, TData>
18
18
  input: TInput
19
19
  /** Renderiza o "tem dado" — o layout é de quem compõe (composição, como no ActionForm). */
20
20
  children?: (data: TData, refetch: () => Promise<void>) => ReactNode
@@ -1,110 +1,144 @@
1
- import * as React from 'react'
2
- import { Command as CommandPrimitive } from 'cmdk'
3
- import { SearchIcon } from 'lucide-react'
1
+ import * as React from "react";
2
+ import { Command as CommandPrimitive } from "cmdk";
3
+ import { SearchIcon } from "lucide-react";
4
4
 
5
- import { cn } from '../../lib/cn.ts'
6
- import { Dialog, DialogContent, DialogHeader, DialogTitle } from './dialog.tsx'
5
+ import { cn } from "../../lib/cn.ts";
6
+ import { Dialog, DialogContent, DialogHeader, DialogTitle } from "./dialog.tsx";
7
7
 
8
- function Command({ className, ...props }: React.ComponentProps<typeof CommandPrimitive>) {
8
+ function Command({
9
+ className,
10
+ ...props
11
+ }: React.ComponentProps<typeof CommandPrimitive>) {
9
12
  return (
10
13
  <CommandPrimitive
11
14
  data-slot="command"
12
15
  className={cn(
13
- 'flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground',
16
+ "flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
14
17
  className,
15
18
  )}
16
19
  {...props}
17
20
  />
18
- )
21
+ );
19
22
  }
20
23
 
21
24
  function CommandDialog({
22
- title = 'Paleta de comandos',
23
- description = 'Busque um comando para executar…',
25
+ title = "Paleta de comandos",
26
+ description = "Busque um comando para executar…",
24
27
  children,
25
28
  className,
26
29
  showCloseButton = true,
27
30
  ...props
28
31
  }: React.ComponentProps<typeof Dialog> & {
29
- title?: string
30
- description?: string
31
- className?: string
32
- showCloseButton?: boolean
32
+ title?: string;
33
+ description?: string;
34
+ className?: string;
35
+ showCloseButton?: boolean;
33
36
  }) {
34
- const descriptionId = React.useId()
37
+ const descriptionId = React.useId();
35
38
  return (
36
39
  <Dialog {...props}>
37
- <DialogHeader className="sr-only">
38
- <DialogTitle>{title}</DialogTitle>
39
- <p id={descriptionId}>{description}</p>
40
- </DialogHeader>
41
40
  <DialogContent
42
- className={cn('overflow-hidden p-0', className)}
41
+ className={cn("overflow-hidden p-0", className)}
43
42
  showCloseButton={showCloseButton}
44
43
  aria-describedby={descriptionId}
45
44
  >
45
+ <DialogHeader className="border-b-0 px-3 py-2">
46
+ <DialogTitle className="text-sm">{title}</DialogTitle>
47
+ <p id={descriptionId} className="sr-only">
48
+ {description}
49
+ </p>
50
+ </DialogHeader>
46
51
  <Command className="**:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
47
52
  {children}
48
53
  </Command>
49
54
  </DialogContent>
50
55
  </Dialog>
51
- )
56
+ );
52
57
  }
53
58
 
54
- function CommandInput({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Input>) {
59
+ function CommandInput({
60
+ className,
61
+ ...props
62
+ }: React.ComponentProps<typeof CommandPrimitive.Input>) {
55
63
  return (
56
- <div data-slot="command-input-wrapper" className="flex h-9 items-center gap-2 border-b px-3">
64
+ <div
65
+ data-slot="command-input-wrapper"
66
+ className="flex h-9 items-center gap-2 border-b px-3"
67
+ >
57
68
  <SearchIcon className="size-4 shrink-0 opacity-50" />
58
69
  <CommandPrimitive.Input
59
70
  data-slot="command-input"
60
71
  className={cn(
61
- 'flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50',
72
+ "flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
62
73
  className,
63
74
  )}
64
75
  {...props}
65
76
  />
66
77
  </div>
67
- )
78
+ );
68
79
  }
69
80
 
70
- function CommandList({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.List>) {
81
+ function CommandList({
82
+ className,
83
+ ...props
84
+ }: React.ComponentProps<typeof CommandPrimitive.List>) {
71
85
  return (
72
86
  <CommandPrimitive.List
73
87
  data-slot="command-list"
74
- className={cn('max-h-[18.75rem] scroll-py-1 overflow-x-hidden overflow-y-auto', className)}
88
+ className={cn(
89
+ "max-h-[18.75rem] scroll-py-1 overflow-x-hidden overflow-y-auto",
90
+ className,
91
+ )}
75
92
  {...props}
76
93
  />
77
- )
94
+ );
78
95
  }
79
96
 
80
- function CommandEmpty({ ...props }: React.ComponentProps<typeof CommandPrimitive.Empty>) {
81
- return <CommandPrimitive.Empty data-slot="command-empty" className="py-6 text-center text-sm" {...props} />
97
+ function CommandEmpty({
98
+ ...props
99
+ }: React.ComponentProps<typeof CommandPrimitive.Empty>) {
100
+ return (
101
+ <CommandPrimitive.Empty
102
+ data-slot="command-empty"
103
+ className="py-6 text-center text-sm"
104
+ {...props}
105
+ />
106
+ );
82
107
  }
83
108
 
84
- function CommandGroup({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Group>) {
109
+ function CommandGroup({
110
+ className,
111
+ ...props
112
+ }: React.ComponentProps<typeof CommandPrimitive.Group>) {
85
113
  return (
86
114
  <CommandPrimitive.Group
87
115
  data-slot="command-group"
88
116
  className={cn(
89
- 'overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground',
117
+ "overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
90
118
  className,
91
119
  )}
92
120
  {...props}
93
121
  />
94
- )
122
+ );
95
123
  }
96
124
 
97
- function CommandSeparator({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Separator>) {
125
+ function CommandSeparator({
126
+ className,
127
+ ...props
128
+ }: React.ComponentProps<typeof CommandPrimitive.Separator>) {
98
129
  return (
99
130
  <CommandPrimitive.Separator
100
131
  data-slot="command-separator"
101
- className={cn('-mx-1 h-px bg-border', className)}
132
+ className={cn("-mx-1 h-px bg-border", className)}
102
133
  {...props}
103
134
  />
104
- )
135
+ );
105
136
  }
106
137
 
107
- function CommandItem({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Item>) {
138
+ function CommandItem({
139
+ className,
140
+ ...props
141
+ }: React.ComponentProps<typeof CommandPrimitive.Item>) {
108
142
  return (
109
143
  <CommandPrimitive.Item
110
144
  data-slot="command-item"
@@ -114,17 +148,23 @@ function CommandItem({ className, ...props }: React.ComponentProps<typeof Comman
114
148
  )}
115
149
  {...props}
116
150
  />
117
- )
151
+ );
118
152
  }
119
153
 
120
- function CommandShortcut({ className, ...props }: React.ComponentProps<'span'>) {
154
+ function CommandShortcut({
155
+ className,
156
+ ...props
157
+ }: React.ComponentProps<"span">) {
121
158
  return (
122
159
  <span
123
160
  data-slot="command-shortcut"
124
- className={cn('ml-auto text-xs tracking-widest text-muted-foreground', className)}
161
+ className={cn(
162
+ "ml-auto text-xs tracking-widest text-muted-foreground",
163
+ className,
164
+ )}
125
165
  {...props}
126
166
  />
127
- )
167
+ );
128
168
  }
129
169
 
130
170
  export {
@@ -137,4 +177,4 @@ export {
137
177
  CommandItem,
138
178
  CommandShortcut,
139
179
  CommandSeparator,
140
- }
180
+ };