@dowel-ui/react 0.5.0 → 0.7.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 (218) hide show
  1. package/README.md +1 -1
  2. package/dist/blocks/admin-dashboard/meta.js +31 -0
  3. package/dist/blocks/admin-dashboard/meta.js.map +1 -0
  4. package/dist/blocks/agent-console/meta.js +27 -0
  5. package/dist/blocks/agent-console/meta.js.map +1 -0
  6. package/dist/blocks/ai-dashboard/meta.js +25 -0
  7. package/dist/blocks/ai-dashboard/meta.js.map +1 -0
  8. package/dist/blocks/ai-workspace/meta.js +33 -0
  9. package/dist/blocks/ai-workspace/meta.js.map +1 -0
  10. package/dist/blocks/analytics/meta.js +24 -0
  11. package/dist/blocks/analytics/meta.js.map +1 -0
  12. package/dist/blocks/billing/meta.js +25 -0
  13. package/dist/blocks/billing/meta.js.map +1 -0
  14. package/dist/blocks/command-center/meta.js +28 -0
  15. package/dist/blocks/command-center/meta.js.map +1 -0
  16. package/dist/blocks/crm/meta.js +32 -0
  17. package/dist/blocks/crm/meta.js.map +1 -0
  18. package/dist/blocks/onboarding/meta.js +23 -0
  19. package/dist/blocks/onboarding/meta.js.map +1 -0
  20. package/dist/components/accordion/accordion.js +1 -1
  21. package/dist/components/accordion/accordion.js.map +1 -1
  22. package/dist/components/activity-feed/activity-feed.js +1 -1
  23. package/dist/components/activity-feed/activity-feed.js.map +1 -1
  24. package/dist/components/ai-agent-plan/ai-agent-plan.js +1 -1
  25. package/dist/components/ai-agent-plan/ai-agent-plan.js.map +1 -1
  26. package/dist/components/ai-extraction-review/ai-extraction-review.js +1 -1
  27. package/dist/components/ai-extraction-review/ai-extraction-review.js.map +1 -1
  28. package/dist/components/ai-prompt-input/ai-prompt-input.js +1 -1
  29. package/dist/components/ai-prompt-input/ai-prompt-input.js.map +1 -1
  30. package/dist/components/ai-reasoning/ai-reasoning.js +1 -1
  31. package/dist/components/ai-reasoning/ai-reasoning.js.map +1 -1
  32. package/dist/components/ai-response/ai-response.js +2 -2
  33. package/dist/components/ai-response/ai-response.js.map +1 -1
  34. package/dist/components/ai-tool/ai-tool.js +2 -2
  35. package/dist/components/ai-tool/ai-tool.js.map +1 -1
  36. package/dist/components/alert/alert.d.ts +1 -1
  37. package/dist/components/avatar/avatar.d.ts +1 -1
  38. package/dist/components/badge/badge.d.ts +1 -1
  39. package/dist/components/breadcrumb/breadcrumb.d.ts +52 -0
  40. package/dist/components/breadcrumb/breadcrumb.d.ts.map +1 -0
  41. package/dist/components/breadcrumb/breadcrumb.js +141 -0
  42. package/dist/components/breadcrumb/breadcrumb.js.map +1 -0
  43. package/dist/components/breadcrumb/index.d.ts +2 -0
  44. package/dist/components/breadcrumb/index.js +2 -0
  45. package/dist/components/breadcrumb/meta.js +17 -0
  46. package/dist/components/breadcrumb/meta.js.map +1 -0
  47. package/dist/components/button/button.d.ts +1 -1
  48. package/dist/components/calendar/calendar.js +2 -2
  49. package/dist/components/calendar/calendar.js.map +1 -1
  50. package/dist/components/code-block/code-block.js +1 -1
  51. package/dist/components/code-block/code-block.js.map +1 -1
  52. package/dist/components/collapsible/collapsible.d.ts +30 -0
  53. package/dist/components/collapsible/collapsible.d.ts.map +1 -0
  54. package/dist/components/collapsible/collapsible.js +38 -0
  55. package/dist/components/collapsible/collapsible.js.map +1 -0
  56. package/dist/components/collapsible/index.d.ts +2 -0
  57. package/dist/components/collapsible/index.js +2 -0
  58. package/dist/components/collapsible/meta.js +17 -0
  59. package/dist/components/collapsible/meta.js.map +1 -0
  60. package/dist/components/combobox/combobox.js +2 -2
  61. package/dist/components/combobox/combobox.js.map +1 -1
  62. package/dist/components/command/command.js +1 -1
  63. package/dist/components/command/command.js.map +1 -1
  64. package/dist/components/data-table/data-table.d.ts.map +1 -1
  65. package/dist/components/data-table/data-table.js +4 -1
  66. package/dist/components/data-table/data-table.js.map +1 -1
  67. package/dist/components/dialog/dialog.js +2 -2
  68. package/dist/components/dialog/dialog.js.map +1 -1
  69. package/dist/components/diff-viewer/diff-viewer.js +4 -4
  70. package/dist/components/diff-viewer/diff-viewer.js.map +1 -1
  71. package/dist/components/direction/direction.d.ts +35 -0
  72. package/dist/components/direction/direction.d.ts.map +1 -0
  73. package/dist/components/direction/direction.js +14 -0
  74. package/dist/components/direction/direction.js.map +1 -0
  75. package/dist/components/direction/index.d.ts +2 -0
  76. package/dist/components/direction/index.js +2 -0
  77. package/dist/components/direction/meta.js +17 -0
  78. package/dist/components/direction/meta.js.map +1 -0
  79. package/dist/components/drawer/drawer.js +1 -1
  80. package/dist/components/drawer/drawer.js.map +1 -1
  81. package/dist/components/dropdown-menu/dropdown-menu.d.ts.map +1 -1
  82. package/dist/components/dropdown-menu/dropdown-menu.js +9 -8
  83. package/dist/components/dropdown-menu/dropdown-menu.js.map +1 -1
  84. package/dist/components/input/input.js +1 -1
  85. package/dist/components/input/input.js.map +1 -1
  86. package/dist/components/log-viewer/log-viewer.js +2 -2
  87. package/dist/components/log-viewer/log-viewer.js.map +1 -1
  88. package/dist/components/meter/meter.js +1 -1
  89. package/dist/components/meter/meter.js.map +1 -1
  90. package/dist/components/pagination/pagination.d.ts.map +1 -1
  91. package/dist/components/pagination/pagination.js +3 -2
  92. package/dist/components/pagination/pagination.js.map +1 -1
  93. package/dist/components/permission-matrix/permission-matrix.js +3 -3
  94. package/dist/components/permission-matrix/permission-matrix.js.map +1 -1
  95. package/dist/components/progress/progress.d.ts +1 -1
  96. package/dist/components/record-diff/record-diff.js +4 -4
  97. package/dist/components/record-diff/record-diff.js.map +1 -1
  98. package/dist/components/select/select.js +2 -2
  99. package/dist/components/select/select.js.map +1 -1
  100. package/dist/components/sheet/sheet.d.ts.map +1 -1
  101. package/dist/components/sheet/sheet.js +2 -2
  102. package/dist/components/sheet/sheet.js.map +1 -1
  103. package/dist/components/sidebar/index.d.ts +2 -0
  104. package/dist/components/sidebar/index.js +2 -0
  105. package/dist/components/sidebar/meta.js +17 -0
  106. package/dist/components/sidebar/meta.js.map +1 -0
  107. package/dist/components/sidebar/sidebar.d.ts +69 -0
  108. package/dist/components/sidebar/sidebar.d.ts.map +1 -0
  109. package/dist/components/sidebar/sidebar.js +256 -0
  110. package/dist/components/sidebar/sidebar.js.map +1 -0
  111. package/dist/components/spinner/spinner.d.ts +1 -1
  112. package/dist/components/table/table.js +2 -2
  113. package/dist/components/table/table.js.map +1 -1
  114. package/dist/components/tags-input/tags-input.js +1 -1
  115. package/dist/components/tags-input/tags-input.js.map +1 -1
  116. package/dist/components/textarea/index.d.ts +2 -0
  117. package/dist/components/textarea/index.js +2 -0
  118. package/dist/components/textarea/meta.js +17 -0
  119. package/dist/components/textarea/meta.js.map +1 -0
  120. package/dist/components/textarea/textarea.d.ts +43 -0
  121. package/dist/components/textarea/textarea.d.ts.map +1 -0
  122. package/dist/components/textarea/textarea.js +119 -0
  123. package/dist/components/textarea/textarea.js.map +1 -0
  124. package/dist/components/time-range-picker/time-range-picker.js +3 -3
  125. package/dist/components/time-range-picker/time-range-picker.js.map +1 -1
  126. package/dist/components/toast/toast.d.ts +1 -1
  127. package/dist/components/toast/toast.d.ts.map +1 -1
  128. package/dist/components/toast/toast.js +2 -2
  129. package/dist/components/toast/toast.js.map +1 -1
  130. package/dist/index.d.ts +12 -2
  131. package/dist/index.js +7 -2
  132. package/dist/lib/styles.d.ts +16 -1
  133. package/dist/lib/styles.d.ts.map +1 -1
  134. package/dist/lib/styles.js +16 -1
  135. package/dist/lib/styles.js.map +1 -1
  136. package/dist/registry/blocks.d.ts.map +1 -1
  137. package/dist/registry/blocks.js +27 -9
  138. package/dist/registry/blocks.js.map +1 -1
  139. package/dist/registry/components.d.ts.map +1 -1
  140. package/dist/registry/components.js +60 -50
  141. package/dist/registry/components.js.map +1 -1
  142. package/dist/registry/schema.d.ts +8 -0
  143. package/dist/registry/schema.d.ts.map +1 -1
  144. package/dist/registry/schema.js.map +1 -1
  145. package/package.json +5 -4
  146. package/src/components/accordion/accordion.tsx +1 -1
  147. package/src/components/activity-feed/activity-feed.tsx +1 -1
  148. package/src/components/ai-agent-plan/ai-agent-plan.tsx +0 -0
  149. package/src/components/ai-extraction-review/ai-extraction-review.tsx +1 -1
  150. package/src/components/ai-prompt-input/ai-prompt-input.tsx +1 -1
  151. package/src/components/ai-reasoning/ai-reasoning.tsx +1 -1
  152. package/src/components/ai-response/ai-response.tsx +2 -2
  153. package/src/components/ai-tool/ai-tool.tsx +2 -2
  154. package/src/components/breadcrumb/breadcrumb.tsx +157 -0
  155. package/src/components/breadcrumb/index.ts +10 -0
  156. package/src/components/breadcrumb/meta.ts +19 -0
  157. package/src/components/calendar/calendar.tsx +2 -2
  158. package/src/components/code-block/code-block.tsx +1 -1
  159. package/src/components/collapsible/collapsible.tsx +63 -0
  160. package/src/components/collapsible/index.ts +6 -0
  161. package/src/components/collapsible/meta.ts +16 -0
  162. package/src/components/combobox/combobox.tsx +2 -2
  163. package/src/components/command/command.tsx +1 -1
  164. package/src/components/data-table/data-table.tsx +14 -3
  165. package/src/components/dialog/dialog.tsx +2 -2
  166. package/src/components/diff-viewer/diff-viewer.tsx +4 -4
  167. package/src/components/direction/direction.tsx +38 -0
  168. package/src/components/direction/index.ts +1 -0
  169. package/src/components/direction/meta.ts +19 -0
  170. package/src/components/drawer/drawer.tsx +1 -1
  171. package/src/components/dropdown-menu/dropdown-menu.tsx +14 -8
  172. package/src/components/input/input.tsx +1 -1
  173. package/src/components/log-viewer/log-viewer.tsx +2 -2
  174. package/src/components/meter/meter.tsx +1 -1
  175. package/src/components/pagination/pagination.tsx +13 -2
  176. package/src/components/permission-matrix/permission-matrix.tsx +4 -4
  177. package/src/components/record-diff/record-diff.tsx +4 -4
  178. package/src/components/select/select.tsx +2 -2
  179. package/src/components/sheet/sheet.tsx +8 -4
  180. package/src/components/sidebar/index.ts +19 -0
  181. package/src/components/sidebar/meta.ts +25 -0
  182. package/src/components/sidebar/sidebar.tsx +374 -0
  183. package/src/components/table/table.tsx +3 -3
  184. package/src/components/tags-input/tags-input.tsx +1 -1
  185. package/src/components/textarea/index.ts +1 -0
  186. package/src/components/textarea/meta.ts +21 -0
  187. package/src/components/textarea/textarea.tsx +210 -0
  188. package/src/components/time-range-picker/time-range-picker.tsx +3 -3
  189. package/src/components/toast/toast.tsx +14 -6
  190. package/src/index.ts +5 -0
  191. package/src/lib/styles.ts +16 -0
  192. package/src/registry/blocks.ts +18 -0
  193. package/src/registry/components.ts +10 -0
  194. package/src/registry/schema.ts +8 -0
  195. package/src/blocks/admin-users/admin-users.tsx +0 -251
  196. package/src/blocks/admin-users/index.ts +0 -1
  197. package/src/blocks/admin-users/meta.ts +0 -27
  198. package/src/blocks/ai-chat/ai-chat.tsx +0 -280
  199. package/src/blocks/ai-chat/index.ts +0 -7
  200. package/src/blocks/ai-chat/meta.ts +0 -32
  201. package/src/blocks/dashboard/dashboard.tsx +0 -192
  202. package/src/blocks/dashboard/index.ts +0 -6
  203. package/src/blocks/dashboard/meta.ts +0 -18
  204. package/src/blocks/forgot-password/forgot-password.tsx +0 -158
  205. package/src/blocks/forgot-password/index.ts +0 -1
  206. package/src/blocks/forgot-password/meta.ts +0 -18
  207. package/src/blocks/login/index.ts +0 -1
  208. package/src/blocks/login/login.tsx +0 -178
  209. package/src/blocks/login/meta.ts +0 -18
  210. package/src/blocks/pricing/index.ts +0 -1
  211. package/src/blocks/pricing/meta.ts +0 -18
  212. package/src/blocks/pricing/pricing.tsx +0 -176
  213. package/src/blocks/settings/index.ts +0 -6
  214. package/src/blocks/settings/meta.ts +0 -29
  215. package/src/blocks/settings/settings.tsx +0 -317
  216. package/src/blocks/signup/index.ts +0 -1
  217. package/src/blocks/signup/meta.ts +0 -18
  218. package/src/blocks/signup/signup.tsx +0 -240
@@ -0,0 +1,157 @@
1
+ import { Slot } from "radix-ui";
2
+ import type { ComponentPropsWithRef } from "react";
3
+
4
+ import { focusRing, mirrorForDirection } from "@/lib/styles";
5
+ import { cn } from "@/lib/utils";
6
+
7
+ /**
8
+ * Where this page sits in the hierarchy.
9
+ *
10
+ * Three things separate a breadcrumb that works from one that only looks like
11
+ * one, and all three are about the last item.
12
+ *
13
+ * It is not a link. A link to the page you are already on is a control that
14
+ * does nothing, and in a screen reader's list of links it is indistinguishable
15
+ * from the ones that go somewhere.
16
+ *
17
+ * It carries `aria-current="page"`. Bolding it says "you are here" to people
18
+ * who can see it and to nobody else.
19
+ *
20
+ * The separators are hidden. A slash between every item is read aloud as
21
+ * "slash" — "Home slash Projects slash Settings" — which is the punctuation of
22
+ * the design leaking into the content.
23
+ */
24
+
25
+ export function Breadcrumb({ className, ...props }: ComponentPropsWithRef<"nav">) {
26
+ return (
27
+ <nav
28
+ // Named, because a page can have more than one navigation landmark and
29
+ // "navigation" on its own does not say which this is.
30
+ aria-label="Breadcrumb"
31
+ data-slot="breadcrumb"
32
+ className={cn("min-w-0", className)}
33
+ {...props}
34
+ />
35
+ );
36
+ }
37
+
38
+ export function BreadcrumbList({ className, ...props }: ComponentPropsWithRef<"ol">) {
39
+ return (
40
+ <ol
41
+ data-slot="breadcrumb-list"
42
+ className={cn(
43
+ "flex flex-wrap items-center gap-1.5 text-sm text-muted-foreground",
44
+ className,
45
+ )}
46
+ {...props}
47
+ />
48
+ );
49
+ }
50
+
51
+ export function BreadcrumbItem({ className, ...props }: ComponentPropsWithRef<"li">) {
52
+ return (
53
+ <li
54
+ data-slot="breadcrumb-item"
55
+ className={cn("inline-flex items-center gap-1.5", className)}
56
+ {...props}
57
+ />
58
+ );
59
+ }
60
+
61
+ export interface BreadcrumbLinkProps extends ComponentPropsWithRef<"a"> {
62
+ /** Render the child as the link, for a router's own component. */
63
+ asChild?: boolean;
64
+ }
65
+
66
+ export function BreadcrumbLink({ className, asChild = false, ...props }: BreadcrumbLinkProps) {
67
+ const Component = asChild ? Slot.Root : "a";
68
+
69
+ return (
70
+ <Component
71
+ data-slot="breadcrumb-link"
72
+ className={cn(
73
+ "rounded-sm transition-colors duration-[var(--duration-fast)]",
74
+ "hover:text-foreground",
75
+ focusRing,
76
+ className,
77
+ )}
78
+ {...props}
79
+ />
80
+ );
81
+ }
82
+
83
+ /**
84
+ * The page you are on.
85
+ *
86
+ * A span, not a link: there is nowhere to go. `aria-current="page"` is what
87
+ * says "you are here" to anything that is not looking at the font weight.
88
+ */
89
+ export function BreadcrumbPage({ className, ...props }: ComponentPropsWithRef<"span">) {
90
+ return (
91
+ <span
92
+ data-slot="breadcrumb-page"
93
+ aria-current="page"
94
+ className={cn("font-medium text-foreground", className)}
95
+ {...props}
96
+ />
97
+ );
98
+ }
99
+
100
+ /**
101
+ * The mark between items.
102
+ *
103
+ * Hidden from assistive technology and given no accessible name: the list
104
+ * structure already conveys the sequence, and reading "slash" between every
105
+ * item is the design's punctuation leaking into the content.
106
+ */
107
+ export function BreadcrumbSeparator({
108
+ className,
109
+ children,
110
+ ...props
111
+ }: ComponentPropsWithRef<"li">) {
112
+ return (
113
+ <li
114
+ data-slot="breadcrumb-separator"
115
+ role="presentation"
116
+ aria-hidden
117
+ className={cn("[&>svg]:size-3.5", className)}
118
+ {...props}
119
+ >
120
+ {children ?? (
121
+ <svg viewBox="0 0 24 24" fill="none" className={mirrorForDirection}>
122
+ <path
123
+ d="m9 18 6-6-6-6"
124
+ stroke="currentColor"
125
+ strokeWidth="2"
126
+ strokeLinecap="round"
127
+ strokeLinejoin="round"
128
+ />
129
+ </svg>
130
+ )}
131
+ </li>
132
+ );
133
+ }
134
+
135
+ /**
136
+ * Stands in for the middle of a long trail.
137
+ *
138
+ * Named rather than hidden, because unlike a separator it is *content*: it
139
+ * says levels have been left out. A bare "…" announces as nothing at all.
140
+ */
141
+ export function BreadcrumbEllipsis({ className, ...props }: ComponentPropsWithRef<"span">) {
142
+ return (
143
+ <span
144
+ data-slot="breadcrumb-ellipsis"
145
+ role="presentation"
146
+ className={cn("flex size-5 items-center justify-center", className)}
147
+ {...props}
148
+ >
149
+ <svg viewBox="0 0 24 24" fill="none" aria-hidden className="size-4">
150
+ <circle cx="5" cy="12" r="1.5" fill="currentColor" />
151
+ <circle cx="12" cy="12" r="1.5" fill="currentColor" />
152
+ <circle cx="19" cy="12" r="1.5" fill="currentColor" />
153
+ </svg>
154
+ <span className="sr-only">Intermediate levels</span>
155
+ </span>
156
+ );
157
+ }
@@ -0,0 +1,10 @@
1
+ export {
2
+ Breadcrumb,
3
+ BreadcrumbEllipsis,
4
+ BreadcrumbItem,
5
+ BreadcrumbLink,
6
+ BreadcrumbList,
7
+ BreadcrumbPage,
8
+ BreadcrumbSeparator,
9
+ type BreadcrumbLinkProps,
10
+ } from "./breadcrumb";
@@ -0,0 +1,19 @@
1
+ import { defineMeta } from "@/registry/schema";
2
+
3
+ export const meta = defineMeta({
4
+ name: "breadcrumb",
5
+ title: "Breadcrumb",
6
+ description: "Where the current page sits in the hierarchy, and how to get back up it.",
7
+ category: "navigation",
8
+ status: "stable",
9
+ dependencies: ["radix-ui"],
10
+ registryDependencies: [],
11
+ files: ["breadcrumb.tsx"],
12
+ a11y:
13
+ 'The current page is a span carrying aria-current="page", not a link: a link to the page ' +
14
+ "you are already on does nothing, and in a list of links it is indistinguishable from the " +
15
+ "ones that go somewhere. Separators are hidden from assistive technology, because the list " +
16
+ 'already conveys the sequence and reading "slash" between every item is the design\'s ' +
17
+ "punctuation leaking into the content. The ellipsis is the exception and is named, because " +
18
+ "it is content: it says levels have been left out.",
19
+ });
@@ -70,12 +70,12 @@ export function Calendar({
70
70
  ),
71
71
  // Range middles keep the muted surface so the two ends stay legible as
72
72
  // the actual selection.
73
- range_start: cn(defaults.range_start, "rounded-l-md bg-accent"),
73
+ range_start: cn(defaults.range_start, "rounded-s-md bg-accent"),
74
74
  range_middle: cn(
75
75
  defaults.range_middle,
76
76
  "bg-accent [&>button]:bg-transparent [&>button]:text-accent-foreground",
77
77
  ),
78
- range_end: cn(defaults.range_end, "rounded-r-md bg-accent"),
78
+ range_end: cn(defaults.range_end, "rounded-e-md bg-accent"),
79
79
  today: cn(defaults.today, "[&>button]:font-semibold [&>button]:text-primary"),
80
80
  outside: cn(defaults.outside, "text-muted-foreground opacity-50"),
81
81
  disabled: cn(defaults.disabled, "opacity-40"),
@@ -143,7 +143,7 @@ export function CopyButton({
143
143
  onClick={copy}
144
144
  aria-label={label}
145
145
  className={cn(
146
- "ml-auto grid size-6 shrink-0 place-items-center rounded text-muted-foreground",
146
+ "ms-auto grid size-6 shrink-0 place-items-center rounded text-muted-foreground",
147
147
  "transition-colors duration-[var(--duration-fast)] hover:bg-accent hover:text-foreground",
148
148
  focusRing,
149
149
  className,
@@ -0,0 +1,63 @@
1
+ "use client";
2
+
3
+ import { Collapsible as CollapsiblePrimitive } from "radix-ui";
4
+ import type { ComponentPropsWithRef } from "react";
5
+
6
+ import { cn } from "@/lib/utils";
7
+
8
+ /**
9
+ * A section that opens and closes.
10
+ *
11
+ * Unlike Accordion, there is no set: one region, one trigger, no notion of
12
+ * "only one open at a time". Reaching for an Accordion with a single item
13
+ * gives that item a `heading` role and a position in a list of one, both of
14
+ * which are noise.
15
+ *
16
+ * The height animation is driven by the primitive's own measured height rather
17
+ * than a guess, so content of any size opens smoothly, and it derives from
18
+ * `--duration-*` so the motion scale applies.
19
+ */
20
+
21
+ export type CollapsibleProps = ComponentPropsWithRef<typeof CollapsiblePrimitive.Root>;
22
+
23
+ /**
24
+ * Wrapped rather than re-exported.
25
+ *
26
+ * A bare `export const Collapsible = CollapsiblePrimitive.Root` leaves the
27
+ * inferred type pointing into the primitive's own package, which TypeScript
28
+ * cannot name in declaration output (TS2883). It also gives every other
29
+ * component's `data-slot` a hole where this one should be.
30
+ */
31
+ export function Collapsible({ className, ...props }: CollapsibleProps) {
32
+ return <CollapsiblePrimitive.Root data-slot="collapsible" className={className} {...props} />;
33
+ }
34
+
35
+ export function CollapsibleTrigger({
36
+ className,
37
+ ...props
38
+ }: ComponentPropsWithRef<typeof CollapsiblePrimitive.Trigger>) {
39
+ return (
40
+ <CollapsiblePrimitive.Trigger
41
+ data-slot="collapsible-trigger"
42
+ className={cn("outline-none", className)}
43
+ {...props}
44
+ />
45
+ );
46
+ }
47
+
48
+ export function CollapsibleContent({
49
+ className,
50
+ ...props
51
+ }: ComponentPropsWithRef<typeof CollapsiblePrimitive.Content>) {
52
+ return (
53
+ <CollapsiblePrimitive.Content
54
+ data-slot="collapsible-content"
55
+ className={cn(
56
+ "overflow-hidden",
57
+ "data-[state=closed]:animate-collapse-up data-[state=open]:animate-collapse-down",
58
+ className,
59
+ )}
60
+ {...props}
61
+ />
62
+ );
63
+ }
@@ -0,0 +1,6 @@
1
+ export {
2
+ Collapsible,
3
+ CollapsibleContent,
4
+ CollapsibleTrigger,
5
+ type CollapsibleProps,
6
+ } from "./collapsible";
@@ -0,0 +1,16 @@
1
+ import { defineMeta } from "@/registry/schema";
2
+
3
+ export const meta = defineMeta({
4
+ name: "collapsible",
5
+ title: "Collapsible",
6
+ description: "A single section that opens and closes, without an accordion's set semantics.",
7
+ category: "layout",
8
+ status: "stable",
9
+ dependencies: ["radix-ui"],
10
+ registryDependencies: [],
11
+ files: ["collapsible.tsx"],
12
+ a11y:
13
+ "The trigger carries aria-expanded and points at the region it controls, both from the " +
14
+ "primitive. Reach for this rather than an Accordion of one item: a single-item Accordion " +
15
+ "gives its trigger a heading role and a position in a list of one, neither of which is true.",
16
+ });
@@ -499,7 +499,7 @@ export function ComboboxItem({
499
499
  }
500
500
  }}
501
501
  className={cn(
502
- "relative flex cursor-default items-center gap-2 rounded-md py-1.5 pr-8 pl-2 text-sm outline-none select-none",
502
+ "relative flex cursor-default items-center gap-2 rounded-md py-1.5 ps-2 pe-8 text-sm outline-none select-none",
503
503
  "transition-colors duration-[var(--duration-instant)]",
504
504
  "data-[active]:bg-accent data-[active]:text-accent-foreground",
505
505
  "data-[disabled]:pointer-events-none data-[disabled]:opacity-55",
@@ -510,7 +510,7 @@ export function ComboboxItem({
510
510
  >
511
511
  {children ?? value}
512
512
  {selected ? (
513
- <span className="absolute right-2 grid size-4 place-items-center">
513
+ <span className="absolute end-2 grid size-4 place-items-center">
514
514
  <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className="size-3.5">
515
515
  <path
516
516
  d="m5 13 4 4L19 7"
@@ -438,7 +438,7 @@ export function CommandShortcut({ className, ...props }: ComponentPropsWithRef<"
438
438
  <span
439
439
  data-slot="command-shortcut"
440
440
  aria-hidden="true"
441
- className={cn("ml-auto text-2xs tracking-wide text-muted-foreground", className)}
441
+ className={cn("ms-auto text-2xs tracking-wide text-muted-foreground", className)}
442
442
  {...props}
443
443
  />
444
444
  );
@@ -33,6 +33,7 @@ import {
33
33
  TableHeader,
34
34
  TableRow,
35
35
  } from "@/components/table";
36
+ import { mirrorForDirection } from "@/lib/styles";
36
37
  import { cn } from "@/lib/utils";
37
38
 
38
39
  /**
@@ -253,7 +254,7 @@ export function DataTableColumnHeader({
253
254
  <Button
254
255
  variant="ghost"
255
256
  size="sm"
256
- className="-ml-2 h-7 gap-1 px-2 font-medium text-muted-foreground data-[state=open]:bg-accent"
257
+ className="-ms-2 h-7 gap-1 px-2 font-medium text-muted-foreground data-[state=open]:bg-accent"
257
258
  >
258
259
  {title}
259
260
  <SortIcon direction={sorted === false ? undefined : sorted} />
@@ -449,7 +450,12 @@ export function DataTablePagination({
449
450
  table.previousPage();
450
451
  }}
451
452
  >
452
- <svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
453
+ <svg
454
+ viewBox="0 0 24 24"
455
+ fill="none"
456
+ aria-hidden="true"
457
+ className={mirrorForDirection}
458
+ >
453
459
  <path
454
460
  d="m15 18-6-6 6-6"
455
461
  stroke="currentColor"
@@ -468,7 +474,12 @@ export function DataTablePagination({
468
474
  table.nextPage();
469
475
  }}
470
476
  >
471
- <svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
477
+ <svg
478
+ viewBox="0 0 24 24"
479
+ fill="none"
480
+ aria-hidden="true"
481
+ className={mirrorForDirection}
482
+ >
472
483
  <path
473
484
  d="m9 18 6-6-6-6"
474
485
  stroke="currentColor"
@@ -66,7 +66,7 @@ export function DialogContent({
66
66
  data-slot="dialog-close"
67
67
  aria-label="Close"
68
68
  className={cn(
69
- "absolute top-4 right-4 grid size-7 place-items-center rounded-md text-muted-foreground",
69
+ "absolute end-4 top-4 grid size-7 place-items-center rounded-md text-muted-foreground",
70
70
  "transition-colors duration-[var(--duration-fast)] hover:bg-accent hover:text-foreground",
71
71
  "[&_svg]:size-4",
72
72
  focusRing,
@@ -91,7 +91,7 @@ export function DialogHeader({ className, ...props }: ComponentPropsWithRef<"div
91
91
  return (
92
92
  <div
93
93
  data-slot="dialog-header"
94
- className={cn("flex flex-col gap-1.5 pr-8 text-left", className)}
94
+ className={cn("flex flex-col gap-1.5 pe-8 text-start", className)}
95
95
  {...props}
96
96
  />
97
97
  );
@@ -197,10 +197,10 @@ function UnifiedRow({ row }: { row: DiffRow }) {
197
197
  <tr data-slot="diff-row" data-kind={row.kind} className={ROW_STYLES[row.kind]}>
198
198
  <LineNumber value={row.before} />
199
199
  <LineNumber value={row.after} />
200
- <td className="w-4 pr-1 text-center text-muted-foreground select-none" aria-hidden="true">
200
+ <td className="w-4 pe-1 text-center text-muted-foreground select-none" aria-hidden="true">
201
201
  {row.kind === "added" ? "+" : row.kind === "removed" ? "−" : ""}
202
202
  </td>
203
- <td className="w-full py-0.5 pr-3 break-all whitespace-pre-wrap">
203
+ <td className="w-full py-0.5 pe-3 break-all whitespace-pre-wrap">
204
204
  {/* The kind, for anyone who cannot see the sign or the tint. Reading a
205
205
  diff aloud without it is reading the same file twice. */}
206
206
  <span className="sr-only">{KIND_LABEL[row.kind]}: </span>
@@ -226,7 +226,7 @@ function SplitCell({ row, side }: { row: DiffRow | null; side: "before" | "after
226
226
  <>
227
227
  <LineNumber value={side === "before" ? row.before : row.after} />
228
228
  <td
229
- className={cn("w-1/2 py-0.5 pr-3 break-all whitespace-pre-wrap", ROW_STYLES[row.kind])}
229
+ className={cn("w-1/2 py-0.5 pe-3 break-all whitespace-pre-wrap", ROW_STYLES[row.kind])}
230
230
  >
231
231
  <span className="sr-only">{KIND_LABEL[row.kind]}: </span>
232
232
  <RowContent row={row} />
@@ -267,7 +267,7 @@ function LineNumber({ value }: { value?: number }) {
267
267
  // Decorative: the line number is orientation for a sighted reader, and
268
268
  // announcing two numbers before every line makes the diff unlistenable.
269
269
  aria-hidden="true"
270
- className="w-10 border-r border-border bg-muted/40 px-2 py-0.5 text-right text-muted-foreground tabular-nums select-none"
270
+ className="w-10 border-e border-border bg-muted/40 px-2 py-0.5 text-end text-muted-foreground tabular-nums select-none"
271
271
  >
272
272
  {value ?? ""}
273
273
  </td>
@@ -0,0 +1,38 @@
1
+ "use client";
2
+
3
+ import { Direction as DirectionPrimitive } from "radix-ui";
4
+ import type { ReactNode } from "react";
5
+
6
+ /**
7
+ * Tells the component set which way the writing runs.
8
+ *
9
+ * Every direction-dependent style in this library is written logically —
10
+ * `ps-`, `me-`, `text-end` — so the CSS follows `dir` on its own. The
11
+ * primitives underneath do not: several of them read direction from React
12
+ * context rather than from the document, and with no provider they assume
13
+ * left-to-right and set `dir="ltr"` on their own elements. The result is a page
14
+ * that mirrors correctly except for its menus, selects and sliders, which is
15
+ * worse than one that does not mirror at all, because it looks deliberate.
16
+ *
17
+ * So an RTL application needs both: `dir` on the document for the CSS, and this
18
+ * for the primitives.
19
+ *
20
+ * ```tsx
21
+ * <html dir="rtl" lang="ar">
22
+ * <body>
23
+ * <DirectionProvider dir="rtl">{children}</DirectionProvider>
24
+ * </body>
25
+ * </html>
26
+ * ```
27
+ *
28
+ * A left-to-right application needs neither. Nothing here is required to render
29
+ * English correctly, which is why it is easy to ship without it and never know.
30
+ */
31
+ export interface DirectionProviderProps {
32
+ dir: "ltr" | "rtl";
33
+ children?: ReactNode;
34
+ }
35
+
36
+ export function DirectionProvider({ dir, children }: DirectionProviderProps) {
37
+ return <DirectionPrimitive.Provider dir={dir}>{children}</DirectionPrimitive.Provider>;
38
+ }
@@ -0,0 +1 @@
1
+ export { DirectionProvider, type DirectionProviderProps } from "./direction";
@@ -0,0 +1,19 @@
1
+ import { defineMeta } from "@/registry/schema";
2
+
3
+ export const meta = defineMeta({
4
+ name: "direction",
5
+ title: "Direction Provider",
6
+ description:
7
+ "Tells the component primitives which way the writing runs, so menus and sliders mirror with the rest of the page.",
8
+ category: "foundation",
9
+ status: "stable",
10
+ dependencies: ["radix-ui"],
11
+ registryDependencies: [],
12
+ files: ["direction.tsx"],
13
+ a11y:
14
+ "Needed only for right-to-left languages, and needed in addition to `dir` on the document " +
15
+ "rather than instead of it. The styling here is written with logical properties and follows " +
16
+ "`dir` by itself; the primitives underneath read direction from React context and assume " +
17
+ "left-to-right without a provider, which mirrors a page correctly except for its menus, " +
18
+ "selects and sliders — worse than not mirroring at all, because it looks deliberate.",
19
+ });
@@ -221,7 +221,7 @@ export function DrawerHeader({ className, ...props }: ComponentPropsWithRef<"div
221
221
  return (
222
222
  <div
223
223
  data-slot="drawer-header"
224
- className={cn("flex shrink-0 flex-col gap-1.5 px-6 pt-4 pb-2 text-left", className)}
224
+ className={cn("flex shrink-0 flex-col gap-1.5 px-6 pt-4 pb-2 text-start", className)}
225
225
  {...props}
226
226
  />
227
227
  );
@@ -3,6 +3,7 @@
3
3
  import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui";
4
4
  import type { ComponentPropsWithRef } from "react";
5
5
 
6
+ import { mirrorForDirection } from "@/lib/styles";
6
7
  import { cn } from "@/lib/utils";
7
8
 
8
9
  /**
@@ -80,7 +81,7 @@ export function DropdownMenuItem({
80
81
  data-variant={variant}
81
82
  className={cn(
82
83
  menuItem,
83
- inset && "pl-8",
84
+ inset && "ps-8",
84
85
  variant === "destructive" &&
85
86
  "text-destructive data-[highlighted]:bg-destructive/10 data-[highlighted]:text-destructive",
86
87
  className,
@@ -94,7 +95,7 @@ export function DropdownMenuItem({
94
95
  * aria-checked, so announcing it twice would be noise. */
95
96
  function ItemIndicator({ radio = false }: { radio?: boolean }) {
96
97
  return (
97
- <span className="pointer-events-none absolute left-2 grid size-4 place-items-center">
98
+ <span className="pointer-events-none absolute start-2 grid size-4 place-items-center">
98
99
  <DropdownMenuPrimitive.ItemIndicator>
99
100
  {radio ? (
100
101
  <span className="size-2 rounded-full bg-current" />
@@ -122,7 +123,7 @@ export function DropdownMenuCheckboxItem({
122
123
  return (
123
124
  <DropdownMenuPrimitive.CheckboxItem
124
125
  data-slot="dropdown-menu-checkbox-item"
125
- className={cn(menuItem, "pl-8", className)}
126
+ className={cn(menuItem, "ps-8", className)}
126
127
  {...props}
127
128
  >
128
129
  <ItemIndicator />
@@ -139,7 +140,7 @@ export function DropdownMenuRadioItem({
139
140
  return (
140
141
  <DropdownMenuPrimitive.RadioItem
141
142
  data-slot="dropdown-menu-radio-item"
142
- className={cn(menuItem, "pl-8", className)}
143
+ className={cn(menuItem, "ps-8", className)}
143
144
  {...props}
144
145
  >
145
146
  <ItemIndicator radio />
@@ -160,7 +161,7 @@ export function DropdownMenuLabel({ className, inset, ...props }: DropdownMenuLa
160
161
  data-slot="dropdown-menu-label"
161
162
  className={cn(
162
163
  "px-2 py-1.5 text-xs font-medium text-muted-foreground",
163
- inset && "pl-8",
164
+ inset && "ps-8",
164
165
  className,
165
166
  )}
166
167
  {...props}
@@ -188,7 +189,7 @@ export function DropdownMenuShortcut({ className, ...props }: ComponentPropsWith
188
189
  <span
189
190
  data-slot="dropdown-menu-shortcut"
190
191
  aria-hidden="true"
191
- className={cn("ml-auto text-2xs tracking-wide text-muted-foreground", className)}
192
+ className={cn("ms-auto text-2xs tracking-wide text-muted-foreground", className)}
192
193
  {...props}
193
194
  />
194
195
  );
@@ -209,11 +210,16 @@ export function DropdownMenuSubTrigger({
209
210
  return (
210
211
  <DropdownMenuPrimitive.SubTrigger
211
212
  data-slot="dropdown-menu-sub-trigger"
212
- className={cn(menuItem, "data-[state=open]:bg-accent", inset && "pl-8", className)}
213
+ className={cn(menuItem, "data-[state=open]:bg-accent", inset && "ps-8", className)}
213
214
  {...props}
214
215
  >
215
216
  {children}
216
- <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className="ml-auto size-3.5">
217
+ <svg
218
+ viewBox="0 0 24 24"
219
+ fill="none"
220
+ aria-hidden="true"
221
+ className={cn("ms-auto size-3.5", mirrorForDirection)}
222
+ >
217
223
  <path
218
224
  d="m9 18 6-6-6-6"
219
225
  stroke="currentColor"
@@ -9,7 +9,7 @@ const inputVariants = cva(
9
9
  "flex w-full min-w-0 rounded-md border border-input bg-background text-foreground shadow-xs",
10
10
  "transition-[border-color,box-shadow] duration-[var(--duration-fast)] ease-[var(--ease-out-quint)]",
11
11
  "placeholder:text-muted-foreground",
12
- "file:mr-3 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground",
12
+ "file:me-3 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground",
13
13
  "focus-visible:border-ring",
14
14
  "disabled:cursor-not-allowed disabled:opacity-55",
15
15
  focusRingInset,
@@ -203,7 +203,7 @@ export function LogViewer({
203
203
  data-index={item.index}
204
204
  data-slot="log-viewer-row"
205
205
  data-level={line.level}
206
- className="absolute top-0 left-0 w-full"
206
+ className="absolute start-0 top-0 w-full"
207
207
  style={{ transform: `translateY(${String(item.start)}px)` }}
208
208
  >
209
209
  <LogViewerRow
@@ -296,7 +296,7 @@ export function LogViewerRow({ line, expanded = false, onToggle }: LogViewerRowP
296
296
  {expanded && line.fields ? (
297
297
  <dl
298
298
  data-slot="log-viewer-fields"
299
- className="mt-1 mb-1 ml-4 grid grid-cols-[auto_1fr] gap-x-3 gap-y-0.5 border-l border-border pl-3"
299
+ className="ms-4 mt-1 mb-1 grid grid-cols-[auto_1fr] gap-x-3 gap-y-0.5 border-s border-border ps-3"
300
300
  >
301
301
  {Object.entries(line.fields).map(([key, value]) => (
302
302
  <div key={key} className="contents">
@@ -141,7 +141,7 @@ export function Meter({
141
141
  data-slot="meter-segment"
142
142
  data-tone={segment.tone ?? "primary"}
143
143
  className={cn(
144
- "h-full first:rounded-l-full last:rounded-r-full",
144
+ "h-full first:rounded-s-full last:rounded-e-full",
145
145
  "transition-[width] duration-[var(--duration-slow)] ease-[var(--ease-out-quint)]",
146
146
  TONES[segment.tone ?? "primary"],
147
147
  )}
@@ -2,6 +2,7 @@ import { Slot } from "radix-ui";
2
2
  import type { ComponentPropsWithRef } from "react";
3
3
 
4
4
  import { buttonVariants } from "@/components/button";
5
+ import { mirrorForDirection } from "@/lib/styles";
5
6
  import { cn } from "@/lib/utils";
6
7
 
7
8
  /**
@@ -80,7 +81,12 @@ export function PaginationPrevious({ className, ...props }: PaginationLinkProps)
80
81
  className={cn("gap-1 px-2.5", className)}
81
82
  {...props}
82
83
  >
83
- <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className="size-4">
84
+ <svg
85
+ viewBox="0 0 24 24"
86
+ fill="none"
87
+ aria-hidden="true"
88
+ className={cn("size-4", mirrorForDirection)}
89
+ >
84
90
  <path
85
91
  d="m15 18-6-6 6-6"
86
92
  stroke="currentColor"
@@ -103,7 +109,12 @@ export function PaginationNext({ className, ...props }: PaginationLinkProps) {
103
109
  {...props}
104
110
  >
105
111
  <span className="hidden sm:inline">Next</span>
106
- <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className="size-4">
112
+ <svg
113
+ viewBox="0 0 24 24"
114
+ fill="none"
115
+ aria-hidden="true"
116
+ className={cn("size-4", mirrorForDirection)}
117
+ >
107
118
  <path
108
119
  d="m9 18 6-6-6-6"
109
120
  stroke="currentColor"