@softize/opus 22.2.0 → 24.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 (115) hide show
  1. package/CHANGELOG.md +120 -1
  2. package/bin/cli.mjs +3 -9
  3. package/bin/lib/check.mjs +242 -49
  4. package/bin/lib/copy.mjs +17 -35
  5. package/bin/lib/gen-runner.mjs +2 -0
  6. package/bin/lib/gen-stubs.mjs +3 -1
  7. package/docs/adr/0005-structural-surfaces-share-an-explicit-anatomy.md +1 -1
  8. package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +6 -0
  9. package/docs/adr/0032-tab-baseline-respects-the-container-inset.md +16 -12
  10. package/docs/adr/0034-tabs-can-own-a-frame-without-owning-a-surface.md +8 -6
  11. package/docs/adr/0036-tabs-declares-trigger-distribution.md +11 -8
  12. package/docs/adr/0037-design-data-lives-outside-the-contract.md +1 -1
  13. package/docs/adr/0038-content-level-defines-primary-presentation.md +62 -0
  14. package/docs/adr/0039-filter-presentation-is-independent-from-filter-type.md +62 -0
  15. package/docs/adr/0040-tabs-use-a-single-navigation-presentation.md +59 -0
  16. package/docs/adr/0042-toggle-group-shares-the-grouped-control-grammar.md +79 -0
  17. package/docs/adr/0043-neutral-surfaces-and-interactions-use-opaque-role-tokens.md +73 -0
  18. package/docs/adr/0044-neutral-appearance-derives-from-bounded-axes-per-mode.md +133 -0
  19. package/docs/adr/0045-surface-replaces-content-and-card.md +230 -0
  20. package/docs/adr/0046-structural-body-owns-content-rhythm.md +103 -0
  21. package/docs/adr/0047-pane-is-only-a-split-layout-unit.md +71 -0
  22. package/docs/adr/0048-modal-intro-separates-content-from-chrome.md +34 -0
  23. package/docs/code-style.md +1 -1
  24. package/package.json +5 -1
  25. package/registry/skills/build-opus-ui/SKILL.md +11 -11
  26. package/registry/skills/build-opus-ui/references/evaluations.md +10 -9
  27. package/registry/skills/build-opus-ui/references/ui-patterns.md +17 -16
  28. package/registry/skills/create-opus-action/references/contract-and-binding.md +3 -3
  29. package/registry/templates/app/src/App.tsx +24 -12
  30. package/src/core/contracts.ts +1 -1
  31. package/src/core/runtime.ts +4 -37
  32. package/src/core/types.ts +5 -23
  33. package/src/design/index.ts +14 -3
  34. package/src/testing/index.ts +6 -26
  35. package/src/ui/appearance.ts +149 -0
  36. package/src/ui/components/patterns/action-form-card.tsx +46 -33
  37. package/src/ui/components/patterns/action-list-dialog.tsx +2 -4
  38. package/src/ui/components/patterns/confirm.tsx +3 -1
  39. package/src/ui/components/patterns/fill-region.ts +8 -7
  40. package/src/ui/components/patterns/form-dialog.tsx +1 -3
  41. package/src/ui/components/patterns/form.tsx +8 -7
  42. package/src/ui/components/patterns/list.tsx +64 -14
  43. package/src/ui/components/patterns/page.tsx +15 -11
  44. package/src/ui/components/patterns/pane.tsx +9 -240
  45. package/src/ui/components/patterns/presentation.tsx +14 -14
  46. package/src/ui/components/patterns/sidebar.tsx +7 -17
  47. package/src/ui/components/patterns/split.tsx +111 -59
  48. package/src/ui/components/patterns/{surface-header.tsx → structural-header.tsx} +50 -19
  49. package/src/ui/components/patterns/structural-media.tsx +26 -0
  50. package/src/ui/components/patterns/surface-anatomy.ts +71 -61
  51. package/src/ui/components/patterns/surface-assistant.tsx +2 -4
  52. package/src/ui/components/patterns/surface.tsx +620 -0
  53. package/src/ui/components/patterns/trigger.tsx +11 -9
  54. package/src/ui/components/primitives/ask.tsx +155 -83
  55. package/src/ui/components/primitives/button.tsx +11 -9
  56. package/src/ui/components/primitives/checkbox.tsx +8 -8
  57. package/src/ui/components/primitives/command.tsx +1 -1
  58. package/src/ui/components/primitives/composer.tsx +47 -45
  59. package/src/ui/components/primitives/detail.tsx +9 -2
  60. package/src/ui/components/primitives/dialog.tsx +60 -26
  61. package/src/ui/components/primitives/drawer.tsx +58 -12
  62. package/src/ui/components/primitives/icon-picker.tsx +93 -70
  63. package/src/ui/components/primitives/input-group.tsx +35 -28
  64. package/src/ui/components/primitives/input-otp.tsx +16 -16
  65. package/src/ui/components/primitives/input.tsx +20 -10
  66. package/src/ui/components/primitives/item.tsx +9 -12
  67. package/src/ui/components/primitives/menu.tsx +44 -65
  68. package/src/ui/components/primitives/metric-card.tsx +75 -59
  69. package/src/ui/components/primitives/radio-group.tsx +9 -9
  70. package/src/ui/components/primitives/select.tsx +344 -209
  71. package/src/ui/components/primitives/switch.tsx +10 -10
  72. package/src/ui/components/primitives/table.tsx +29 -24
  73. package/src/ui/components/primitives/tabs.tsx +58 -88
  74. package/src/ui/components/primitives/textarea.tsx +6 -6
  75. package/src/ui/components/primitives/toggle-group-context.ts +24 -0
  76. package/src/ui/components/primitives/toggle-group.tsx +116 -74
  77. package/src/ui/components/primitives/toggle.tsx +82 -16
  78. package/src/ui/docs/DocBrowser.tsx +77 -69
  79. package/src/ui/docs/content/action-form-card.md +9 -9
  80. package/src/ui/docs/content/action-list.md +85 -70
  81. package/src/ui/docs/content/action-view.md +33 -18
  82. package/src/ui/docs/content/actions.md +6 -7
  83. package/src/ui/docs/content/button.md +1 -1
  84. package/src/ui/docs/content/communication.md +1 -1
  85. package/src/ui/docs/content/customization.md +64 -11
  86. package/src/ui/docs/content/detail.md +8 -6
  87. package/src/ui/docs/content/dialog.md +100 -58
  88. package/src/ui/docs/content/drawer.md +63 -19
  89. package/src/ui/docs/content/field.md +14 -15
  90. package/src/ui/docs/content/icon-tile.md +14 -13
  91. package/src/ui/docs/content/page.md +34 -26
  92. package/src/ui/docs/content/pane.md +41 -290
  93. package/src/ui/docs/content/presentation.md +3 -3
  94. package/src/ui/docs/content/semantic-context.md +0 -1
  95. package/src/ui/docs/content/separator.md +6 -6
  96. package/src/ui/docs/content/sidebar.md +68 -266
  97. package/src/ui/docs/content/skeleton.md +20 -18
  98. package/src/ui/docs/content/split.md +14 -14
  99. package/src/ui/docs/content/surface.md +513 -0
  100. package/src/ui/docs/content/tabs.md +82 -83
  101. package/src/ui/docs/content/testing.md +2 -3
  102. package/src/ui/docs/content/toggle.md +120 -63
  103. package/src/ui/docs/content/tokens.md +124 -40
  104. package/src/ui/docs/registry.tsx +4 -6
  105. package/src/ui/drivers/react.tsx +4 -3
  106. package/src/ui/index.ts +30 -27
  107. package/src/ui/meta.ts +15 -27
  108. package/src/ui/react.tsx +42 -46
  109. package/src/ui/theme.css +311 -64
  110. package/src/vite/design.ts +23 -21
  111. package/src/ui/components/patterns/content-header.tsx +0 -532
  112. package/src/ui/components/patterns/surface-media.tsx +0 -69
  113. package/src/ui/components/primitives/card.tsx +0 -191
  114. package/src/ui/docs/content/card.md +0 -292
  115. package/src/ui/docs/content/content.md +0 -276
@@ -1,8 +1,8 @@
1
- import * as React from "react"
2
- import { Switch as SwitchPrimitive } from "radix-ui"
1
+ import * as React from "react";
2
+ import { Switch as SwitchPrimitive } from "radix-ui";
3
3
 
4
- import { cn } from '../../lib/cn.ts'
5
- import type { ControlSize } from './control.ts'
4
+ import { cn } from "../../lib/cn.ts";
5
+ import type { ControlSize } from "./control.ts";
6
6
 
7
7
  /** `size` usa os nomes da escala de control.ts; a chave é um trilho (não uma linha), então a
8
8
  * medida é a do glifo daquele tamanho: `sm` acompanha o svg de um controle sm (0.875rem). */
@@ -11,15 +11,15 @@ function Switch({
11
11
  size = "default",
12
12
  ...props
13
13
  }: React.ComponentProps<typeof SwitchPrimitive.Root> & {
14
- size?: Extract<ControlSize, "sm" | "default">
14
+ size?: Extract<ControlSize, "sm" | "default">;
15
15
  }) {
16
16
  return (
17
17
  <SwitchPrimitive.Root
18
18
  data-slot="switch"
19
19
  data-size={size}
20
20
  className={cn(
21
- "peer group/switch inline-flex shrink-0 items-center rounded-full border border-transparent transition-all outline-none focus-visible:border-ring focus-visible:ring-[0.1875rem] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-[1.15rem] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input dark:data-[state=unchecked]:bg-input/80",
22
- className
21
+ "peer group/switch inline-flex shrink-0 items-center rounded-full border border-transparent transition-all outline-none focus-visible:border-ring focus-visible:ring-[0.1875rem] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-[1.15rem] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6 data-[state=checked]:bg-primary data-[state=unchecked]:bg-interaction-selected",
22
+ className,
23
23
  )}
24
24
  {...props}
25
25
  >
@@ -27,11 +27,11 @@ function Switch({
27
27
  data-slot="switch-thumb"
28
28
  // px: o percurso do thumb desconta a borda (um pixel de cada lado) no calc do translate.
29
29
  className={cn(
30
- "pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0 dark:data-[state=checked]:bg-primary-foreground dark:data-[state=unchecked]:bg-foreground"
30
+ "pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0 dark:data-[state=checked]:bg-primary-foreground dark:data-[state=unchecked]:bg-foreground",
31
31
  )}
32
32
  />
33
33
  </SwitchPrimitive.Root>
34
- )
34
+ );
35
35
  }
36
36
 
37
- export { Switch }
37
+ export { Switch };
@@ -1,18 +1,23 @@
1
- import * as React from "react"
1
+ import * as React from "react";
2
2
 
3
- import { cn } from '../../lib/cn.ts'
3
+ import { cn } from "../../lib/cn.ts";
4
4
 
5
- type TableVariant = 'plain' | 'framed'
6
- const TableVariantContext = React.createContext<TableVariant>('plain')
5
+ type TableVariant = "plain" | "framed";
6
+ const TableVariantContext = React.createContext<TableVariant>("plain");
7
7
 
8
8
  export interface TableProps extends React.ComponentProps<"table"> {
9
9
  /** `framed` aplica a moldura canônica de datagrid no contêiner da tabela. */
10
- variant?: TableVariant
10
+ variant?: TableVariant;
11
11
  /** Classes do contêiner responsável pela rolagem e pela moldura da tabela. */
12
- containerClassName?: string
12
+ containerClassName?: string;
13
13
  }
14
14
 
15
- function Table({ className, containerClassName, variant = 'plain', ...props }: TableProps) {
15
+ function Table({
16
+ className,
17
+ containerClassName,
18
+ variant = "plain",
19
+ ...props
20
+ }: TableProps) {
16
21
  return (
17
22
  <TableVariantContext.Provider value={variant}>
18
23
  <div
@@ -20,7 +25,7 @@ function Table({ className, containerClassName, variant = 'plain', ...props }: T
20
25
  data-variant={variant}
21
26
  className={cn(
22
27
  "relative w-full overflow-x-auto",
23
- variant === 'framed' && 'rounded-lg border border-border',
28
+ variant === "framed" && "rounded-lg border border-border",
24
29
  containerClassName,
25
30
  )}
26
31
  >
@@ -31,22 +36,22 @@ function Table({ className, containerClassName, variant = 'plain', ...props }: T
31
36
  />
32
37
  </div>
33
38
  </TableVariantContext.Provider>
34
- )
39
+ );
35
40
  }
36
41
 
37
42
  function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
38
- const variant = React.useContext(TableVariantContext)
43
+ const variant = React.useContext(TableVariantContext);
39
44
  return (
40
45
  <thead
41
46
  data-slot="table-header"
42
47
  className={cn(
43
48
  "[&_tr]:border-b [&_tr:hover]:bg-transparent",
44
- variant === 'framed' && 'bg-muted/30 dark:bg-input/30',
49
+ variant === "framed" && "bg-control",
45
50
  className,
46
51
  )}
47
52
  {...props}
48
53
  />
49
- )
54
+ );
50
55
  }
51
56
 
52
57
  function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
@@ -56,7 +61,7 @@ function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
56
61
  className={cn("[&_tr:last-child]:border-0", className)}
57
62
  {...props}
58
63
  />
59
- )
64
+ );
60
65
  }
61
66
 
62
67
  function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
@@ -65,11 +70,11 @@ function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
65
70
  data-slot="table-footer"
66
71
  className={cn(
67
72
  "border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
68
- className
73
+ className,
69
74
  )}
70
75
  {...props}
71
76
  />
72
- )
77
+ );
73
78
  }
74
79
 
75
80
  function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
@@ -77,12 +82,12 @@ function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
77
82
  <tr
78
83
  data-slot="table-row"
79
84
  className={cn(
80
- "border-b transition-colors hover:bg-muted/50 has-aria-expanded:bg-muted/50 data-[state=selected]:bg-muted",
81
- className
85
+ "border-b transition-colors hover:bg-interaction-hover has-aria-expanded:bg-interaction-selected data-[state=selected]:bg-interaction-selected",
86
+ className,
82
87
  )}
83
88
  {...props}
84
89
  />
85
- )
90
+ );
86
91
  }
87
92
 
88
93
  function TableHead({ className, ...props }: React.ComponentProps<"th">) {
@@ -91,11 +96,11 @@ function TableHead({ className, ...props }: React.ComponentProps<"th">) {
91
96
  data-slot="table-head"
92
97
  className={cn(
93
98
  "h-10 px-3 text-left align-middle font-medium whitespace-nowrap text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[0.125rem]",
94
- className
99
+ className,
95
100
  )}
96
101
  {...props}
97
102
  />
98
- )
103
+ );
99
104
  }
100
105
 
101
106
  function TableCell({ className, ...props }: React.ComponentProps<"td">) {
@@ -104,11 +109,11 @@ function TableCell({ className, ...props }: React.ComponentProps<"td">) {
104
109
  data-slot="table-cell"
105
110
  className={cn(
106
111
  "px-3 py-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[0.125rem]",
107
- className
112
+ className,
108
113
  )}
109
114
  {...props}
110
115
  />
111
- )
116
+ );
112
117
  }
113
118
 
114
119
  function TableCaption({
@@ -121,7 +126,7 @@ function TableCaption({
121
126
  className={cn("mt-4 text-sm text-muted-foreground", className)}
122
127
  {...props}
123
128
  />
124
- )
129
+ );
125
130
  }
126
131
 
127
132
  export {
@@ -133,4 +138,4 @@ export {
133
138
  TableRow,
134
139
  TableCell,
135
140
  TableCaption,
136
- }
141
+ };
@@ -1,12 +1,11 @@
1
1
  import * as React from "react";
2
- import { cva, type VariantProps } from "class-variance-authority";
3
2
  import { Tabs as TabsPrimitive } from "radix-ui";
4
3
 
5
4
  import { cn } from "../../lib/cn.ts";
6
5
  import { controlHeight, type ControlSize } from "./control.ts";
7
6
 
8
7
  // Divergência da casa (ejetado): o Tabs ganhou `size` — Button e Select têm `sm`, o Tabs
9
- // não tinha, e uma fileira densa ficava com o segmento 0.25rem mais alto
8
+ // não tinha, e uma fileira densa ficava com as abas 0.25rem mais altas
10
9
  // que os irmãos. O tamanho flui pra TabsList via contexto (a altura é dela) e a medida é a
11
10
  // da escala de control.ts.
12
11
  type TabsSize = Extract<ControlSize, "default" | "sm">;
@@ -18,9 +17,6 @@ const TabsFramedContext = React.createContext(false);
18
17
  type TabsOrientation = "horizontal" | "vertical";
19
18
  const TabsOrientationContext =
20
19
  React.createContext<TabsOrientation>("horizontal");
21
- const TabsListVariantContext = React.createContext<"default" | "line">(
22
- "default",
23
- );
24
20
 
25
21
  function Tabs({
26
22
  className,
@@ -62,87 +58,64 @@ function Tabs({
62
58
  );
63
59
  }
64
60
 
65
- const tabsListVariants = cva(
66
- // A altura horizontal entra como classe simples no componente (abaixo), para que um
67
- // `className="h-full"` do consumidor consiga substituí-la via tailwind-merge. Seletores
68
- // condicionais por data-size têm especificidade maior e tornavam a altura inextensível.
69
- "group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[0.1875rem] 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",
70
- {
71
- variants: {
72
- variant: {
73
- default: "bg-muted",
74
- line: "gap-1 bg-transparent",
75
- },
76
- },
77
- defaultVariants: {
78
- variant: "default",
79
- },
80
- },
81
- );
82
-
83
61
  function TabsList({
84
62
  className,
85
63
  children,
86
64
  style,
87
- variant = "default",
88
65
  ...props
89
- }: React.ComponentProps<typeof TabsPrimitive.List> &
90
- VariantProps<typeof tabsListVariants>) {
66
+ }: React.ComponentProps<typeof TabsPrimitive.List>) {
91
67
  // O size vem do <Tabs> (contexto) — a altura mora aqui, na list.
92
68
  const size = React.useContext(TabsSizeContext);
93
69
  const orientation = React.useContext(TabsOrientationContext);
94
70
  const distribution = React.useContext(TabsDistributionContext);
95
- const normalizedVariant = variant ?? "default";
96
- const hasHorizontalLine =
97
- normalizedVariant === "line" && orientation === "horizontal";
71
+ const framed = React.useContext(TabsFramedContext);
72
+ const hasHorizontalLine = orientation === "horizontal";
98
73
  return (
99
- <TabsListVariantContext.Provider value={normalizedVariant}>
100
- <TabsPrimitive.List
101
- data-slot="tabs-list"
102
- data-variant={normalizedVariant}
103
- data-size={size}
104
- data-distribution={distribution}
105
- className={cn(
106
- tabsListVariants({ variant: normalizedVariant }),
107
- controlHeight[size],
108
- distribution === "equal" && orientation === "horizontal" && "w-full",
109
- className,
110
- )}
111
- style={{
112
- ...(hasHorizontalLine
113
- ? {
114
- position: "relative",
115
- width: "100%",
116
- height: "auto",
117
- justifyContent: "flex-start",
118
- }
119
- : {}),
120
- ...style,
121
- }}
122
- {...props}
123
- >
124
- {hasHorizontalLine && (
125
- <span
126
- aria-hidden="true"
127
- data-slot="tabs-line"
128
- style={{
129
- position: "absolute",
130
- insetInline: "0rem",
131
- bottom: 0,
132
- borderBottomWidth: "0.0625rem",
133
- borderBottomStyle: "solid",
134
- borderBottomColor: "var(--border)",
135
- pointerEvents: "none",
136
- }}
137
- />
138
- )}
139
- {children}
140
- </TabsPrimitive.List>
141
- </TabsListVariantContext.Provider>
74
+ <TabsPrimitive.List
75
+ data-slot="tabs-list"
76
+ data-size={size}
77
+ data-distribution={distribution}
78
+ className={cn(
79
+ "group/tabs-list inline-flex w-fit items-center justify-center gap-1 rounded-none bg-transparent p-0 text-muted-foreground group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col",
80
+ controlHeight[size],
81
+ distribution === "equal" && orientation === "horizontal" && "w-full",
82
+ framed && orientation === "horizontal" && "px-1 pt-1",
83
+ className,
84
+ )}
85
+ style={{
86
+ ...(hasHorizontalLine
87
+ ? {
88
+ position: "relative",
89
+ width: "100%",
90
+ height: "auto",
91
+ justifyContent: "flex-start",
92
+ }
93
+ : {}),
94
+ ...style,
95
+ }}
96
+ {...props}
97
+ >
98
+ {hasHorizontalLine && (
99
+ <span
100
+ aria-hidden="true"
101
+ data-slot="tabs-line"
102
+ style={{
103
+ position: "absolute",
104
+ insetInline: "0rem",
105
+ bottom: 0,
106
+ borderBottomWidth: "0.0625rem",
107
+ borderBottomStyle: "solid",
108
+ borderBottomColor: "var(--border)",
109
+ pointerEvents: "none",
110
+ }}
111
+ />
112
+ )}
113
+ {children}
114
+ </TabsPrimitive.List>
142
115
  );
143
116
  }
144
117
 
145
- function TabsTrigger({
118
+ function TabsListItem({
146
119
  className,
147
120
  style,
148
121
  icon,
@@ -152,17 +125,16 @@ function TabsTrigger({
152
125
  /** Ícone decorativo antes do rótulo. Em uma aba sem texto, declare também `aria-label`. */
153
126
  icon?: React.ReactNode;
154
127
  }) {
155
- const variant = React.useContext(TabsListVariantContext);
156
128
  const orientation = React.useContext(TabsOrientationContext);
157
129
  const size = React.useContext(TabsSizeContext);
158
130
  const distribution = React.useContext(TabsDistributionContext);
159
131
  const framed = React.useContext(TabsFramedContext);
160
- const hasHorizontalLine = variant === "line" && orientation === "horizontal";
132
+ const hasHorizontalLine = orientation === "horizontal";
161
133
  const content = (
162
134
  <>
163
135
  {icon !== undefined && (
164
136
  <span
165
- data-slot="tabs-trigger-icon"
137
+ data-slot="tabs-list-item-icon"
166
138
  aria-hidden="true"
167
139
  className="flex size-4 shrink-0 items-center justify-center [&_svg]:size-4"
168
140
  >
@@ -174,17 +146,15 @@ function TabsTrigger({
174
146
  );
175
147
  return (
176
148
  <TabsPrimitive.Trigger
177
- data-slot="tabs-trigger"
149
+ data-slot="tabs-list-item"
178
150
  // px: altura e indicador compensam a hairline do contêiner (um pixel físico, não escala).
179
151
  className={cn(
180
- "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-[0.1875rem] 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",
181
- "dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent",
182
- "data-[state=active]:bg-background data-[state=active]:text-foreground dark:data-[state=active]:border-input dark:data-[state=active]:text-foreground",
183
- "after:absolute after:bg-primary 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",
152
+ "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-[0.1875rem] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 data-[state=active]:text-foreground data-[state=active]:shadow-none dark:text-muted-foreground dark:hover:text-foreground dark:data-[state=active]:border-transparent dark:data-[state=active]:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
153
+ "after:absolute after:bg-primary 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 data-[state=active]:after:opacity-100",
184
154
  hasHorizontalLine &&
185
155
  cn(
186
- "group/tab-trigger rounded-none px-0 py-0 hover:bg-transparent active:brightness-100 data-[state=active]:bg-transparent data-[state=active]:hover:bg-transparent",
187
- framed ? "pb-2" : "pb-1.5",
156
+ "group/tab-list-item rounded-none px-0 py-0 hover:bg-transparent active:brightness-100 data-[state=active]:bg-transparent data-[state=active]:hover:bg-transparent",
157
+ framed ? "pb-1" : "pb-1.5",
188
158
  ),
189
159
  className,
190
160
  )}
@@ -202,9 +172,9 @@ function TabsTrigger({
202
172
  >
203
173
  {hasHorizontalLine ? (
204
174
  <span
205
- data-slot="tabs-trigger-content"
175
+ data-slot="tabs-list-item-content"
206
176
  className={cn(
207
- "inline-flex items-center justify-center gap-1.5 rounded-md px-3 transition-colors hover:bg-accent hover:text-accent-foreground active:brightness-90 group-data-[state=active]/tab-trigger:text-foreground",
177
+ "inline-flex items-center justify-center gap-1.5 rounded-md px-3 transition-colors hover:bg-interaction-hover hover:text-accent-foreground active:bg-interaction-pressed group-data-[state=active]/tab-list-item:text-foreground",
208
178
  distribution === "equal" && "w-full",
209
179
  controlHeight[size],
210
180
  )}
@@ -218,17 +188,17 @@ function TabsTrigger({
218
188
  );
219
189
  }
220
190
 
221
- function TabsContent({
191
+ function TabsPanel({
222
192
  className,
223
193
  ...props
224
194
  }: React.ComponentProps<typeof TabsPrimitive.Content>) {
225
195
  return (
226
196
  <TabsPrimitive.Content
227
- data-slot="tabs-content"
197
+ data-slot="tabs-panel"
228
198
  className={cn("flex-1 outline-none", className)}
229
199
  {...props}
230
200
  />
231
201
  );
232
202
  }
233
203
 
234
- export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants };
204
+ export { Tabs, TabsList, TabsListItem, TabsPanel };
@@ -1,18 +1,18 @@
1
- import * as React from "react"
1
+ import * as React from "react";
2
2
 
3
- import { cn } from '../../lib/cn.ts'
3
+ import { cn } from "../../lib/cn.ts";
4
4
 
5
5
  function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
6
6
  return (
7
7
  <textarea
8
8
  data-slot="textarea"
9
9
  className={cn(
10
- "flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-muted/30 px-3 py-2 text-sm transition-[color,box-shadow] outline-none placeholder:text-muted-foreground 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 dark:bg-input/30 dark:aria-invalid:ring-context-danger/40",
11
- className
10
+ "flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-control px-3 py-2 text-sm transition-[color,box-shadow] outline-none placeholder:text-muted-foreground 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 dark:aria-invalid:ring-context-danger/40",
11
+ className,
12
12
  )}
13
13
  {...props}
14
14
  />
15
- )
15
+ );
16
16
  }
17
17
 
18
- export { Textarea }
18
+ export { Textarea };
@@ -0,0 +1,24 @@
1
+ import * as React from "react";
2
+
3
+ import type { ControlShape } from "./control.ts";
4
+
5
+ type ToggleGroupMode = "connected" | "spaced" | "divided" | "segmented";
6
+ type ToggleGroupDistribution = "content" | "equal";
7
+
8
+ type ToggleGroupContextValue = {
9
+ mode: ToggleGroupMode;
10
+ orientation: "horizontal" | "vertical";
11
+ variant?: "default" | "outline" | null;
12
+ size?: "default" | "sm" | "lg" | null;
13
+ shape?: ControlShape | null;
14
+ };
15
+
16
+ const ToggleGroupContext = React.createContext<ToggleGroupContextValue | null>(
17
+ null,
18
+ );
19
+
20
+ export {
21
+ ToggleGroupContext,
22
+ type ToggleGroupDistribution,
23
+ type ToggleGroupMode,
24
+ };