@softize/opus 13.0.0 → 14.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 +81 -0
- package/PROMOTED.md +46 -0
- package/README.md +28 -19
- package/bin/cli.mjs +87 -216
- package/bin/lib/cli-shared.mjs +131 -0
- package/bin/lib/copy.mjs +276 -6
- package/bin/lib/db.mjs +16 -74
- package/bin/lib/gen-openapi.mjs +3 -3
- package/bin/lib/gen-runner.mjs +1 -1
- package/bin/lib/gen.mjs +14 -69
- package/bin/lib/mcp.mjs +3 -1
- package/bin/lib/seed.mjs +5 -62
- package/docs/code-style.md +4 -1
- package/docs/ownership-vs-shadcn-lock.md +2 -3
- package/docs/protocol.md +7 -7
- package/docs/releasing.md +8 -2
- package/package.json +7 -3
- package/registry/instructions/opus.md +3 -3
- package/registry/templates/app/package.json +1 -1
- package/registry/templates/app/src/App.tsx +11 -6
- package/registry/templates/app/src/main.tsx +4 -4
- package/src/audit/drivers/console.ts +1 -0
- package/src/auth/drivers/better-auth.ts +1 -0
- package/src/auth/drivers/jwt.ts +1 -0
- package/src/cache/drivers/memory.ts +1 -0
- package/src/client/drivers/fetch.ts +2 -1
- package/src/core/actions.ts +6 -1
- package/src/core/audit.ts +9 -3
- package/src/core/contracts.ts +7 -0
- package/src/core/domain.ts +1 -1
- package/src/core/errors.ts +18 -15
- package/src/core/index.ts +4 -2
- package/src/core/package-version.ts +26 -0
- package/src/core/reactions.ts +1 -1
- package/src/core/runtime.ts +33 -23
- package/src/core/schedules.ts +1 -1
- package/src/core/types.ts +5 -6
- package/src/dsl/eval.ts +2 -2
- package/src/dsl/kysely.ts +2 -2
- package/src/dsl/loads.ts +1 -1
- package/src/dsl/parser.ts +5 -5
- package/src/events/drivers/mitt.ts +1 -0
- package/src/mcp/index.ts +2 -1
- package/src/observability/drivers/opentelemetry.ts +1 -0
- package/src/queue/drivers/bullmq.ts +3 -3
- package/src/scheduler/drivers/node-cron.ts +3 -2
- package/src/scheduler/every.ts +7 -7
- package/src/schema/openapi.ts +3 -3
- package/src/seed/index.ts +29 -0
- package/src/server/drivers/fastify.ts +5 -2
- package/src/server/drivers/node.ts +9 -6
- package/src/server/index.ts +3 -1
- package/src/storage/drivers/fs.ts +1 -0
- package/src/testing/index.ts +3 -3
- package/src/ui/components/patterns/action-list-dialog.tsx +10 -3
- package/src/ui/components/patterns/confirm.tsx +2 -31
- package/src/ui/components/patterns/content-header.tsx +44 -137
- package/src/ui/components/patterns/data-state.tsx +42 -68
- package/src/ui/components/patterns/dock.tsx +20 -3
- package/src/ui/components/patterns/form.tsx +26 -22
- package/src/ui/components/patterns/list.tsx +18 -12
- package/src/ui/components/patterns/page-state.tsx +39 -51
- package/src/ui/components/patterns/page.tsx +37 -54
- package/src/ui/components/patterns/sidebar.tsx +17 -6
- package/src/ui/components/patterns/state-surface.tsx +148 -0
- package/src/ui/components/patterns/surface-header.tsx +119 -0
- package/src/ui/components/patterns/trigger.tsx +21 -25
- package/src/ui/components/patterns/view.tsx +29 -22
- package/src/ui/components/primitives/alert.tsx +1 -27
- package/src/ui/components/primitives/ask.tsx +3 -3
- package/src/ui/components/primitives/avatar.tsx +15 -5
- package/src/ui/components/primitives/badge.tsx +5 -41
- package/src/ui/components/primitives/breadcrumb.tsx +2 -2
- package/src/ui/components/primitives/button.tsx +39 -30
- package/src/ui/components/primitives/calendar.tsx +28 -2
- package/src/ui/components/primitives/carousel.tsx +3 -3
- package/src/ui/components/primitives/chat.tsx +1 -1
- package/src/ui/components/primitives/checkbox.tsx +1 -1
- package/src/ui/components/primitives/command.tsx +2 -2
- package/src/ui/components/primitives/control.ts +72 -0
- package/src/ui/components/primitives/copyable.tsx +1 -1
- package/src/ui/components/primitives/dialog.tsx +12 -7
- package/src/ui/components/primitives/dot.tsx +1 -25
- package/src/ui/components/primitives/drawer.tsx +10 -3
- package/src/ui/components/primitives/field.tsx +3 -3
- package/src/ui/components/primitives/icon-picker.tsx +3 -1
- package/src/ui/components/primitives/input-group.tsx +12 -9
- package/src/ui/components/primitives/input-otp.tsx +1 -1
- package/src/ui/components/primitives/input.tsx +2 -2
- package/src/ui/components/primitives/item.tsx +3 -1
- package/src/ui/components/primitives/menu.tsx +1 -7
- package/src/ui/components/primitives/pagination.tsx +16 -8
- package/src/ui/components/primitives/progress.tsx +32 -3
- package/src/ui/components/primitives/radio-group.tsx +1 -1
- package/src/ui/components/primitives/resizable.tsx +3 -1
- package/src/ui/components/primitives/select.tsx +6 -6
- package/src/ui/components/primitives/slider.tsx +5 -1
- package/src/ui/components/primitives/sonner.tsx +3 -0
- package/src/ui/components/primitives/spinner.tsx +13 -16
- package/src/ui/components/primitives/switch.tsx +5 -1
- package/src/ui/components/primitives/tabs.tsx +6 -3
- package/src/ui/components/primitives/textarea.tsx +1 -1
- package/src/ui/components/primitives/toggle.tsx +9 -4
- package/src/ui/components/primitives/tooltip.tsx +1 -0
- package/src/ui/docs/changelog.tsx +1 -1
- package/src/ui/docs/content/action-form.md +36 -3
- package/src/ui/docs/content/action-list-dialog.md +2 -2
- package/src/ui/docs/content/action-list.md +13 -2
- package/src/ui/docs/content/action-trigger.md +12 -5
- package/src/ui/docs/content/action-view.md +11 -3
- package/src/ui/docs/content/ask.md +11 -0
- package/src/ui/docs/content/avatar.md +7 -3
- package/src/ui/docs/content/button.md +30 -14
- package/src/ui/docs/content/calendar.md +13 -0
- package/src/ui/docs/content/card.md +26 -0
- package/src/ui/docs/content/chat.md +20 -0
- package/src/ui/docs/content/cli.md +71 -19
- package/src/ui/docs/content/communication.md +36 -0
- package/src/ui/docs/content/composer.md +15 -0
- package/src/ui/docs/content/content.md +17 -1
- package/src/ui/docs/content/copyable.md +8 -0
- package/src/ui/docs/content/data-state.md +17 -13
- package/src/ui/docs/content/detail.md +19 -1
- package/src/ui/docs/content/dialog.md +1 -4
- package/src/ui/docs/content/dictionary-value.md +9 -2
- package/src/ui/docs/content/dock.md +8 -0
- package/src/ui/docs/content/dot.md +8 -0
- package/src/ui/docs/content/empty.md +2 -2
- package/src/ui/docs/content/getting-started.md +2 -2
- package/src/ui/docs/content/icon-picker.md +11 -0
- package/src/ui/docs/content/input.md +1 -1
- package/src/ui/docs/content/item.md +1 -1
- package/src/ui/docs/content/label.md +7 -0
- package/src/ui/docs/content/menu.md +6 -0
- package/src/ui/docs/content/metric-card.md +13 -0
- package/src/ui/docs/content/page.md +20 -4
- package/src/ui/docs/content/pagination.md +11 -9
- package/src/ui/docs/content/popover.md +6 -0
- package/src/ui/docs/content/progress.md +8 -11
- package/src/ui/docs/content/select.md +5 -5
- package/src/ui/docs/content/semantic-context.md +5 -4
- package/src/ui/docs/content/sidebar.md +13 -47
- package/src/ui/docs/content/skeleton.md +6 -0
- package/src/ui/docs/content/spinner.md +9 -6
- package/src/ui/docs/content/split.md +21 -0
- package/src/ui/docs/content/switch.md +1 -1
- package/src/ui/docs/content/tabs.md +1 -1
- package/src/ui/docs/content/textarea.md +7 -0
- package/src/ui/docs/content/toggle.md +1 -1
- package/src/ui/docs/content/tokens.md +4 -4
- package/src/ui/docs/content/truncate.md +8 -0
- package/src/ui/docs/content/ui.md +14 -0
- package/src/ui/docs/doc-client.tsx +5 -5
- package/src/ui/docs/doc.tsx +26 -14
- package/src/ui/docs/registry.tsx +5 -5
- package/src/ui/docs/standalone.tsx +2 -2
- package/src/ui/drivers/react.tsx +12 -12
- package/src/ui/lib/action-errors.ts +45 -0
- package/src/ui/lib/zod-pt-br.ts +31 -4
- package/src/ui/meta.ts +8 -8
- package/src/ui/react.tsx +10 -16
- package/src/ui/theme.css +10 -8
- package/src/vite/design.ts +6 -18
- package/src/ui/components/patterns/shell-nav.tsx +0 -147
|
@@ -3,31 +3,44 @@ import { Slot } from '@radix-ui/react-slot'
|
|
|
3
3
|
|
|
4
4
|
import type { UiContext } from '../../../core/ui-context.ts'
|
|
5
5
|
import { cn } from '../../lib/cn.ts'
|
|
6
|
-
import
|
|
6
|
+
import {
|
|
7
|
+
controlBase,
|
|
8
|
+
controlGlyphClass,
|
|
9
|
+
controlHeight,
|
|
10
|
+
controlSquare,
|
|
11
|
+
focusRing,
|
|
12
|
+
type ControlScale,
|
|
13
|
+
type ControlShape,
|
|
14
|
+
} from './control.ts'
|
|
7
15
|
import { Spinner } from './spinner.tsx'
|
|
8
16
|
|
|
9
17
|
export type ButtonContext = Extract<UiContext, 'neutral' | 'primary' | 'danger'>
|
|
10
18
|
export type ButtonVariant = 'solid' | 'subtle' | 'outline' | 'ghost' | 'link'
|
|
11
|
-
|
|
12
|
-
export type ButtonSize =
|
|
19
|
+
/** A escala completa de control.ts: alturas de linha e quadrados só-ícone. */
|
|
20
|
+
export type ButtonSize = ControlScale
|
|
13
21
|
|
|
14
22
|
export interface ButtonStyleOptions {
|
|
23
|
+
/** Sem `context`, `solid`/`subtle` assumem `primary`; `outline`/`ghost`/`link` assumem `neutral`. */
|
|
15
24
|
context?: ButtonContext | null
|
|
16
|
-
|
|
25
|
+
/** Tratamento visual. */
|
|
26
|
+
variant?: ButtonVariant | null
|
|
17
27
|
size?: ButtonSize | null
|
|
18
28
|
shape?: ControlShape | null
|
|
19
29
|
}
|
|
20
30
|
|
|
21
31
|
const baseClasses =
|
|
22
|
-
'inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium outline-none transition-all
|
|
32
|
+
'inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium outline-none transition-all disabled:pointer-events-none disabled:opacity-50 active:brightness-90 aria-invalid:border-context-danger aria-invalid:ring-context-danger/20 [&_svg]:pointer-events-none [&_svg]:shrink-0'
|
|
23
33
|
|
|
34
|
+
/** Padding e ritmo por tamanho; altura e quadrado vêm da escala compartilhada. */
|
|
24
35
|
const sizeClasses: Record<ButtonSize, string> = {
|
|
25
|
-
|
|
26
|
-
sm:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
'icon-
|
|
30
|
-
'icon-
|
|
36
|
+
xs: `${controlHeight.xs} gap-1 px-2 text-xs has-[>svg]:px-1.5`,
|
|
37
|
+
sm: `${controlHeight.sm} gap-1.5 px-3 has-[>svg]:px-2.5`,
|
|
38
|
+
default: `${controlHeight.default} px-4 py-2 has-[>svg]:px-3`,
|
|
39
|
+
lg: `${controlHeight.lg} px-6 has-[>svg]:px-4`,
|
|
40
|
+
'icon-xs': controlSquare['icon-xs'],
|
|
41
|
+
'icon-sm': controlSquare['icon-sm'],
|
|
42
|
+
icon: controlSquare.icon,
|
|
43
|
+
'icon-lg': controlSquare['icon-lg'],
|
|
31
44
|
}
|
|
32
45
|
|
|
33
46
|
const solidClasses: Record<ButtonContext, string> = {
|
|
@@ -64,19 +77,17 @@ export function resolveButtonStyle({ context, variant }: ButtonStyleOptions = {}
|
|
|
64
77
|
context: ButtonContext
|
|
65
78
|
variant: ButtonVariant
|
|
66
79
|
} {
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (variant === 'destructive') return { context: 'danger', variant: 'solid' }
|
|
74
|
-
return { context: context ?? 'primary', variant: variant ?? 'solid' }
|
|
80
|
+
const resolvedVariant: ButtonVariant = variant ?? 'solid'
|
|
81
|
+
// Tratamentos de superfície (solid/subtle) pedem destaque; os leves (outline/ghost/link)
|
|
82
|
+
// são o chrome discreto da casa e só ganham cor quando o contexto é declarado.
|
|
83
|
+
const defaultContext: ButtonContext =
|
|
84
|
+
resolvedVariant === 'solid' || resolvedVariant === 'subtle' ? 'primary' : 'neutral'
|
|
85
|
+
return { context: context ?? defaultContext, variant: resolvedVariant }
|
|
75
86
|
}
|
|
76
87
|
|
|
77
88
|
/**
|
|
78
|
-
* Classes do Button. `context` seleciona a família semântica
|
|
79
|
-
*
|
|
89
|
+
* Classes do Button. `context` seleciona a família semântica, `variant` o tratamento visual e
|
|
90
|
+
* `size` a medida na escala compartilhada de control.ts.
|
|
80
91
|
*/
|
|
81
92
|
export function buttonVariants(options: ButtonStyleOptions = {}): string {
|
|
82
93
|
const resolved = resolveButtonStyle(options)
|
|
@@ -92,8 +103,10 @@ export function buttonVariants(options: ButtonStyleOptions = {}): string {
|
|
|
92
103
|
: linkClasses[resolved.context]
|
|
93
104
|
return cn(
|
|
94
105
|
baseClasses,
|
|
106
|
+
focusRing,
|
|
95
107
|
variantClasses,
|
|
96
108
|
sizeClasses[options.size ?? 'default'],
|
|
109
|
+
controlGlyphClass(options.size),
|
|
97
110
|
options.shape === 'pill' && 'rounded-full',
|
|
98
111
|
)
|
|
99
112
|
}
|
|
@@ -103,8 +116,9 @@ export interface ButtonProps extends React.ComponentProps<'button'>, ButtonStyle
|
|
|
103
116
|
asChild?: boolean
|
|
104
117
|
/** Estado de ação em andamento; mostra Spinner e desabilita o controle. */
|
|
105
118
|
busy?: boolean
|
|
106
|
-
/** Ícone à esquerda
|
|
107
|
-
|
|
119
|
+
/** Ícone à esquerda, como nó (`icon={<Plus />}`); o tamanho do glifo vem do `size`. No
|
|
120
|
+
* `busy`, o Spinner ocupa o mesmo espaço. */
|
|
121
|
+
icon?: React.ReactNode
|
|
108
122
|
}
|
|
109
123
|
|
|
110
124
|
export function Button({
|
|
@@ -115,14 +129,13 @@ export function Button({
|
|
|
115
129
|
shape,
|
|
116
130
|
asChild = false,
|
|
117
131
|
busy = false,
|
|
118
|
-
icon
|
|
132
|
+
icon,
|
|
119
133
|
disabled,
|
|
120
134
|
children,
|
|
121
135
|
...props
|
|
122
136
|
}: ButtonProps): React.ReactElement {
|
|
123
137
|
const Comp = asChild ? Slot : 'button'
|
|
124
138
|
const resolved = resolveButtonStyle({ context, variant })
|
|
125
|
-
const iconSize = size === 'sm' ? 'size-3' : 'size-4'
|
|
126
139
|
return (
|
|
127
140
|
<Comp
|
|
128
141
|
data-slot="button"
|
|
@@ -136,11 +149,7 @@ export function Button({
|
|
|
136
149
|
children
|
|
137
150
|
) : (
|
|
138
151
|
<>
|
|
139
|
-
{busy ? (
|
|
140
|
-
<Spinner size={size === 'sm' ? 'sm' : 'default'} />
|
|
141
|
-
) : Icon ? (
|
|
142
|
-
<Icon className={iconSize} />
|
|
143
|
-
) : null}
|
|
152
|
+
{busy ? <Spinner size={controlBase(size)} /> : icon}
|
|
144
153
|
{children}
|
|
145
154
|
</>
|
|
146
155
|
)}
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
ChevronLeftIcon,
|
|
5
5
|
ChevronRightIcon,
|
|
6
6
|
} from "lucide-react";
|
|
7
|
+
import { ptBR } from "date-fns/locale";
|
|
7
8
|
import {
|
|
8
9
|
DayPicker,
|
|
9
10
|
getDefaultClassNames,
|
|
@@ -13,12 +14,34 @@ import {
|
|
|
13
14
|
import { cn } from "../../lib/cn.ts";
|
|
14
15
|
import { Button, buttonVariants } from "./button.tsx";
|
|
15
16
|
|
|
17
|
+
/** Rótulos acessíveis da navegação em pt-BR — o react-day-picker só traduz nomes de mês e
|
|
18
|
+
* dia pelo `locale`; os aria-labels dos controles continuariam em inglês. */
|
|
19
|
+
const LABELS_PT_BR: NonNullable<React.ComponentProps<typeof DayPicker>["labels"]> = {
|
|
20
|
+
labelPrevious: () => "Mês anterior",
|
|
21
|
+
labelNext: () => "Próximo mês",
|
|
22
|
+
labelMonthDropdown: () => "Escolher o mês",
|
|
23
|
+
labelYearDropdown: () => "Escolher o ano",
|
|
24
|
+
labelDayButton: (date, modifiers, options) => {
|
|
25
|
+
const day = date.toLocaleDateString(options?.locale?.code ?? "pt-BR", {
|
|
26
|
+
weekday: "long",
|
|
27
|
+
day: "numeric",
|
|
28
|
+
month: "long",
|
|
29
|
+
year: "numeric",
|
|
30
|
+
});
|
|
31
|
+
const prefix = modifiers.today ? "Hoje, " : "";
|
|
32
|
+
const suffix = modifiers.selected ? ", selecionado" : "";
|
|
33
|
+
return `${prefix}${day}${suffix}`;
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
16
37
|
function Calendar({
|
|
17
38
|
className,
|
|
18
39
|
classNames,
|
|
19
40
|
showOutsideDays = true,
|
|
20
41
|
captionLayout = "label",
|
|
21
42
|
buttonVariant = "ghost",
|
|
43
|
+
locale = ptBR,
|
|
44
|
+
labels,
|
|
22
45
|
formatters,
|
|
23
46
|
components,
|
|
24
47
|
...props
|
|
@@ -30,6 +53,8 @@ function Calendar({
|
|
|
30
53
|
return (
|
|
31
54
|
<DayPicker
|
|
32
55
|
showOutsideDays={showOutsideDays}
|
|
56
|
+
locale={locale}
|
|
57
|
+
labels={{ ...LABELS_PT_BR, ...labels }}
|
|
33
58
|
className={cn(
|
|
34
59
|
"group/calendar bg-background p-3 [--cell-size:--spacing(8)] [[data-slot=card-body]_&]:bg-transparent [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
|
|
35
60
|
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
|
|
@@ -38,8 +63,9 @@ function Calendar({
|
|
|
38
63
|
)}
|
|
39
64
|
captionLayout={captionLayout}
|
|
40
65
|
formatters={{
|
|
41
|
-
|
|
42
|
-
|
|
66
|
+
// O dateLib já carrega o `locale` em vigor: o dropdown fala a mesma língua da legenda.
|
|
67
|
+
formatMonthDropdown: (date, dateLib) =>
|
|
68
|
+
dateLib?.format(date, "LLL") ?? date.toLocaleString("pt-BR", { month: "short" }),
|
|
43
69
|
...formatters,
|
|
44
70
|
}}
|
|
45
71
|
classNames={{
|
|
@@ -120,7 +120,7 @@ function Carousel({
|
|
|
120
120
|
onKeyDownCapture={handleKeyDown}
|
|
121
121
|
className={cn("relative", className)}
|
|
122
122
|
role="region"
|
|
123
|
-
aria-roledescription="
|
|
123
|
+
aria-roledescription="carrossel"
|
|
124
124
|
data-slot="carousel"
|
|
125
125
|
{...props}
|
|
126
126
|
>
|
|
@@ -194,7 +194,7 @@ function CarouselPrevious({
|
|
|
194
194
|
{...props}
|
|
195
195
|
>
|
|
196
196
|
<ArrowLeft />
|
|
197
|
-
<span className="sr-only">
|
|
197
|
+
<span className="sr-only">Slide anterior</span>
|
|
198
198
|
</Button>
|
|
199
199
|
)
|
|
200
200
|
}
|
|
@@ -224,7 +224,7 @@ function CarouselNext({
|
|
|
224
224
|
{...props}
|
|
225
225
|
>
|
|
226
226
|
<ArrowRight />
|
|
227
|
-
<span className="sr-only">
|
|
227
|
+
<span className="sr-only">Próximo slide</span>
|
|
228
228
|
</Button>
|
|
229
229
|
)
|
|
230
230
|
}
|
|
@@ -193,7 +193,7 @@ const ChatTranscript = React.memo(function ChatTranscript({
|
|
|
193
193
|
return (
|
|
194
194
|
<div
|
|
195
195
|
key={i}
|
|
196
|
-
className="max-w-[88%] self-start rounded-xl border border-
|
|
196
|
+
className="max-w-[88%] self-start rounded-xl border border-context-danger-border bg-context-danger-subtle px-3.5 py-2.5 text-sm text-context-danger-emphasis"
|
|
197
197
|
>
|
|
198
198
|
{item.content}
|
|
199
199
|
</div>
|
|
@@ -12,7 +12,7 @@ function Checkbox({
|
|
|
12
12
|
<CheckboxPrimitive.Root
|
|
13
13
|
data-slot="checkbox"
|
|
14
14
|
className={cn(
|
|
15
|
-
"peer size-4 shrink-0 rounded-[0.25rem] border border-input transition-shadow outline-none focus-visible:border-ring focus-visible:ring-[0.1875rem] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-
|
|
15
|
+
"peer size-4 shrink-0 rounded-[0.25rem] border border-input transition-shadow outline-none 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 data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:bg-input/30 dark:aria-invalid:ring-context-danger/40 dark:data-[state=checked]:bg-primary",
|
|
16
16
|
className
|
|
17
17
|
)}
|
|
18
18
|
{...props}
|
|
@@ -28,8 +28,8 @@ function Command({
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
function CommandDialog({
|
|
31
|
-
title = "
|
|
32
|
-
description = "
|
|
31
|
+
title = "Paleta de comandos",
|
|
32
|
+
description = "Busque um comando para executar…",
|
|
33
33
|
children,
|
|
34
34
|
className,
|
|
35
35
|
showCloseButton = true,
|
|
@@ -1,2 +1,74 @@
|
|
|
1
1
|
/** Geometria compartilhada pelos controles inline da casa. */
|
|
2
2
|
export type ControlShape = 'default' | 'pill'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A escala única de `size` dos controles (14.0.0). Um nome, uma medida — em qualquer
|
|
6
|
+
* componente que aceite `size`, `sm` é a mesma altura de linha e `icon-sm` é o mesmo quadrado.
|
|
7
|
+
* Cada componente aceita o subconjunto coerente com seu papel, nunca um nome com outra medida.
|
|
8
|
+
*
|
|
9
|
+
* - Altura de linha (`xs` · `sm` · `default` · `lg`): 1.5 · 2 · 2.25 · 2.5rem.
|
|
10
|
+
* - Quadrado só-ícone (`icon-xs` · `icon-sm` · `icon` · `icon-lg`): 1.5 · 1.75 · 2.25 · 2.5rem.
|
|
11
|
+
* `icon-sm` é 1.75rem de propósito: é o degrau da ação que mora dentro de uma linha densa
|
|
12
|
+
* (o que os consumidores forçavam com `size-7`), menor que o botão de texto `sm` ao lado.
|
|
13
|
+
* - Glifo (o svg dentro do controle daquele tamanho): 0.875 · 0.875 · 1 · 1.25rem. O Spinner
|
|
14
|
+
* usa esta medida, por isso `busy` substitui o ícone sem mexer na largura do botão.
|
|
15
|
+
*/
|
|
16
|
+
export type ControlSize = 'xs' | 'sm' | 'default' | 'lg'
|
|
17
|
+
export type ControlIconSize = 'icon-xs' | 'icon-sm' | 'icon' | 'icon-lg'
|
|
18
|
+
export type ControlScale = ControlSize | ControlIconSize
|
|
19
|
+
|
|
20
|
+
export const controlHeight: Record<ControlSize, string> = {
|
|
21
|
+
xs: 'h-6',
|
|
22
|
+
sm: 'h-8',
|
|
23
|
+
default: 'h-9',
|
|
24
|
+
lg: 'h-10',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const controlSquare: Record<ControlIconSize, string> = {
|
|
28
|
+
'icon-xs': 'size-6',
|
|
29
|
+
'icon-sm': 'size-7',
|
|
30
|
+
icon: 'size-9',
|
|
31
|
+
'icon-lg': 'size-10',
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const controlGlyph: Record<ControlSize, string> = {
|
|
35
|
+
xs: 'size-3.5',
|
|
36
|
+
sm: 'size-3.5',
|
|
37
|
+
default: 'size-4',
|
|
38
|
+
lg: 'size-5',
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** A altura de linha por trás de um tamanho da escala (`icon-sm` → `sm`). */
|
|
42
|
+
export function controlBase(size: ControlScale | null | undefined): ControlSize {
|
|
43
|
+
switch (size) {
|
|
44
|
+
case 'icon-xs':
|
|
45
|
+
return 'xs'
|
|
46
|
+
case 'icon-sm':
|
|
47
|
+
return 'sm'
|
|
48
|
+
case 'icon-lg':
|
|
49
|
+
return 'lg'
|
|
50
|
+
case 'icon':
|
|
51
|
+
case null:
|
|
52
|
+
case undefined:
|
|
53
|
+
return 'default'
|
|
54
|
+
default:
|
|
55
|
+
return size
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Fallback de tamanho do svg descendente, respeitando um `size-*` explícito no próprio ícone. */
|
|
60
|
+
export function controlGlyphClass(size: ControlScale | null | undefined): string {
|
|
61
|
+
return `[&_svg:not([class*=size-])]:${controlGlyph[controlBase(size)]}`
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Anel de foco da casa: fino (0.1875rem), translúcido e encostado na borda — sem offset.
|
|
66
|
+
* Todo controle focável usa esta constante; espessuras próprias (`ring-2`, `ring-4`) ou
|
|
67
|
+
* `ring-offset-*` quebram a leitura de "foco" que o tema calibrou (ver theme.css, `--ring`).
|
|
68
|
+
*/
|
|
69
|
+
export const focusRing =
|
|
70
|
+
'focus-visible:border-ring focus-visible:ring-[0.1875rem] focus-visible:ring-ring/50'
|
|
71
|
+
|
|
72
|
+
/** O mesmo anel, disparado por um descendente focado (grupos que envolvem um campo). */
|
|
73
|
+
export const focusRingWithin =
|
|
74
|
+
'focus-within:border-ring focus-within:ring-[0.1875rem] focus-within:ring-ring/50'
|
|
@@ -44,7 +44,7 @@ export function Copyable({
|
|
|
44
44
|
{...props}
|
|
45
45
|
>
|
|
46
46
|
{children}
|
|
47
|
-
{copied ? <CheckIcon className="size-3.5 text-
|
|
47
|
+
{copied ? <CheckIcon className="size-3.5 text-context-success-emphasis" /> : <CopyIcon className="size-3.5" />}
|
|
48
48
|
</button>
|
|
49
49
|
)
|
|
50
50
|
}
|
|
@@ -3,6 +3,7 @@ import * as DialogPrimitive from '@radix-ui/react-dialog'
|
|
|
3
3
|
import { AlertDialog as AlertDialogPrimitive } from 'radix-ui'
|
|
4
4
|
import { XIcon } from 'lucide-react'
|
|
5
5
|
import { cn } from '../../lib/cn.ts'
|
|
6
|
+
import { focusRing } from './control.ts'
|
|
6
7
|
|
|
7
8
|
export type DialogMode = 'default' | 'alert'
|
|
8
9
|
|
|
@@ -110,25 +111,26 @@ export function DialogClose({
|
|
|
110
111
|
)
|
|
111
112
|
}
|
|
112
113
|
|
|
114
|
+
/** Véu compartilhado por Dialog e Drawer: mesmo escurecimento, desfoque e animação. */
|
|
115
|
+
export const overlayClasses =
|
|
116
|
+
'fixed inset-0 z-50 bg-black/30 backdrop-blur-xs data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0'
|
|
117
|
+
|
|
113
118
|
export function DialogOverlay({
|
|
114
119
|
className,
|
|
115
120
|
...props
|
|
116
121
|
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
|
|
117
122
|
const { mode } = useDialogContext()
|
|
118
|
-
const
|
|
119
|
-
'fixed inset-0 z-50 bg-black/30 backdrop-blur-xs data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0',
|
|
120
|
-
className,
|
|
121
|
-
)
|
|
123
|
+
const overlay = cn(overlayClasses, className)
|
|
122
124
|
return mode === 'alert' ? (
|
|
123
125
|
<AlertDialogPrimitive.Overlay
|
|
124
126
|
data-slot="dialog-overlay"
|
|
125
|
-
className={
|
|
127
|
+
className={overlay}
|
|
126
128
|
{...props}
|
|
127
129
|
/>
|
|
128
130
|
) : (
|
|
129
131
|
<DialogPrimitive.Overlay
|
|
130
132
|
data-slot="dialog-overlay"
|
|
131
|
-
className={
|
|
133
|
+
className={overlay}
|
|
132
134
|
{...props}
|
|
133
135
|
/>
|
|
134
136
|
)
|
|
@@ -181,7 +183,10 @@ export function DialogContent({
|
|
|
181
183
|
{showClose && (
|
|
182
184
|
<DialogPrimitive.Close
|
|
183
185
|
data-slot="dialog-close"
|
|
184
|
-
className=
|
|
186
|
+
className={cn(
|
|
187
|
+
"absolute right-4 top-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus-visible:outline-hidden disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
188
|
+
focusRing,
|
|
189
|
+
)}
|
|
185
190
|
>
|
|
186
191
|
<XIcon />
|
|
187
192
|
<span className="sr-only">Fechar</span>
|
|
@@ -5,17 +5,9 @@ import { cn } from '../../lib/cn.ts'
|
|
|
5
5
|
|
|
6
6
|
export type DotContext = UiContext
|
|
7
7
|
export type DotVariant = 'solid' | 'outline'
|
|
8
|
-
export type LegacyDotVariant =
|
|
9
|
-
| 'default'
|
|
10
|
-
| 'secondary'
|
|
11
|
-
| 'destructive'
|
|
12
|
-
| 'success'
|
|
13
|
-
| 'warning'
|
|
14
|
-
| 'info'
|
|
15
|
-
|
|
16
8
|
interface DotStyleOptions {
|
|
17
9
|
context?: DotContext | null
|
|
18
|
-
variant?: DotVariant |
|
|
10
|
+
variant?: DotVariant | null
|
|
19
11
|
}
|
|
20
12
|
|
|
21
13
|
const solidClasses: Record<DotContext, string> = {
|
|
@@ -40,22 +32,6 @@ export function resolveDotStyle({ context, variant }: DotStyleOptions = {}): {
|
|
|
40
32
|
context: DotContext
|
|
41
33
|
variant: DotVariant
|
|
42
34
|
} {
|
|
43
|
-
const legacy =
|
|
44
|
-
variant === 'default' ||
|
|
45
|
-
variant === 'secondary' ||
|
|
46
|
-
variant === 'destructive' ||
|
|
47
|
-
variant === 'success' ||
|
|
48
|
-
variant === 'warning' ||
|
|
49
|
-
variant === 'info'
|
|
50
|
-
if (context != null && legacy) {
|
|
51
|
-
throw new Error('Dot não permite combinar context com uma variante semântica legada.')
|
|
52
|
-
}
|
|
53
|
-
if (variant === 'default') return { context: 'primary', variant: 'solid' }
|
|
54
|
-
if (variant === 'secondary') return { context: 'neutral', variant: 'solid' }
|
|
55
|
-
if (variant === 'destructive') return { context: 'danger', variant: 'solid' }
|
|
56
|
-
if (variant === 'success' || variant === 'warning' || variant === 'info') {
|
|
57
|
-
return { context: variant, variant: 'solid' }
|
|
58
|
-
}
|
|
59
35
|
return { context: context ?? 'neutral', variant: variant ?? 'solid' }
|
|
60
36
|
}
|
|
61
37
|
|
|
@@ -3,6 +3,8 @@ import { XIcon } from "lucide-react";
|
|
|
3
3
|
import { Dialog as DrawerPrimitive } from "radix-ui";
|
|
4
4
|
|
|
5
5
|
import { cn } from "../../lib/cn.ts";
|
|
6
|
+
import { focusRing } from "./control.ts";
|
|
7
|
+
import { overlayClasses } from "./dialog.tsx";
|
|
6
8
|
|
|
7
9
|
function Drawer({
|
|
8
10
|
...props
|
|
@@ -36,7 +38,7 @@ function DrawerOverlay({
|
|
|
36
38
|
<DrawerPrimitive.Overlay
|
|
37
39
|
data-slot="drawer-overlay"
|
|
38
40
|
className={cn(
|
|
39
|
-
|
|
41
|
+
overlayClasses,
|
|
40
42
|
className,
|
|
41
43
|
)}
|
|
42
44
|
{...props}
|
|
@@ -75,9 +77,14 @@ function DrawerContent({
|
|
|
75
77
|
>
|
|
76
78
|
{children}
|
|
77
79
|
{showCloseButton && (
|
|
78
|
-
<DrawerPrimitive.Close
|
|
80
|
+
<DrawerPrimitive.Close
|
|
81
|
+
className={cn(
|
|
82
|
+
"absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus-visible:outline-hidden disabled:pointer-events-none data-[state=open]:bg-secondary",
|
|
83
|
+
focusRing,
|
|
84
|
+
)}
|
|
85
|
+
>
|
|
79
86
|
<XIcon className="size-4" />
|
|
80
|
-
<span className="sr-only">
|
|
87
|
+
<span className="sr-only">Fechar</span>
|
|
81
88
|
</DrawerPrimitive.Close>
|
|
82
89
|
)}
|
|
83
90
|
</DrawerPrimitive.Content>
|
|
@@ -53,7 +53,7 @@ function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
const fieldVariants = cva(
|
|
56
|
-
"group/field flex w-full data-[invalid=true]:text-
|
|
56
|
+
"group/field flex w-full data-[invalid=true]:text-context-danger-emphasis",
|
|
57
57
|
{
|
|
58
58
|
variants: {
|
|
59
59
|
orientation: {
|
|
@@ -113,7 +113,7 @@ function FieldLabel({
|
|
|
113
113
|
<Label
|
|
114
114
|
data-slot="field-label"
|
|
115
115
|
className={cn(
|
|
116
|
-
"group/field-label peer/field-label flex w-fit gap-2 text-xs leading-snug font-medium text-muted-foreground group-data-[invalid=true]/field:text-
|
|
116
|
+
"group/field-label peer/field-label flex w-fit gap-2 text-xs leading-snug font-medium text-muted-foreground group-data-[invalid=true]/field:text-context-danger-emphasis group-data-[disabled=true]/field:opacity-50",
|
|
117
117
|
"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&>*]:data-[slot=field]:p-4",
|
|
118
118
|
"has-[>[data-slot=field]]:has-data-[state=checked]:border-primary has-[>[data-slot=field]]:has-data-[state=checked]:bg-primary/5 dark:has-[>[data-slot=field]]:has-data-[state=checked]:bg-primary/10",
|
|
119
119
|
className
|
|
@@ -224,7 +224,7 @@ function FieldError({
|
|
|
224
224
|
<div
|
|
225
225
|
role="alert"
|
|
226
226
|
data-slot="field-error"
|
|
227
|
-
className={cn("text-sm font-normal text-
|
|
227
|
+
className={cn("text-sm font-normal text-context-danger-emphasis", className)}
|
|
228
228
|
{...props}
|
|
229
229
|
>
|
|
230
230
|
{content}
|
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
type LucideIcon,
|
|
47
47
|
} from 'lucide-react'
|
|
48
48
|
import { cn } from '../../lib/cn.ts'
|
|
49
|
+
import { focusRing } from './control.ts'
|
|
49
50
|
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from './command.tsx'
|
|
50
51
|
import { Popover, PopoverContent, PopoverTrigger } from './popover.tsx'
|
|
51
52
|
|
|
@@ -141,7 +142,8 @@ export function IconPicker({
|
|
|
141
142
|
aria-describedby={ariaDescribedBy}
|
|
142
143
|
data-slot="icon-picker"
|
|
143
144
|
className={cn(
|
|
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]
|
|
145
|
+
'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] disabled:cursor-not-allowed disabled:opacity-50 dark:bg-input/30',
|
|
146
|
+
focusRing,
|
|
145
147
|
className,
|
|
146
148
|
)}
|
|
147
149
|
>
|
|
@@ -3,7 +3,7 @@ import { cva, type VariantProps } from "class-variance-authority"
|
|
|
3
3
|
|
|
4
4
|
import { cn } from '../../lib/cn.ts'
|
|
5
5
|
import type { ControlShape } from './control.ts'
|
|
6
|
-
import { Button } from './button.tsx'
|
|
6
|
+
import { Button, type ButtonProps } from './button.tsx'
|
|
7
7
|
import { Input } from './input.tsx'
|
|
8
8
|
import { Textarea } from './textarea.tsx'
|
|
9
9
|
|
|
@@ -27,7 +27,7 @@ function InputGroup({ className, shape = 'default', ...props }: React.ComponentP
|
|
|
27
27
|
"has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-[0.1875rem] has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50",
|
|
28
28
|
|
|
29
29
|
// Error state.
|
|
30
|
-
"has-[[data-slot][aria-invalid=true]]:border-
|
|
30
|
+
"has-[[data-slot][aria-invalid=true]]:border-context-danger has-[[data-slot][aria-invalid=true]]:ring-context-danger/20 dark:has-[[data-slot][aria-invalid=true]]:ring-context-danger/40",
|
|
31
31
|
|
|
32
32
|
className
|
|
33
33
|
)}
|
|
@@ -79,16 +79,16 @@ function InputGroupAddon({
|
|
|
79
79
|
)
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/** Só o acabamento por tamanho: altura, quadrado e glifo vêm da escala de control.ts via Button. */
|
|
82
83
|
const inputGroupButtonVariants = cva(
|
|
83
84
|
"flex items-center gap-2 text-sm shadow-none",
|
|
84
85
|
{
|
|
85
86
|
variants: {
|
|
86
87
|
size: {
|
|
87
|
-
xs: "
|
|
88
|
-
sm: "
|
|
89
|
-
"icon-xs":
|
|
90
|
-
|
|
91
|
-
"icon-sm": "size-8 p-0 has-[>svg]:p-0",
|
|
88
|
+
xs: "rounded-[calc(var(--radius)-0.3125rem)] has-[>svg]:px-2",
|
|
89
|
+
sm: "rounded-md",
|
|
90
|
+
"icon-xs": "rounded-[calc(var(--radius)-0.3125rem)] p-0 has-[>svg]:p-0",
|
|
91
|
+
"icon-sm": "p-0 has-[>svg]:p-0",
|
|
92
92
|
},
|
|
93
93
|
},
|
|
94
94
|
defaultVariants: {
|
|
@@ -97,19 +97,22 @@ const inputGroupButtonVariants = cva(
|
|
|
97
97
|
}
|
|
98
98
|
)
|
|
99
99
|
|
|
100
|
+
/** Subconjunto da escala que cabe dentro de um campo: `xs`, `sm`, `icon-xs` e `icon-sm`. */
|
|
101
|
+
type InputGroupButtonSize = Extract<NonNullable<ButtonProps['size']>, 'xs' | 'sm' | 'icon-xs' | 'icon-sm'>
|
|
102
|
+
|
|
100
103
|
function InputGroupButton({
|
|
101
104
|
className,
|
|
102
105
|
type = "button",
|
|
103
106
|
variant = "ghost",
|
|
104
107
|
size = "xs",
|
|
105
108
|
...props
|
|
106
|
-
}: Omit<
|
|
107
|
-
VariantProps<typeof inputGroupButtonVariants>) {
|
|
109
|
+
}: Omit<ButtonProps, 'size'> & { size?: InputGroupButtonSize }) {
|
|
108
110
|
return (
|
|
109
111
|
<Button
|
|
110
112
|
type={type}
|
|
111
113
|
data-size={size}
|
|
112
114
|
variant={variant}
|
|
115
|
+
size={size}
|
|
113
116
|
className={cn(inputGroupButtonVariants({ size }), className)}
|
|
114
117
|
{...props}
|
|
115
118
|
/>
|
|
@@ -49,7 +49,7 @@ function InputOTPSlot({
|
|
|
49
49
|
data-slot="input-otp-slot"
|
|
50
50
|
data-active={isActive}
|
|
51
51
|
className={cn(
|
|
52
|
-
"relative flex h-9 w-9 items-center justify-center border-y border-r border-input text-sm transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md aria-invalid:border-
|
|
52
|
+
"relative flex h-9 w-9 items-center justify-center border-y border-r border-input text-sm transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md aria-invalid:border-context-danger data-[active=true]:z-10 data-[active=true]:border-ring data-[active=true]:ring-[0.1875rem] data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:border-context-danger data-[active=true]:aria-invalid:ring-context-danger/20 dark:bg-input/30 dark:data-[active=true]:aria-invalid:ring-context-danger/40",
|
|
53
53
|
className
|
|
54
54
|
)}
|
|
55
55
|
{...props}
|
|
@@ -14,7 +14,7 @@ export interface InputProps extends React.ComponentProps<"input"> {
|
|
|
14
14
|
|
|
15
15
|
// O <input> CRU (sem adorno): ELE é o campo — borda, foco, ref e className moram nele.
|
|
16
16
|
const bareInput =
|
|
17
|
-
"h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30 focus-visible:border-ring focus-visible:ring-[0.1875rem] focus-visible:ring-ring/50 aria-invalid:border-
|
|
17
|
+
"h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30 focus-visible:border-ring focus-visible:ring-[0.1875rem] focus-visible:ring-ring/50 aria-invalid:border-context-danger aria-invalid:ring-context-danger/20 dark:aria-invalid:ring-context-danger/40"
|
|
18
18
|
|
|
19
19
|
function Input({ className, type, icon, trailing, ...props }: InputProps) {
|
|
20
20
|
// Sem adorno: o <input> cru de sempre — retrocompatível (refs, forms, InputGroupInput).
|
|
@@ -33,7 +33,7 @@ function Input({ className, type, icon, trailing, ...props }: InputProps) {
|
|
|
33
33
|
className={cn(
|
|
34
34
|
"flex h-9 w-full min-w-0 items-center gap-1.5 rounded-md border border-input bg-transparent px-3 text-base transition-[color,box-shadow] outline-none md:text-sm dark:bg-input/30",
|
|
35
35
|
"has-[input:focus-visible]:border-ring has-[input:focus-visible]:ring-[0.1875rem] has-[input:focus-visible]:ring-ring/50",
|
|
36
|
-
"has-[input[aria-invalid=true]]:border-
|
|
36
|
+
"has-[input[aria-invalid=true]]:border-context-danger has-[input[aria-invalid=true]]:ring-context-danger/20 dark:has-[input[aria-invalid=true]]:ring-context-danger/40",
|
|
37
37
|
"has-[input:disabled]:pointer-events-none has-[input:disabled]:opacity-50",
|
|
38
38
|
className,
|
|
39
39
|
)}
|
|
@@ -3,6 +3,7 @@ import { cva, type VariantProps } from "class-variance-authority";
|
|
|
3
3
|
import { Slot } from "radix-ui";
|
|
4
4
|
|
|
5
5
|
import { cn } from "../../lib/cn.ts";
|
|
6
|
+
import type { ControlSize } from "./control.ts";
|
|
6
7
|
import { Separator } from "./separator.tsx";
|
|
7
8
|
|
|
8
9
|
const itemGroupVariants = cva(
|
|
@@ -72,10 +73,11 @@ const itemVariants = cva(
|
|
|
72
73
|
outline: "border-border",
|
|
73
74
|
muted: "bg-muted/50",
|
|
74
75
|
},
|
|
76
|
+
// Nomes da escala de control.ts; a linha não tem altura fixa, `sm` aperta o respiro.
|
|
75
77
|
size: {
|
|
76
78
|
default: "gap-4 p-4",
|
|
77
79
|
sm: "gap-2.5 px-4 py-3",
|
|
78
|
-
},
|
|
80
|
+
} satisfies Partial<Record<ControlSize, string>>,
|
|
79
81
|
},
|
|
80
82
|
defaultVariants: { variant: "default", size: "default" },
|
|
81
83
|
},
|