@softize/opus 8.9.1 → 9.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 +39 -0
- package/bin/cli.mjs +2 -2
- package/bin/lib/check.mjs +236 -2
- package/docs/consumer-upgrade-propagation.md +56 -0
- package/docs/elevation-scale.md +55 -0
- package/docs/ownership-vs-shadcn-lock.md +4 -3
- package/package.json +1 -1
- package/registry/instructions/opus.md +2 -0
- package/src/ui/components/primitives/alert-dialog.tsx +1 -1
- package/src/ui/components/primitives/button-group.tsx +26 -1
- package/src/ui/components/primitives/button.tsx +10 -2
- package/src/ui/components/primitives/card.tsx +1 -1
- package/src/ui/components/primitives/chat.tsx +6 -2
- package/src/ui/components/primitives/composer.tsx +3 -2
- package/src/ui/components/primitives/control.ts +2 -0
- package/src/ui/components/primitives/dialog.tsx +1 -1
- package/src/ui/components/primitives/drawer.tsx +1 -1
- package/src/ui/components/primitives/input-group.tsx +4 -2
- package/src/ui/components/primitives/menu.tsx +2 -2
- package/src/ui/components/primitives/popover.tsx +1 -1
- package/src/ui/components/primitives/select.tsx +27 -9
- package/src/ui/components/primitives/tabs.tsx +6 -5
- package/src/ui/components/primitives/toggle-group.tsx +13 -2
- package/src/ui/components/primitives/toggle.tsx +8 -1
- package/src/ui/docs/content/card.md +2 -2
- package/src/ui/docs/content/composer.md +1 -1
- package/src/ui/docs/content/tokens.md +18 -18
- package/src/ui/meta.ts +5 -5
- package/src/ui/react.tsx +1 -0
- package/src/ui/theme.css +16 -27
|
@@ -54,7 +54,7 @@ export function DialogContent({
|
|
|
54
54
|
// Superfície PURA: só a casca (borda/bg/raio/sombra) + coluna flex com teto de
|
|
55
55
|
// altura. O padding mora nos SLOTS (Header/Body/Footer), não aqui — assim ninguém
|
|
56
56
|
// precisa negar margem pra escapar de um p-6 embutido.
|
|
57
|
-
"fixed left-[50%] top-[50%] z-50 flex max-h-[85vh] w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] flex-col overflow-hidden rounded-dialog
|
|
57
|
+
"fixed left-[50%] top-[50%] z-50 flex max-h-[85vh] w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] flex-col overflow-hidden rounded-dialog border border-border bg-card shadow-lg duration-200 outline-none data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-lg",
|
|
58
58
|
className,
|
|
59
59
|
)}
|
|
60
60
|
{...props}
|
|
@@ -58,7 +58,7 @@ function DrawerContent({
|
|
|
58
58
|
<DrawerPrimitive.Content
|
|
59
59
|
data-slot="drawer-content"
|
|
60
60
|
className={cn(
|
|
61
|
-
"fixed z-50 flex flex-col gap-4 bg-background shadow-
|
|
61
|
+
"fixed z-50 flex flex-col gap-4 bg-background shadow-lg transition ease-in-out data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:animate-in data-[state=open]:duration-500",
|
|
62
62
|
side === "right" &&
|
|
63
63
|
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
|
|
64
64
|
side === "left" &&
|
|
@@ -2,17 +2,19 @@ import * as React from "react"
|
|
|
2
2
|
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
3
|
|
|
4
4
|
import { cn } from '../../lib/cn.ts'
|
|
5
|
+
import type { ControlShape } from './control.ts'
|
|
5
6
|
import { Button } from './button.tsx'
|
|
6
7
|
import { Input } from './input.tsx'
|
|
7
8
|
import { Textarea } from './textarea.tsx'
|
|
8
9
|
|
|
9
|
-
function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
10
|
+
function InputGroup({ className, shape = 'default', ...props }: React.ComponentProps<"div"> & { shape?: ControlShape }) {
|
|
10
11
|
return (
|
|
11
12
|
<div
|
|
12
13
|
data-slot="input-group"
|
|
14
|
+
data-shape={shape}
|
|
13
15
|
role="group"
|
|
14
16
|
className={cn(
|
|
15
|
-
"group/input-group relative flex w-full items-center rounded-md border border-input transition-[color,box-shadow] outline-none dark:bg-input/30",
|
|
17
|
+
"group/input-group relative flex w-full items-center rounded-md border border-input transition-[color,box-shadow] outline-none data-[shape=pill]:rounded-full dark:bg-input/30",
|
|
16
18
|
"h-9 min-w-0 has-[>textarea]:h-auto",
|
|
17
19
|
|
|
18
20
|
// Variants based on alignment.
|
|
@@ -45,7 +45,7 @@ function MenuContent({
|
|
|
45
45
|
data-slot="menu-content"
|
|
46
46
|
sideOffset={sideOffset}
|
|
47
47
|
className={cn(
|
|
48
|
-
"z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[10rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-popover bg-popover p-1 text-popover-foreground
|
|
48
|
+
"z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[10rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-popover border border-border bg-popover p-1 text-popover-foreground shadow-md will-change-[transform,opacity] data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
|
49
49
|
className
|
|
50
50
|
)}
|
|
51
51
|
{...props}
|
|
@@ -233,7 +233,7 @@ function MenuSubContent({
|
|
|
233
233
|
<MenuPrimitive.SubContent
|
|
234
234
|
data-slot="menu-sub-content"
|
|
235
235
|
className={cn(
|
|
236
|
-
"z-50 min-w-[10rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-popover bg-popover p-1 text-popover-foreground
|
|
236
|
+
"z-50 min-w-[10rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-popover border border-border bg-popover p-1 text-popover-foreground shadow-md will-change-[transform,opacity] data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
|
237
237
|
className
|
|
238
238
|
)}
|
|
239
239
|
{...props}
|
|
@@ -34,7 +34,7 @@ function PopoverContent({
|
|
|
34
34
|
align={align}
|
|
35
35
|
sideOffset={sideOffset}
|
|
36
36
|
className={cn(
|
|
37
|
-
"z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-popover bg-popover p-4 text-popover-foreground
|
|
37
|
+
"z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-popover border border-border bg-popover p-4 text-popover-foreground shadow-md outline-hidden data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
|
38
38
|
className
|
|
39
39
|
)}
|
|
40
40
|
{...props}
|
|
@@ -25,6 +25,7 @@ import { Check, ChevronDownIcon, SearchIcon, X } from 'lucide-react'
|
|
|
25
25
|
import { Command as CommandPrimitive } from 'cmdk'
|
|
26
26
|
|
|
27
27
|
import { cn } from '../../lib/cn.ts'
|
|
28
|
+
import type { ControlShape } from './control.ts'
|
|
28
29
|
import { CommandEmpty, CommandGroup, CommandItem, CommandList } from './command.tsx'
|
|
29
30
|
import { Popover, PopoverAnchor, PopoverContent } from './popover.tsx'
|
|
30
31
|
|
|
@@ -57,6 +58,8 @@ interface SelectBaseProps {
|
|
|
57
58
|
icon?: React.ReactNode
|
|
58
59
|
/** Altura: `default` (h-9, a do Input/Button) ou `sm` (h-8) pra toolbar densa. */
|
|
59
60
|
size?: 'sm' | 'default'
|
|
61
|
+
/** Geometria do controle; `pill` preserva a variante visual escolhida. */
|
|
62
|
+
shape?: ControlShape
|
|
60
63
|
'aria-label'?: string
|
|
61
64
|
}
|
|
62
65
|
|
|
@@ -84,8 +87,8 @@ interface SelectCustomBase extends SelectBaseProps {
|
|
|
84
87
|
loading?: boolean
|
|
85
88
|
/** X no campo quando há seleção — limpa num clique. Bom em filtro. */
|
|
86
89
|
clearable?: boolean
|
|
87
|
-
/** `ghost`: sem borda nem fundo
|
|
88
|
-
variant?: 'default' | 'ghost'
|
|
90
|
+
/** `outline`: compacto com borda; `ghost`: compacto sem borda nem fundo. */
|
|
91
|
+
variant?: 'default' | 'outline' | 'ghost'
|
|
89
92
|
/** Placeholder enquanto se digita a busca (cai pro `placeholder`). */
|
|
90
93
|
searchPlaceholder?: string
|
|
91
94
|
emptyText?: string
|
|
@@ -123,6 +126,7 @@ function NativeSelect({
|
|
|
123
126
|
id,
|
|
124
127
|
icon,
|
|
125
128
|
size = 'default',
|
|
129
|
+
shape = 'default',
|
|
126
130
|
value,
|
|
127
131
|
onChange,
|
|
128
132
|
...rest
|
|
@@ -142,13 +146,14 @@ function NativeSelect({
|
|
|
142
146
|
<select
|
|
143
147
|
data-slot="select"
|
|
144
148
|
data-size={size}
|
|
149
|
+
data-shape={shape}
|
|
145
150
|
id={id}
|
|
146
151
|
disabled={disabled}
|
|
147
152
|
value={value}
|
|
148
153
|
aria-label={rest['aria-label']}
|
|
149
154
|
onChange={(e) => onChange(e.target.value)}
|
|
150
155
|
className={cn(
|
|
151
|
-
"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 dark:bg-input/30 dark:hover:bg-input/50",
|
|
156
|
+
"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",
|
|
152
157
|
'focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50',
|
|
153
158
|
'aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40',
|
|
154
159
|
icon ? 'pl-9' : '',
|
|
@@ -199,6 +204,7 @@ function CustomSelect(props: Exclude<SelectProps, SelectNativeProps>): React.Rea
|
|
|
199
204
|
id,
|
|
200
205
|
icon,
|
|
201
206
|
size = 'default',
|
|
207
|
+
shape = 'default',
|
|
202
208
|
searchable = false,
|
|
203
209
|
onSearch,
|
|
204
210
|
loading,
|
|
@@ -285,6 +291,8 @@ function CustomSelect(props: Exclude<SelectProps, SelectNativeProps>): React.Rea
|
|
|
285
291
|
}
|
|
286
292
|
|
|
287
293
|
const ghost = variant === 'ghost'
|
|
294
|
+
const outline = variant === 'outline'
|
|
295
|
+
const compact = ghost || outline
|
|
288
296
|
|
|
289
297
|
/**
|
|
290
298
|
* O GATILHO nunca é caixa de busca — em nenhum modo.
|
|
@@ -326,12 +334,13 @@ function CustomSelect(props: Exclude<SelectProps, SelectNativeProps>): React.Rea
|
|
|
326
334
|
setOpen(v)
|
|
327
335
|
}}
|
|
328
336
|
>
|
|
329
|
-
<div ref={rootRef} data-slot="select" className={cn(
|
|
337
|
+
<div ref={rootRef} data-slot="select" data-shape={shape} className={cn(compact ? 'w-fit max-w-full min-w-0' : 'min-w-40', shape === 'pill' && 'rounded-full', className)}>
|
|
330
338
|
<CommandPrimitive data-slot="select-command" shouldFilter={canSearch && onSearch === undefined} className="overflow-visible">
|
|
331
339
|
<PopoverAnchor asChild>
|
|
332
340
|
<div
|
|
333
341
|
data-slot="select-control"
|
|
334
342
|
data-size={size}
|
|
343
|
+
data-shape={shape}
|
|
335
344
|
data-variant={variant}
|
|
336
345
|
onClick={() => {
|
|
337
346
|
if (disabled !== true) {
|
|
@@ -340,10 +349,12 @@ function CustomSelect(props: Exclude<SelectProps, SelectNativeProps>): React.Rea
|
|
|
340
349
|
}
|
|
341
350
|
}}
|
|
342
351
|
className={cn(
|
|
343
|
-
'flex items-center gap-1.5 rounded-md outline-none transition-[color,box-shadow]',
|
|
352
|
+
'flex items-center gap-1.5 rounded-md outline-none transition-[color,box-shadow] data-[shape=pill]:rounded-full',
|
|
344
353
|
ghost
|
|
345
|
-
? 'min-h-
|
|
346
|
-
: "min-h-9
|
|
354
|
+
? 'min-h-9 px-3 py-1 text-muted-foreground hover:bg-muted hover:text-foreground focus-within:ring-2 focus-within:ring-ring/50 data-[size=sm]:min-h-8 data-[size=sm]:px-2'
|
|
355
|
+
: "min-h-9 border border-input bg-transparent px-3 py-1 focus-within:border-ring focus-within:ring-2 focus-within:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[size=sm]:min-h-8 data-[size=sm]:py-0.5 dark:bg-input/30 dark:aria-invalid:ring-destructive/40",
|
|
356
|
+
variant === 'default' && 'flex-wrap',
|
|
357
|
+
outline && 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-input/50',
|
|
347
358
|
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
348
359
|
canSearch ? '' : 'cursor-pointer',
|
|
349
360
|
disabled === true && 'cursor-not-allowed opacity-50',
|
|
@@ -383,7 +394,14 @@ function CustomSelect(props: Exclude<SelectProps, SelectNativeProps>): React.Rea
|
|
|
383
394
|
o gatilho ficaria em branco (o input que carregava o placeholder virou
|
|
384
395
|
invisível). Campo mudo é pior que campo vazio: não diz nem o que é. */}
|
|
385
396
|
{(!props.multiple || selectedValues.length === 0) && (
|
|
386
|
-
<span
|
|
397
|
+
<span
|
|
398
|
+
data-slot="select-trigger-label"
|
|
399
|
+
className={cn(
|
|
400
|
+
'min-w-0 truncate text-left text-sm',
|
|
401
|
+
compact ? 'flex-initial' : 'flex-1',
|
|
402
|
+
selectedOption === undefined && 'text-muted-foreground',
|
|
403
|
+
)}
|
|
404
|
+
>
|
|
387
405
|
{props.multiple ? placeholder : (selectedOption?.triggerLabel ?? selectedOption?.label ?? placeholder)}
|
|
388
406
|
</span>
|
|
389
407
|
)}
|
|
@@ -473,7 +491,7 @@ function CustomSelect(props: Exclude<SelectProps, SelectNativeProps>): React.Rea
|
|
|
473
491
|
// título inteiro.
|
|
474
492
|
className={cn(
|
|
475
493
|
'pointer-events-auto overflow-hidden p-0',
|
|
476
|
-
|
|
494
|
+
compact ? 'w-auto min-w-56 max-w-80' : 'w-(--radix-popover-trigger-width) min-w-40',
|
|
477
495
|
)}
|
|
478
496
|
/*
|
|
479
497
|
* O foco ao abrir é SEMPRE nosso — o Radix nunca mexe.
|
|
@@ -34,9 +34,10 @@ function Tabs({
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
const tabsListVariants = cva(
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
|
|
37
|
+
// A altura horizontal entra como classe simples no componente (abaixo), para que um
|
|
38
|
+
// `className="h-full"` do consumidor consiga substituí-la via tailwind-merge. Seletores
|
|
39
|
+
// condicionais por data-size têm especificidade maior e tornavam a altura inextensível.
|
|
40
|
+
"group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col data-[variant=line]:rounded-none data-[variant=line]:p-0",
|
|
40
41
|
{
|
|
41
42
|
variants: {
|
|
42
43
|
variant: {
|
|
@@ -63,7 +64,7 @@ function TabsList({
|
|
|
63
64
|
data-slot="tabs-list"
|
|
64
65
|
data-variant={variant}
|
|
65
66
|
data-size={size}
|
|
66
|
-
className={cn(tabsListVariants({ variant }), className)}
|
|
67
|
+
className={cn(tabsListVariants({ variant }), size === "sm" ? "h-8" : "h-9", className)}
|
|
67
68
|
{...props}
|
|
68
69
|
/>
|
|
69
70
|
)
|
|
@@ -80,7 +81,7 @@ function TabsTrigger({
|
|
|
80
81
|
"relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none dark:text-muted-foreground dark:hover:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
81
82
|
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent",
|
|
82
83
|
"data-[state=active]:bg-background data-[state=active]:text-foreground dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 dark:data-[state=active]:text-foreground",
|
|
83
|
-
"after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-
|
|
84
|
+
"after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-1px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100",
|
|
84
85
|
className
|
|
85
86
|
)}
|
|
86
87
|
{...props}
|
|
@@ -3,16 +3,19 @@ import { type VariantProps } from "class-variance-authority"
|
|
|
3
3
|
import { ToggleGroup as ToggleGroupPrimitive } from "radix-ui"
|
|
4
4
|
|
|
5
5
|
import { cn } from '../../lib/cn.ts'
|
|
6
|
+
import type { ControlShape } from './control.ts'
|
|
6
7
|
import { toggleVariants } from './toggle.tsx'
|
|
7
8
|
|
|
8
9
|
const ToggleGroupContext = React.createContext<
|
|
9
10
|
VariantProps<typeof toggleVariants> & {
|
|
10
11
|
spacing?: number
|
|
12
|
+
shape?: ControlShape
|
|
11
13
|
}
|
|
12
14
|
>({
|
|
13
15
|
size: "default",
|
|
14
16
|
variant: "default",
|
|
15
17
|
spacing: 0,
|
|
18
|
+
shape: "default",
|
|
16
19
|
})
|
|
17
20
|
|
|
18
21
|
function ToggleGroup({
|
|
@@ -20,11 +23,13 @@ function ToggleGroup({
|
|
|
20
23
|
variant,
|
|
21
24
|
size,
|
|
22
25
|
spacing = 0,
|
|
26
|
+
shape = "default",
|
|
23
27
|
children,
|
|
24
28
|
...props
|
|
25
29
|
}: React.ComponentProps<typeof ToggleGroupPrimitive.Root> &
|
|
26
30
|
VariantProps<typeof toggleVariants> & {
|
|
27
31
|
spacing?: number
|
|
32
|
+
shape?: ControlShape
|
|
28
33
|
}) {
|
|
29
34
|
return (
|
|
30
35
|
<ToggleGroupPrimitive.Root
|
|
@@ -32,14 +37,15 @@ function ToggleGroup({
|
|
|
32
37
|
data-variant={variant}
|
|
33
38
|
data-size={size}
|
|
34
39
|
data-spacing={spacing}
|
|
40
|
+
data-shape={shape}
|
|
35
41
|
style={{ "--gap": spacing } as React.CSSProperties}
|
|
36
42
|
className={cn(
|
|
37
|
-
"group/toggle-group flex w-fit items-center gap-[--spacing(var(--gap))] rounded-md",
|
|
43
|
+
"group/toggle-group flex w-fit items-center gap-[--spacing(var(--gap))] rounded-md data-[shape=pill]:rounded-full",
|
|
38
44
|
className
|
|
39
45
|
)}
|
|
40
46
|
{...props}
|
|
41
47
|
>
|
|
42
|
-
<ToggleGroupContext.Provider value={{ variant, size, spacing }}>
|
|
48
|
+
<ToggleGroupContext.Provider value={{ variant, size, spacing, shape }}>
|
|
43
49
|
{children}
|
|
44
50
|
</ToggleGroupContext.Provider>
|
|
45
51
|
</ToggleGroupPrimitive.Root>
|
|
@@ -51,6 +57,7 @@ function ToggleGroupItem({
|
|
|
51
57
|
children,
|
|
52
58
|
variant,
|
|
53
59
|
size,
|
|
60
|
+
shape,
|
|
54
61
|
...props
|
|
55
62
|
}: React.ComponentProps<typeof ToggleGroupPrimitive.Item> &
|
|
56
63
|
VariantProps<typeof toggleVariants>) {
|
|
@@ -62,13 +69,17 @@ function ToggleGroupItem({
|
|
|
62
69
|
data-variant={context.variant || variant}
|
|
63
70
|
data-size={context.size || size}
|
|
64
71
|
data-spacing={context.spacing}
|
|
72
|
+
data-item-shape={shape}
|
|
65
73
|
className={cn(
|
|
66
74
|
toggleVariants({
|
|
67
75
|
variant: context.variant || variant,
|
|
68
76
|
size: context.size || size,
|
|
77
|
+
shape: shape ?? context.shape,
|
|
69
78
|
}),
|
|
70
79
|
"w-auto min-w-0 shrink-0 px-3 focus:z-10 focus-visible:z-10",
|
|
71
80
|
"data-[spacing=0]:rounded-none data-[spacing=0]:shadow-none data-[spacing=0]:first:rounded-l-md data-[spacing=0]:last:rounded-r-md data-[spacing=0]:data-[variant=outline]:border-l-0 data-[spacing=0]:data-[variant=outline]:first:border-l",
|
|
81
|
+
"group-data-[shape=pill]/toggle-group:data-[spacing=0]:first:rounded-l-full group-data-[shape=pill]/toggle-group:data-[spacing=0]:last:rounded-r-full",
|
|
82
|
+
"data-[item-shape=pill]:data-[spacing=0]:rounded-full",
|
|
72
83
|
className
|
|
73
84
|
)}
|
|
74
85
|
{...props}
|
|
@@ -18,10 +18,15 @@ const toggleVariants = cva(
|
|
|
18
18
|
sm: "h-8 min-w-8 px-1.5",
|
|
19
19
|
lg: "h-10 min-w-10 px-2.5",
|
|
20
20
|
},
|
|
21
|
+
shape: {
|
|
22
|
+
default: '',
|
|
23
|
+
pill: 'rounded-full',
|
|
24
|
+
},
|
|
21
25
|
},
|
|
22
26
|
defaultVariants: {
|
|
23
27
|
variant: "default",
|
|
24
28
|
size: "default",
|
|
29
|
+
shape: "default",
|
|
25
30
|
},
|
|
26
31
|
}
|
|
27
32
|
)
|
|
@@ -30,13 +35,15 @@ function Toggle({
|
|
|
30
35
|
className,
|
|
31
36
|
variant,
|
|
32
37
|
size,
|
|
38
|
+
shape,
|
|
33
39
|
...props
|
|
34
40
|
}: React.ComponentProps<typeof TogglePrimitive.Root> &
|
|
35
41
|
VariantProps<typeof toggleVariants>) {
|
|
36
42
|
return (
|
|
37
43
|
<TogglePrimitive.Root
|
|
38
44
|
data-slot="toggle"
|
|
39
|
-
|
|
45
|
+
data-shape={shape}
|
|
46
|
+
className={cn(toggleVariants({ variant, size, shape, className }))}
|
|
40
47
|
{...props}
|
|
41
48
|
/>
|
|
42
49
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
## Box simples
|
|
2
2
|
|
|
3
|
-
O uso mais comum: a superfície de conteúdo em repouso (rounded-card +
|
|
3
|
+
O uso mais comum: a superfície de conteúdo em repouso (`rounded-card` + `border` + `shadow-sm` — ver Tokens & Tema → Escala de elevação) com o padding pelo `className`. O Card não força flex/gap/padding — você compõe o miolo (sem brigar com `space-y`/`flex` seu).
|
|
4
4
|
|
|
5
5
|
```tsx preview col
|
|
6
6
|
<Card className="p-4">
|
|
@@ -30,7 +30,7 @@ Pra painel com estrutura: cada slot é dono do próprio padding (como o Dialog).
|
|
|
30
30
|
|
|
31
31
|
## Com divisor
|
|
32
32
|
|
|
33
|
-
border-b no header / border-t no footer dão o divisor quando a estrutura pede separação visível. A aresta externa
|
|
33
|
+
`border-b` no header / `border-t` no footer dão o divisor quando a estrutura pede separação visível. A aresta externa e os divisores internos usam o mesmo vocabulário de `border`.
|
|
34
34
|
|
|
35
35
|
```tsx preview col
|
|
36
36
|
<Card>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
## Básico
|
|
2
2
|
|
|
3
|
-
A caixa de escrever da casa: textarea numa pílula elevada (`rounded-card` + `
|
|
3
|
+
A caixa de escrever da casa: textarea numa pílula elevada (`rounded-card` + `border` + `shadow-sm`), **Enter** envia / **Shift+Enter** quebra linha, enviar dentro. É o composer do [Chat](/components/chat) extraído — use SOZINHO quando há entrada de texto mas não um chat (ex.: criar uma sessão). Controlado: o dono do texto é você.
|
|
4
4
|
|
|
5
5
|
```tsx preview col
|
|
6
6
|
const [text, setText] = React.useState('')
|
|
@@ -69,31 +69,30 @@ render(
|
|
|
69
69
|
)
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
-
##
|
|
72
|
+
## Escala de elevação
|
|
73
73
|
|
|
74
|
-
> A sombra
|
|
75
|
-
>
|
|
76
|
-
>
|
|
77
|
-
>
|
|
78
|
-
> `shadow-md` cru no código é sinal de review, não vocabulário da casa.
|
|
74
|
+
> A sombra usa a escala de intensidade do Tailwind (`shadow-sm` → `shadow-2xl`), mas o
|
|
75
|
+
> Opus controla sua composição para deixá-la mais clara e difusa. O componente escolhe
|
|
76
|
+
> quanto precisa subir; o nome não o prende a um papel. A aresta usa `border border-border`;
|
|
77
|
+
> o spread negativo evita que a sombra engrosse visualmente essa borda.
|
|
79
78
|
|
|
80
79
|
```tsx preview
|
|
81
|
-
const
|
|
82
|
-
['
|
|
83
|
-
['
|
|
84
|
-
['
|
|
80
|
+
const LEVELS = [
|
|
81
|
+
['sm', 'Superfície em repouso.'],
|
|
82
|
+
['md', 'Flutuante intermediário.'],
|
|
83
|
+
['lg', 'Modal ou destaque forte.'],
|
|
85
84
|
]
|
|
86
85
|
render(
|
|
87
86
|
<div className="flex flex-wrap gap-8 py-4">
|
|
88
|
-
{
|
|
89
|
-
<div key={
|
|
87
|
+
{LEVELS.map(([level, desc]) => (
|
|
88
|
+
<div key={level} className="flex flex-col items-center gap-3">
|
|
90
89
|
<div
|
|
91
|
-
className={'flex h-24 w-40 items-center justify-center
|
|
90
|
+
className={'flex h-24 w-40 items-center justify-center rounded-card border border-border bg-card text-xs shadow-' + level}
|
|
92
91
|
>
|
|
93
92
|
Texto
|
|
94
93
|
</div>
|
|
95
94
|
<div className="text-center">
|
|
96
|
-
<div className="font-mono text-xs">shadow-{
|
|
95
|
+
<div className="font-mono text-xs">shadow-{level}</div>
|
|
97
96
|
<div className="max-w-44 text-xs text-muted-foreground">{desc}</div>
|
|
98
97
|
</div>
|
|
99
98
|
</div>
|
|
@@ -112,7 +111,7 @@ render(
|
|
|
112
111
|
|
|
113
112
|
> Escala mapeada no `@theme inline` preservando o sentido pré-v4 dos utilitários `rounded-*`
|
|
114
113
|
> (neutraliza o rename do Tailwind v4). E a forma também tem papel: `rounded-card/popover/dialog`
|
|
115
|
-
> são a forma das superfícies elevadas (
|
|
114
|
+
> são a forma das superfícies elevadas (separada da intensidade de `shadow-*`), derivados de `--radius` — a
|
|
116
115
|
> identidade por app escala tudo junto. Controles (botões, inputs) seguem na escala sm–xl.
|
|
117
116
|
|
|
118
117
|
```tsx preview
|
|
@@ -150,11 +149,12 @@ render(
|
|
|
150
149
|
da casa; dialog e card "sobem" da página de verdade.
|
|
151
150
|
3. Radius mapeado (sm/md/lg/xl) preservando o sentido pré-v4 dos utilitários `rounded-*`.
|
|
152
151
|
|
|
153
|
-
|
|
154
|
-
|
|
152
|
+
4. Escala `shadow-*` recomposta em duas camadas claras e difusas — preserva os degraus do
|
|
153
|
+
Tailwind e troca somente o peso visual. Os aliases antigos por papel foram removidos;
|
|
154
|
+
componentes e consumidores usam diretamente a escala.
|
|
155
155
|
|
|
156
156
|
5. Bordas mais leves no light: `--border`/`--input` em 92.5% (upstream 89.8%) — par do canvas
|
|
157
|
-
quase-branco do body;
|
|
157
|
+
quase-branco do body; superfícies elevadas usam essa mesma borda real.
|
|
158
158
|
|
|
159
159
|
## Identidade por app
|
|
160
160
|
|
package/src/ui/meta.ts
CHANGED
|
@@ -27,7 +27,7 @@ export const componentMeta = {
|
|
|
27
27
|
name: 'button',
|
|
28
28
|
ancestry: 'shadcn',
|
|
29
29
|
whenToUse:
|
|
30
|
-
'Ação clicável. `variant`
|
|
30
|
+
'Ação clicável. `variant` define intenção (default/secondary/outline/ghost/destructive/link), `size` define altura (default/sm/lg · icon/icon-sm/icon-xs pros só-ícone, 36/32/24px) e `shape="pill"` troca somente a geometria, sem alterar cor ou hierarquia. `busy` mostra spinner e desabilita. `asChild` renderiza como outro elemento.',
|
|
31
31
|
},
|
|
32
32
|
'card': {
|
|
33
33
|
name: 'card',
|
|
@@ -110,7 +110,7 @@ export const componentMeta = {
|
|
|
110
110
|
name: 'select',
|
|
111
111
|
ancestry: 'opus',
|
|
112
112
|
whenToUse:
|
|
113
|
-
'O seletor ÚNICO da casa: toda escolha em lista. Data-driven (`options` = { value, label, hint?, content?, group? }), nunca JSX de item. Modos por prop
|
|
113
|
+
'O seletor ÚNICO da casa: toda escolha em lista. Data-driven (`options` = { value, label, hint?, content?, group? }), nunca JSX de item. Modos por prop: `searchable`, `multiple`, `onSearch` e `native`. `variant="default"` é campo de formulário; `outline` abraça o conteúdo com borda; `ghost` abraça o conteúdo sem borda. `size` segue a régua inline (default 36px/sm 32px) e `shape="pill"` troca somente a geometria. Também aceita `icon`, `trailing`, `triggerLabel` e `clearable`.',
|
|
114
114
|
},
|
|
115
115
|
'separator': {
|
|
116
116
|
name: 'separator',
|
|
@@ -137,7 +137,7 @@ export const componentMeta = {
|
|
|
137
137
|
name: 'tabs',
|
|
138
138
|
ancestry: 'shadcn',
|
|
139
139
|
whenToUse:
|
|
140
|
-
'Abas pra alternar entre painéis de conteúdo (Radix). Compõe Tabs > (TabsList > TabsTrigger + TabsContent), pareando `value` do trigger com o do content. `variant` na TabsList: `default` (pill) ou `line` (barra sublinhada). `size` no Tabs (default h-9 / sm h-8 — o par do sm de Button/Select, pra fileira densa)
|
|
140
|
+
'Abas pra alternar entre painéis de conteúdo (Radix). Compõe Tabs > (TabsList > TabsTrigger + TabsContent), pareando `value` do trigger com o do content. `variant` na TabsList: `default` (pill) ou `line` (barra sublinhada, ancorada na borda da lista). `size` no Tabs (default h-9 / sm h-8 — o par do sm de Button/Select, pra fileira densa); a altura pode ser substituída em `TabsList`. `orientation` (horizontal/vertical).',
|
|
141
141
|
},
|
|
142
142
|
'textarea': {
|
|
143
143
|
name: 'textarea',
|
|
@@ -197,7 +197,7 @@ export const componentMeta = {
|
|
|
197
197
|
name: 'button-group',
|
|
198
198
|
ancestry: 'shadcn',
|
|
199
199
|
whenToUse:
|
|
200
|
-
'Junta botões (e
|
|
200
|
+
'Junta botões (e Select) num bloco coeso — bordas internas colapsadas e cantos arredondados só nas pontas. `orientation` define o eixo e `shape="pill"` arredonda as extremidades externas sem reabrir a junção interna. ButtonGroupText adiciona rótulo/prefixo; ButtonGroupSeparator corta visualmente entre ações.',
|
|
201
201
|
},
|
|
202
202
|
'calendar': {
|
|
203
203
|
name: 'calendar',
|
|
@@ -239,7 +239,7 @@ export const componentMeta = {
|
|
|
239
239
|
name: 'input-group',
|
|
240
240
|
ancestry: 'shadcn',
|
|
241
241
|
whenToUse:
|
|
242
|
-
'Campo composto: cola ícones, texto e botões a um InputGroupInput/InputGroupTextarea numa única moldura (foco e erro propagam pro grupo todo).
|
|
242
|
+
'Campo composto: cola ícones, texto e botões a um InputGroupInput/InputGroupTextarea numa única moldura (foco e erro propagam pro grupo todo). `shape="pill"` aplica a geometria arredondada à moldura. InputGroupAddon ancora adornos; InputGroupText é rótulo inerte; InputGroupButton é o botão embutido. Pra agrupar botões soltos, use ButtonGroup.',
|
|
243
243
|
},
|
|
244
244
|
'input-otp': {
|
|
245
245
|
name: 'input-otp',
|
package/src/ui/react.tsx
CHANGED
|
@@ -13,6 +13,7 @@ export { cn } from './lib/cn.ts'
|
|
|
13
13
|
// Primitivos
|
|
14
14
|
export { Button, buttonVariants } from './components/primitives/button.tsx'
|
|
15
15
|
export type { ButtonProps } from './components/primitives/button.tsx'
|
|
16
|
+
export type { ControlShape } from './components/primitives/control.ts'
|
|
16
17
|
|
|
17
18
|
export { Input } from './components/primitives/input.tsx'
|
|
18
19
|
|
package/src/ui/theme.css
CHANGED
|
@@ -12,11 +12,10 @@
|
|
|
12
12
|
* da casa (upstream v4 também elevou; calibramos pelo site deles em 2026-06).
|
|
13
13
|
* 3. Radius mapeado (sm/md/lg/xl em @theme inline) preservando o sentido pré-v4 dos
|
|
14
14
|
* utilitários `rounded-*` — neutraliza o rename do v4.
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* defaults do Tailwind.)
|
|
15
|
+
* 4. Escala shadow-* recomposta diretamente em duas camadas claras, difusas e com spread
|
|
16
|
+
* negativo — preserva inclusive modifiers como `shadow-lg/30` (ver docs/elevation-scale.md).
|
|
18
17
|
* 5. Bordas mais leves no light: --border/--input em 92.5% (upstream 89.8%) — par do
|
|
19
|
-
* canvas quase-branco;
|
|
18
|
+
* canvas quase-branco; superfícies elevadas usam a mesma borda real dos demais papéis.
|
|
20
19
|
* Tudo o mais (escala neutra, radius base 0.625rem, semânticas) segue o upstream.
|
|
21
20
|
*/
|
|
22
21
|
@import 'tailwindcss';
|
|
@@ -27,15 +26,6 @@
|
|
|
27
26
|
@custom-variant dark (&:is(.dark *));
|
|
28
27
|
|
|
29
28
|
:root {
|
|
30
|
-
/* Elevação POR PAPEL (par do bg-card/bg-popover): família Notion — camada curta +
|
|
31
|
-
longa, tinte 25/25/25 — e a ARESTA (--edge-value): o anel hairline que SUBSTITUI a
|
|
32
|
-
borda em superfície elevada (ring-1 ring-edge). Papel novo = token novo; os
|
|
33
|
-
defaults shadow-* do Tailwind ficam INTACTOS (sem pegadinha). */
|
|
34
|
-
--edge-value: rgba(42, 28, 0, 0.07);
|
|
35
|
-
--elevation-card: 0 1px 2px rgba(25, 25, 25, 0.027), 0 3px 6px rgba(25, 25, 25, 0.05);
|
|
36
|
-
--elevation-popover: 0 5px 8px rgba(25, 25, 25, 0.027), 0 20px 24px rgba(25, 25, 25, 0.05);
|
|
37
|
-
--elevation-dialog: 0 4px 8px rgba(25, 25, 25, 0.03), 0 16px 24px rgba(25, 25, 25, 0.06);
|
|
38
|
-
|
|
39
29
|
/* Scrollbars e controles NATIVOS (date picker, autofill…) seguem o tema — sem
|
|
40
30
|
isto o browser assume light e pinta scrollbar branca no dark. */
|
|
41
31
|
color-scheme: light;
|
|
@@ -64,10 +54,6 @@
|
|
|
64
54
|
}
|
|
65
55
|
|
|
66
56
|
.dark {
|
|
67
|
-
--edge-value: rgba(255, 255, 255, 0.06);
|
|
68
|
-
--elevation-card: 0 1px 2px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.28);
|
|
69
|
-
--elevation-popover: 0 5px 8px rgba(0, 0, 0, 0.16), 0 20px 24px rgba(0, 0, 0, 0.3);
|
|
70
|
-
--elevation-dialog: 0 4px 8px rgba(0, 0, 0, 0.2), 0 16px 24px rgba(0, 0, 0, 0.35);
|
|
71
57
|
color-scheme: dark;
|
|
72
58
|
--background: hsl(0 0% 3.9%);
|
|
73
59
|
--foreground: hsl(0 0% 98%);
|
|
@@ -151,20 +137,23 @@
|
|
|
151
137
|
--radius-lg: var(--radius);
|
|
152
138
|
--radius-xl: calc(var(--radius) + 4px);
|
|
153
139
|
|
|
154
|
-
/* Forma por papel (rounded-card/popover/dialog)
|
|
155
|
-
|
|
156
|
-
|
|
140
|
+
/* Forma por papel (rounded-card/popover/dialog), separada da intensidade de elevação.
|
|
141
|
+
Derivados de --radius: a identidade por app (sobrescrever --radius) escala os papéis
|
|
142
|
+
junto. Controles ficam na escala sm–xl. */
|
|
157
143
|
--radius-card: calc(var(--radius) + 6px);
|
|
158
144
|
--radius-popover: calc(var(--radius) - 2px);
|
|
159
145
|
--radius-dialog: calc(var(--radius) + 6px);
|
|
160
146
|
|
|
161
|
-
/*
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
--shadow-
|
|
165
|
-
--shadow-
|
|
166
|
-
--shadow
|
|
167
|
-
--
|
|
147
|
+
/* Escala Tailwind, com receita direta para o compilador preservar modifiers `/opacity`.
|
|
148
|
+
`light-dark()` mantém o peso adequado em cada tema sem uma variável intermediária. */
|
|
149
|
+
--shadow-2xs: 0 1px 2px -1px light-dark(rgb(15 15 15 / 0.03), rgb(0 0 0 / 0.12));
|
|
150
|
+
--shadow-xs: 0 1px 2px -1px light-dark(rgb(15 15 15 / 0.025), rgb(0 0 0 / 0.12)), 0 2px 4px -2px light-dark(rgb(15 15 15 / 0.035), rgb(0 0 0 / 0.18));
|
|
151
|
+
--shadow-sm: 0 2px 4px -2px light-dark(rgb(15 15 15 / 0.025), rgb(0 0 0 / 0.16)), 0 4px 8px -4px light-dark(rgb(15 15 15 / 0.04), rgb(0 0 0 / 0.28));
|
|
152
|
+
--shadow: 0 2px 6px -2px light-dark(rgb(15 15 15 / 0.03), rgb(0 0 0 / 0.16)), 0 6px 14px -6px light-dark(rgb(15 15 15 / 0.05), rgb(0 0 0 / 0.29));
|
|
153
|
+
--shadow-md: 0 4px 8px -4px light-dark(rgb(15 15 15 / 0.03), rgb(0 0 0 / 0.16)), 0 10px 28px -8px light-dark(rgb(15 15 15 / 0.065), rgb(0 0 0 / 0.3));
|
|
154
|
+
--shadow-lg: 0 6px 16px -6px light-dark(rgb(15 15 15 / 0.03), rgb(0 0 0 / 0.18)), 0 18px 48px -12px light-dark(rgb(15 15 15 / 0.07), rgb(0 0 0 / 0.3));
|
|
155
|
+
--shadow-xl: 0 8px 24px -10px light-dark(rgb(15 15 15 / 0.04), rgb(0 0 0 / 0.2)), 0 24px 64px -18px light-dark(rgb(15 15 15 / 0.08), rgb(0 0 0 / 0.34));
|
|
156
|
+
--shadow-2xl: 0 12px 32px -14px light-dark(rgb(15 15 15 / 0.05), rgb(0 0 0 / 0.22)), 0 32px 88px -24px light-dark(rgb(15 15 15 / 0.1), rgb(0 0 0 / 0.38));
|
|
168
157
|
}
|
|
169
158
|
|
|
170
159
|
/* Base canônica do consumidor — vem DAQUI, não do entry de cada app. Tailwind v4
|