@carlonicora/nextjs-jsonapi 3.3.9 → 3.4.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 (175) hide show
  1. package/dist/{BlockNoteEditor-BYMRWUVG.mjs → BlockNoteEditor-3D5D7YL7.mjs} +2 -2
  2. package/dist/{BlockNoteEditor-USRMFZGP.js → BlockNoteEditor-F6TGKMTW.js} +9 -9
  3. package/dist/{BlockNoteEditor-USRMFZGP.js.map → BlockNoteEditor-F6TGKMTW.js.map} +1 -1
  4. package/dist/billing/index.js +347 -347
  5. package/dist/billing/index.js.map +1 -1
  6. package/dist/billing/index.mjs +27 -27
  7. package/dist/billing/index.mjs.map +1 -1
  8. package/dist/{chunk-CFZW6GU4.mjs → chunk-G5LSXG5K.mjs} +2572 -2482
  9. package/dist/chunk-G5LSXG5K.mjs.map +1 -0
  10. package/dist/{chunk-5WFYQE22.js → chunk-KT4CTWCV.js} +825 -735
  11. package/dist/chunk-KT4CTWCV.js.map +1 -0
  12. package/dist/client/index.d.mts +5 -0
  13. package/dist/client/index.d.ts +5 -0
  14. package/dist/client/index.js +2 -2
  15. package/dist/client/index.mjs +1 -1
  16. package/dist/components/index.d.mts +12 -6
  17. package/dist/components/index.d.ts +12 -6
  18. package/dist/components/index.js +2 -2
  19. package/dist/components/index.mjs +1 -1
  20. package/dist/contexts/index.d.mts +13 -1
  21. package/dist/contexts/index.d.ts +13 -1
  22. package/dist/contexts/index.js +6 -2
  23. package/dist/contexts/index.js.map +1 -1
  24. package/dist/contexts/index.mjs +5 -1
  25. package/dist/features/help/index.js +35 -35
  26. package/dist/features/help/index.js.map +1 -1
  27. package/dist/features/help/index.mjs +6 -6
  28. package/dist/features/help/index.mjs.map +1 -1
  29. package/dist/features/tokenusage/index.js +135 -129
  30. package/dist/features/tokenusage/index.js.map +1 -1
  31. package/dist/features/tokenusage/index.mjs +74 -68
  32. package/dist/features/tokenusage/index.mjs.map +1 -1
  33. package/package.json +2 -2
  34. package/scripts/check-rtl-classes.mjs +60 -0
  35. package/scripts/check-rtl-classes.test.ts +25 -0
  36. package/src/components/TableCellAvatar.tsx +1 -1
  37. package/src/components/containers/PageContainer.tsx +1 -1
  38. package/src/components/containers/ReactMarkdownContainer.tsx +20 -4
  39. package/src/components/containers/RoundPageContainer.tsx +7 -7
  40. package/src/components/contents/AttributeElement.tsx +1 -1
  41. package/src/components/editors/BlockNoteDiffInlineContent.tsx +2 -2
  42. package/src/components/editors/BlockNoteEditorMentionHoverCard.tsx +1 -0
  43. package/src/components/forms/CommonDeleter.tsx +1 -1
  44. package/src/components/forms/CommonEditorButtons.tsx +1 -1
  45. package/src/components/forms/DatePickerPopover.tsx +2 -2
  46. package/src/components/forms/DateRangeSelector.tsx +2 -2
  47. package/src/components/forms/EditorSheet.tsx +18 -9
  48. package/src/components/forms/EntityMultiSelector.tsx +7 -7
  49. package/src/components/forms/EntitySelector.tsx +8 -8
  50. package/src/components/forms/FileUploader.tsx +1 -0
  51. package/src/components/forms/FormCheckbox.tsx +3 -3
  52. package/src/components/forms/FormDateTime.tsx +3 -3
  53. package/src/components/forms/FormFieldWrapper.tsx +1 -1
  54. package/src/components/forms/FormPlaceAutocomplete.tsx +2 -2
  55. package/src/components/forms/GdprConsentCheckbox.tsx +1 -1
  56. package/src/components/forms/MultiFileUploader.tsx +1 -1
  57. package/src/components/forms/PasswordInput.tsx +1 -1
  58. package/src/components/forms/__tests__/FormCheckbox.test.tsx +4 -4
  59. package/src/components/navigations/Header.tsx +2 -2
  60. package/src/components/navigations/PageSection.tsx +1 -1
  61. package/src/components/pages/PageContainerContentDetails.tsx +1 -1
  62. package/src/components/pages/PageContentContainer.tsx +5 -2
  63. package/src/components/tables/ContentListTable.tsx +4 -4
  64. package/src/components/tables/ContentTableSearch.tsx +5 -5
  65. package/src/contexts/DirectionContext.tsx +25 -0
  66. package/src/contexts/__tests__/DirectionContext.test.tsx +22 -0
  67. package/src/contexts/index.ts +1 -0
  68. package/src/features/ai-connection/components/lists/AiConnectionTypeCard.tsx +1 -1
  69. package/src/features/assistant/components/containers/AssistantPageContainer.tsx +1 -1
  70. package/src/features/assistant/components/parts/AssistantBlockNoteComposer.tsx +2 -2
  71. package/src/features/assistant/components/parts/AssistantComposer.tsx +2 -2
  72. package/src/features/assistant/components/parts/AssistantEmptyState.tsx +1 -1
  73. package/src/features/assistant/components/parts/AssistantSidebar.tsx +3 -3
  74. package/src/features/assistant-action/components/ApprovalActionCard.tsx +1 -1
  75. package/src/features/assistant-action/components/AssistantContainerWithApprovals.tsx +1 -1
  76. package/src/features/assistant-message/components/MessageItem.tsx +3 -3
  77. package/src/features/assistant-message/components/__tests__/MessageItem.spec.tsx +1 -0
  78. package/src/features/assistant-message/components/parts/MessageSourcesPanel.tsx +2 -2
  79. package/src/features/assistant-message/components/parts/RelevanceMeter.tsx +1 -1
  80. package/src/features/assistant-message/components/parts/tabs/SuggestedQuestionsTab.tsx +1 -1
  81. package/src/features/auth/components/buttons/GoogleSignInButton.tsx +1 -1
  82. package/src/features/auth/components/forms/Register.tsx +2 -2
  83. package/src/features/auth/components/two-factor/BackupCodesDialog.tsx +3 -3
  84. package/src/features/auth/utils/__tests__/clearClientStorage.spec.ts +4 -4
  85. package/src/features/billing/components/cards/BillingUsageSummaryCard.tsx +1 -1
  86. package/src/features/billing/components/cards/CustomerInfoCard.tsx +1 -1
  87. package/src/features/billing/components/cards/InvoicesSummaryCard.tsx +1 -1
  88. package/src/features/billing/components/cards/PaymentMethodSummaryCard.tsx +1 -1
  89. package/src/features/billing/components/cards/SubscriptionSummaryCard.tsx +1 -1
  90. package/src/features/billing/components/containers/BillingDashboardContainer.tsx +1 -1
  91. package/src/features/billing/stripe-customer/components/details/PaymentMethodCard.tsx +1 -1
  92. package/src/features/billing/stripe-invoice/components/details/InvoiceDetails.tsx +6 -6
  93. package/src/features/billing/stripe-invoice/components/lists/InvoicesList.tsx +2 -2
  94. package/src/features/billing/stripe-price/hooks/useStripePriceTableStructure.tsx +3 -3
  95. package/src/features/billing/stripe-product/hooks/useStripeProductTableStructure.tsx +2 -2
  96. package/src/features/billing/stripe-subscription/components/lists/SubscriptionsList.tsx +4 -4
  97. package/src/features/billing/stripe-subscription/components/widgets/PricingCard.tsx +2 -2
  98. package/src/features/billing/stripe-subscription/components/widgets/SubscriptionConfirmation.tsx +1 -1
  99. package/src/features/billing/stripe-subscription/components/wizards/WizardStepReview.tsx +2 -2
  100. package/src/features/billing/stripe-usage/components/lists/UsageHistoryTable.tsx +2 -2
  101. package/src/features/company/components/forms/CompanyDeleter.tsx +4 -4
  102. package/src/features/content/hooks/useContentTableStructure.tsx +1 -1
  103. package/src/features/feature/components/forms/FormFeatures.tsx +2 -2
  104. package/src/features/help/components/HelpAssistantSheet.tsx +3 -2
  105. package/src/features/help/components/HelpHint.tsx +2 -2
  106. package/src/features/help/components/HelpTOC.tsx +1 -1
  107. package/src/features/how-to/components/containers/HowToCommand.tsx +2 -2
  108. package/src/features/how-to/components/containers/HowToCommandViewer.tsx +5 -5
  109. package/src/features/how-to/components/forms/HowToEditor.tsx +3 -3
  110. package/src/features/how-to/components/forms/HowToSelector.tsx +5 -5
  111. package/src/features/notification/components/lists/NotificationsList.tsx +2 -2
  112. package/src/features/notification/components/modals/NotificationModal.tsx +1 -1
  113. package/src/features/oauth/components/OAuthClientCard.tsx +2 -2
  114. package/src/features/oauth/components/OAuthClientDetail.tsx +2 -2
  115. package/src/features/oauth/components/OAuthClientList.tsx +3 -3
  116. package/src/features/oauth/components/OAuthRedirectUriInput.tsx +1 -1
  117. package/src/features/oauth/components/OAuthScopeSelector.tsx +2 -2
  118. package/src/features/onboarding/components/OnboardingCard.tsx +1 -7
  119. package/src/features/rbac/components/RbacByRoleContainer.tsx +3 -3
  120. package/src/features/rbac/components/RbacContainer.tsx +3 -3
  121. package/src/features/referral/components/ReferralWidget.tsx +1 -0
  122. package/src/features/role/components/forms/FormRoles.tsx +1 -1
  123. package/src/features/role/components/forms/RemoveUserFromRole.tsx +1 -1
  124. package/src/features/role/components/forms/UserRoleAdd.tsx +1 -1
  125. package/src/features/tokenusage/components/TokenUsageBreakdownTable.tsx +9 -9
  126. package/src/features/tokenusage/components/TokenUsageRankedBar.tsx +8 -3
  127. package/src/features/tokenusage/components/TokenUsageTimelineChart.tsx +5 -1
  128. package/src/features/user/components/details/UserStandaloneDetails.tsx +1 -1
  129. package/src/features/user/components/forms/UserReactivator.tsx +2 -2
  130. package/src/features/user/components/forms/UserResentInvitationEmail.tsx +1 -1
  131. package/src/features/user/components/forms/UserSelector.tsx +7 -7
  132. package/src/features/user/components/lists/ContributorsList.tsx +1 -1
  133. package/src/features/user/components/lists/UserListInAdd.tsx +1 -1
  134. package/src/features/user/components/widgets/UserSearchPopover.tsx +5 -5
  135. package/src/features/user/hooks/useUserTableStructure.tsx +1 -1
  136. package/src/features/waitlist/components/forms/WaitlistQuestionnaireRenderer.tsx +1 -1
  137. package/src/features/waitlist/hooks/useWaitlistTableStructure.tsx +1 -1
  138. package/src/hooks/useCustomD3Graph.tsx +16 -0
  139. package/src/lib/__tests__/direction.test.ts +18 -0
  140. package/src/lib/direction.ts +8 -0
  141. package/src/shadcnui/custom/multi-select.tsx +6 -6
  142. package/src/shadcnui/custom/multiple-selector.tsx +6 -2
  143. package/src/shadcnui/ui/accordion.tsx +1 -1
  144. package/src/shadcnui/ui/alert-dialog.tsx +2 -1
  145. package/src/shadcnui/ui/alert.tsx +2 -2
  146. package/src/shadcnui/ui/avatar.tsx +1 -1
  147. package/src/shadcnui/ui/badge.tsx +1 -1
  148. package/src/shadcnui/ui/breadcrumb.tsx +1 -1
  149. package/src/shadcnui/ui/button.tsx +4 -4
  150. package/src/shadcnui/ui/calendar.tsx +9 -8
  151. package/src/shadcnui/ui/carousel.tsx +18 -11
  152. package/src/shadcnui/ui/combobox.tsx +3 -3
  153. package/src/shadcnui/ui/command.tsx +2 -2
  154. package/src/shadcnui/ui/context-menu.tsx +15 -11
  155. package/src/shadcnui/ui/dialog.tsx +2 -1
  156. package/src/shadcnui/ui/drawer.tsx +19 -3
  157. package/src/shadcnui/ui/dropdown-menu.tsx +12 -10
  158. package/src/shadcnui/ui/field.tsx +2 -2
  159. package/src/shadcnui/ui/input-group.tsx +3 -3
  160. package/src/shadcnui/ui/input-otp.tsx +2 -0
  161. package/src/shadcnui/ui/navigation-menu.tsx +2 -2
  162. package/src/shadcnui/ui/progress.tsx +1 -1
  163. package/src/shadcnui/ui/radio-group.tsx +1 -0
  164. package/src/shadcnui/ui/resizable.tsx +1 -0
  165. package/src/shadcnui/ui/scroll-area.tsx +1 -1
  166. package/src/shadcnui/ui/select.tsx +2 -2
  167. package/src/shadcnui/ui/sheet.tsx +11 -4
  168. package/src/shadcnui/ui/sidebar.tsx +37 -20
  169. package/src/shadcnui/ui/sonner.tsx +6 -1
  170. package/src/shadcnui/ui/switch.tsx +1 -1
  171. package/src/shadcnui/ui/table.tsx +2 -2
  172. package/src/shadcnui/ui/tabs.tsx +1 -1
  173. package/dist/chunk-5WFYQE22.js.map +0 -1
  174. package/dist/chunk-CFZW6GU4.mjs.map +0 -1
  175. /package/dist/{BlockNoteEditor-BYMRWUVG.mjs.map → BlockNoteEditor-3D5D7YL7.mjs.map} +0 -0
@@ -6,6 +6,7 @@ import useEmblaCarousel, { type UseEmblaCarouselType } from "embla-carousel-reac
6
6
  import { cn } from "@/lib/utils";
7
7
  import { Button } from "@/components/ui/button";
8
8
  import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
9
+ import { useDir } from "../../contexts/DirectionContext";
9
10
 
10
11
  type CarouselApi = UseEmblaCarouselType[1];
11
12
  type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
@@ -49,10 +50,12 @@ function Carousel({
49
50
  children,
50
51
  ...props
51
52
  }: React.ComponentProps<"div"> & CarouselProps) {
53
+ const dir = useDir();
52
54
  const [carouselRef, api] = useEmblaCarousel(
53
55
  {
54
56
  ...opts,
55
57
  axis: orientation === "horizontal" ? "x" : "y",
58
+ direction: opts?.direction ?? dir,
56
59
  },
57
60
  plugins,
58
61
  );
@@ -75,15 +78,17 @@ function Carousel({
75
78
 
76
79
  const handleKeyDown = React.useCallback(
77
80
  (event: React.KeyboardEvent<HTMLDivElement>) => {
78
- if (event.key === "ArrowLeft") {
81
+ // Arrow keys are physical; in RTL the previous slide sits to the right.
82
+ const toPrev = dir === "rtl" ? "ArrowRight" : "ArrowLeft";
83
+ if (event.key === toPrev) {
79
84
  event.preventDefault();
80
85
  scrollPrev();
81
- } else if (event.key === "ArrowRight") {
86
+ } else if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
82
87
  event.preventDefault();
83
88
  scrollNext();
84
89
  }
85
90
  },
86
- [scrollPrev, scrollNext],
91
+ [scrollPrev, scrollNext, dir],
87
92
  );
88
93
 
89
94
  React.useEffect(() => {
@@ -134,7 +139,7 @@ function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
134
139
 
135
140
  return (
136
141
  <div ref={carouselRef} className="overflow-hidden" data-slot="carousel-content">
137
- <div className={cn("flex", orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col", className)} {...props} />
142
+ <div className={cn("flex", orientation === "horizontal" ? "-ms-4" : "-mt-4 flex-col", className)} {...props} />
138
143
  </div>
139
144
  );
140
145
  }
@@ -147,7 +152,7 @@ function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
147
152
  role="group"
148
153
  aria-roledescription="slide"
149
154
  data-slot="carousel-item"
150
- className={cn("min-w-0 shrink-0 grow-0 basis-full", orientation === "horizontal" ? "pl-4" : "pt-4", className)}
155
+ className={cn("min-w-0 shrink-0 grow-0 basis-full", orientation === "horizontal" ? "ps-4" : "pt-4", className)}
151
156
  {...props}
152
157
  />
153
158
  );
@@ -169,15 +174,16 @@ function CarouselPrevious({
169
174
  className={cn(
170
175
  "rounded-full absolute touch-manipulation",
171
176
  orientation === "horizontal"
172
- ? "top-1/2 -left-12 -translate-y-1/2"
173
- : "-top-12 left-1/2 -translate-x-1/2 rotate-90",
177
+ ? "top-1/2 -start-12 -translate-y-1/2"
178
+ : // rtl-ok: symmetric centering (vertical carousel)
179
+ "-top-12 left-1/2 -translate-x-1/2 rotate-90",
174
180
  className,
175
181
  )}
176
182
  disabled={!canScrollPrev}
177
183
  onClick={scrollPrev}
178
184
  {...props}
179
185
  >
180
- <ChevronLeftIcon />
186
+ <ChevronLeftIcon className={orientation === "horizontal" ? "rtl:rotate-180" : undefined} />
181
187
  <span className="sr-only">Previous slide</span>
182
188
  </Button>
183
189
  );
@@ -199,15 +205,16 @@ function CarouselNext({
199
205
  className={cn(
200
206
  "rounded-full absolute touch-manipulation",
201
207
  orientation === "horizontal"
202
- ? "top-1/2 -right-12 -translate-y-1/2"
203
- : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
208
+ ? "top-1/2 -end-12 -translate-y-1/2"
209
+ : // rtl-ok: symmetric centering (vertical carousel)
210
+ "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
204
211
  className,
205
212
  )}
206
213
  disabled={!canScrollNext}
207
214
  onClick={scrollNext}
208
215
  {...props}
209
216
  >
210
- <ChevronRightIcon />
217
+ <ChevronRightIcon className={orientation === "horizontal" ? "rtl:rotate-180" : undefined} />
211
218
  <span className="sr-only">Next slide</span>
212
219
  </Button>
213
220
  );
@@ -131,7 +131,7 @@ function ComboboxItem({ className, children, ...props }: ComboboxPrimitive.Item.
131
131
  >
132
132
  {children}
133
133
  <ComboboxPrimitive.ItemIndicator
134
- render={<span className="pointer-events-none absolute right-2 flex items-center justify-center" />}
134
+ render={<span className="pointer-events-none absolute end-2 flex items-center justify-center" />}
135
135
  >
136
136
  <CheckIcon className="pointer-events-none" />
137
137
  </ComboboxPrimitive.ItemIndicator>
@@ -208,7 +208,7 @@ function ComboboxChip({
208
208
  <ComboboxPrimitive.Chip
209
209
  data-slot="combobox-chip"
210
210
  className={cn(
211
- "bg-muted-foreground/10 text-foreground flex h-[calc(--spacing(4.75))] w-fit items-center justify-center gap-1 rounded-[calc(var(--radius-sm)-2px)] px-1.5 text-xs/relaxed font-medium whitespace-nowrap has-data-[slot=combobox-chip-remove]:pr-0 has-disabled:pointer-events-none has-disabled:cursor-not-allowed has-disabled:opacity-50",
211
+ "bg-muted-foreground/10 text-foreground flex h-[calc(--spacing(4.75))] w-fit items-center justify-center gap-1 rounded-[calc(var(--radius-sm)-2px)] px-1.5 text-xs/relaxed font-medium whitespace-nowrap has-data-[slot=combobox-chip-remove]:pe-0 has-disabled:pointer-events-none has-disabled:cursor-not-allowed has-disabled:opacity-50",
212
212
  className,
213
213
  )}
214
214
  {...props}
@@ -217,7 +217,7 @@ function ComboboxChip({
217
217
  {showRemove && (
218
218
  <ComboboxPrimitive.ChipRemove
219
219
  render={<Button variant="ghost" size="icon-xs" />}
220
- className="-ml-1 opacity-50 hover:opacity-100"
220
+ className="-ms-1 opacity-50 hover:opacity-100"
221
221
  data-slot="combobox-chip-remove"
222
222
  >
223
223
  <XIcon className="pointer-events-none" />
@@ -137,7 +137,7 @@ function CommandItem({ className, children, ...props }: React.ComponentProps<typ
137
137
  {...props}
138
138
  >
139
139
  {children}
140
- <CheckIcon className="ml-auto opacity-0 group-has-[[data-slot=command-shortcut]]/command-item:hidden group-data-[checked=true]/command-item:opacity-100" />
140
+ <CheckIcon className="ms-auto opacity-0 group-has-[[data-slot=command-shortcut]]/command-item:hidden group-data-[checked=true]/command-item:opacity-100" />
141
141
  </CommandPrimitive.Item>
142
142
  );
143
143
  }
@@ -147,7 +147,7 @@ function CommandShortcut({ className, ...props }: React.ComponentProps<"span">)
147
147
  <span
148
148
  data-slot="command-shortcut"
149
149
  className={cn(
150
- "text-muted-foreground group-data-[selected=true]/command-item:text-foreground ml-auto text-xs tracking-widest",
150
+ "text-muted-foreground group-data-[selected=true]/command-item:text-foreground ms-auto text-xs tracking-widest",
151
151
  className,
152
152
  )}
153
153
  {...props}
@@ -28,7 +28,9 @@ function ContextMenuContent({
28
28
  className,
29
29
  align = "start",
30
30
  alignOffset = 4,
31
- side = "right",
31
+ // Base UI 1.6.0 `Side` accepts logical values; "inline-end" resolves to the
32
+ // physical right in LTR (unchanged) and to the left in RTL.
33
+ side = "inline-end",
32
34
  sideOffset = 0,
33
35
  anchor,
34
36
  ...props
@@ -72,7 +74,7 @@ function ContextMenuLabel({
72
74
  <ContextMenuPrimitive.GroupLabel
73
75
  data-slot="context-menu-label"
74
76
  data-inset={inset}
75
- className={cn("text-muted-foreground px-2 py-1.5 text-xs data-[inset]:pl-8", className)}
77
+ className={cn("text-muted-foreground px-2 py-1.5 text-xs data-[inset]:ps-8", className)}
76
78
  {...props}
77
79
  />
78
80
  );
@@ -93,7 +95,7 @@ function ContextMenuItem({
93
95
  data-inset={inset}
94
96
  data-variant={variant}
95
97
  className={cn(
96
- "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive not-data-[variant=destructive]:focus:**:text-accent-foreground min-h-7 gap-2 rounded-md px-2 py-1 text-xs/relaxed [&_svg:not([class*='size-'])]:size-3.5 group/context-menu-item relative flex cursor-pointer items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0",
98
+ "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive not-data-[variant=destructive]:focus:**:text-accent-foreground min-h-7 gap-2 rounded-md px-2 py-1 text-xs/relaxed [&_svg:not([class*='size-'])]:size-3.5 group/context-menu-item relative flex cursor-pointer items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-[inset]:ps-8 [&_svg]:pointer-events-none [&_svg]:shrink-0",
97
99
  className,
98
100
  )}
99
101
  {...props}
@@ -118,19 +120,21 @@ function ContextMenuSubTrigger({
118
120
  data-slot="context-menu-sub-trigger"
119
121
  data-inset={inset}
120
122
  className={cn(
121
- "focus:bg-accent focus:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground min-h-7 gap-2 rounded-md px-2 py-1 text-xs [&_svg:not([class*='size-'])]:size-3.5 flex cursor-pointer items-center outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0",
123
+ "focus:bg-accent focus:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground min-h-7 gap-2 rounded-md px-2 py-1 text-xs [&_svg:not([class*='size-'])]:size-3.5 flex cursor-pointer items-center outline-hidden select-none data-[inset]:ps-8 [&_svg]:pointer-events-none [&_svg]:shrink-0",
122
124
  className,
123
125
  )}
124
126
  {...props}
125
127
  >
126
128
  {children}
127
- <ChevronRightIcon className="ml-auto" />
129
+ <ChevronRightIcon className="ms-auto rtl:rotate-180" />
128
130
  </ContextMenuPrimitive.SubmenuTrigger>
129
131
  );
130
132
  }
131
133
 
132
134
  function ContextMenuSubContent({ ...props }: React.ComponentProps<typeof ContextMenuContent>) {
133
- return <ContextMenuContent data-slot="context-menu-sub-content" className="shadow-lg" side="right" {...props} />;
135
+ // Base UI 1.6.0 `Side` accepts logical values, so the submenu opens on the inline-end
136
+ // side of its trigger in both directions (identical to "right" under LTR).
137
+ return <ContextMenuContent data-slot="context-menu-sub-content" className="shadow-lg" side="inline-end" {...props} />;
134
138
  }
135
139
 
136
140
  function ContextMenuCheckboxItem({ className, children, checked, ...props }: ContextMenuPrimitive.CheckboxItem.Props) {
@@ -138,13 +142,13 @@ function ContextMenuCheckboxItem({ className, children, checked, ...props }: Con
138
142
  <ContextMenuPrimitive.CheckboxItem
139
143
  data-slot="context-menu-checkbox-item"
140
144
  className={cn(
141
- "focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground min-h-7 gap-2 rounded-md py-1.5 pr-8 pl-2 text-xs [&_svg:not([class*='size-'])]:size-3.5 relative flex cursor-pointer items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
145
+ "focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground min-h-7 gap-2 rounded-md py-1.5 pe-8 ps-2 text-xs [&_svg:not([class*='size-'])]:size-3.5 relative flex cursor-pointer items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
142
146
  className,
143
147
  )}
144
148
  checked={checked}
145
149
  {...props}
146
150
  >
147
- <span className="pointer-events-none absolute right-2 flex items-center justify-center pointer-events-none">
151
+ <span className="pointer-events-none absolute end-2 flex items-center justify-center pointer-events-none">
148
152
  <ContextMenuPrimitive.CheckboxItemIndicator>
149
153
  <CheckIcon />
150
154
  </ContextMenuPrimitive.CheckboxItemIndicator>
@@ -163,12 +167,12 @@ function ContextMenuRadioItem({ className, children, ...props }: ContextMenuPrim
163
167
  <ContextMenuPrimitive.RadioItem
164
168
  data-slot="context-menu-radio-item"
165
169
  className={cn(
166
- "focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground min-h-7 gap-2 rounded-md py-1.5 pr-8 pl-2 text-xs [&_svg:not([class*='size-'])]:size-3.5 relative flex cursor-pointer items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
170
+ "focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground min-h-7 gap-2 rounded-md py-1.5 pe-8 ps-2 text-xs [&_svg:not([class*='size-'])]:size-3.5 relative flex cursor-pointer items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
167
171
  className,
168
172
  )}
169
173
  {...props}
170
174
  >
171
- <span className="pointer-events-none absolute right-2 flex items-center justify-center pointer-events-none">
175
+ <span className="pointer-events-none absolute end-2 flex items-center justify-center pointer-events-none">
172
176
  <ContextMenuPrimitive.RadioItemIndicator>
173
177
  <CheckIcon />
174
178
  </ContextMenuPrimitive.RadioItemIndicator>
@@ -193,7 +197,7 @@ function ContextMenuShortcut({ className, ...props }: React.ComponentProps<"span
193
197
  <span
194
198
  data-slot="context-menu-shortcut"
195
199
  className={cn(
196
- "text-muted-foreground group-focus/context-menu-item:text-accent-foreground ml-auto text-xs tracking-widest",
200
+ "text-muted-foreground group-focus/context-menu-item:text-accent-foreground ms-auto text-xs tracking-widest",
197
201
  className,
198
202
  )}
199
203
  {...props}
@@ -50,6 +50,7 @@ function DialogContent({
50
50
  <DialogPrimitive.Popup
51
51
  data-slot="dialog-content"
52
52
  className={cn(
53
+ // rtl-ok: symmetric centering (`left-1/2` + `-translate-x-1/2`) — identical in LTR and RTL
53
54
  "bg-background data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 ring-foreground/10 grid gap-4 rounded-xl p-4 text-xs/relaxed ring-1 duration-100 fixed top-1/2 left-1/2 z-50 w-full max-w-lg -translate-x-1/2 -translate-y-1/2 outline-none",
54
55
  className,
55
56
  )}
@@ -59,7 +60,7 @@ function DialogContent({
59
60
  {showCloseButton && (
60
61
  <DialogPrimitive.Close
61
62
  data-slot="dialog-close"
62
- render={<Button variant="ghost" className="absolute top-2 right-2" size="icon-sm" />}
63
+ render={<Button variant="ghost" className="absolute top-2 end-2" size="icon-sm" />}
63
64
  >
64
65
  <XIcon />
65
66
  <span className="sr-only">Close</span>
@@ -4,9 +4,24 @@ import * as React from "react";
4
4
  import { Drawer as DrawerPrimitive } from "vaul";
5
5
 
6
6
  import { cn } from "@/lib/utils";
7
+ import { resolvePhysicalSide } from "@/lib/direction";
8
+ import { useDir } from "../../contexts/DirectionContext";
7
9
 
8
- function Drawer({ ...props }: React.ComponentProps<typeof DrawerPrimitive.Root>) {
9
- return <DrawerPrimitive.Root data-slot="drawer" {...props} />;
10
+ // vaul's DialogProps is an intersection with a discriminated union
11
+ // (WithFadeFromProps | WithoutFadeFromProps); a plain Omit collapses it and
12
+ // breaks the snapPoints/fadeFromIndex correlation, so distribute the Omit.
13
+ type DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;
14
+
15
+ function Drawer({
16
+ direction,
17
+ ...props
18
+ }: DistributiveOmit<React.ComponentProps<typeof DrawerPrimitive.Root>, "direction"> & {
19
+ direction?: "top" | "bottom" | "left" | "right" | "start" | "end";
20
+ }) {
21
+ const dir = useDir();
22
+ const resolved = direction === "start" || direction === "end" ? resolvePhysicalSide(direction, dir) : direction;
23
+
24
+ return <DrawerPrimitive.Root data-slot="drawer" direction={resolved} {...props} />;
10
25
  }
11
26
 
12
27
  function DrawerTrigger({ ...props }: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {
@@ -40,6 +55,7 @@ function DrawerContent({ className, children, ...props }: React.ComponentProps<t
40
55
  <DrawerOverlay />
41
56
  <DrawerPrimitive.Content
42
57
  data-slot="drawer-content"
58
+ // rtl-ok: data-[vaul-drawer-direction=…] classes key off the RESOLVED physical side
43
59
  className={cn(
44
60
  "before:bg-background relative flex h-auto flex-col bg-transparent p-2 text-xs/relaxed before:absolute before:inset-2 before:-z-10 before:rounded-xl data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=left]:sm:max-w-sm data-[vaul-drawer-direction=right]:sm:max-w-sm group/drawer-content fixed z-50",
45
61
  className,
@@ -58,7 +74,7 @@ function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) {
58
74
  <div
59
75
  data-slot="drawer-header"
60
76
  className={cn(
61
- "gap-1 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:text-left flex flex-col",
77
+ "gap-1 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:text-start flex flex-col",
62
78
  className,
63
79
  )}
64
80
  {...props}
@@ -63,7 +63,7 @@ function DropdownMenuLabel({
63
63
  <MenuPrimitive.GroupLabel
64
64
  data-slot="dropdown-menu-label"
65
65
  data-inset={inset}
66
- className={cn("text-muted-foreground px-2 py-1.5 text-xs data-[inset]:pl-8", className)}
66
+ className={cn("text-muted-foreground px-2 py-1.5 text-xs data-[inset]:ps-8", className)}
67
67
  {...props}
68
68
  />
69
69
  );
@@ -84,7 +84,7 @@ function DropdownMenuItem({
84
84
  data-inset={inset}
85
85
  data-variant={variant}
86
86
  className={cn(
87
- "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive not-data-[variant=destructive]:focus:**:text-accent-foreground min-h-7 gap-2 rounded-md px-2 py-1 text-xs/relaxed [&_svg:not([class*='size-'])]:size-3.5 group/dropdown-menu-item relative flex cursor-pointer items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0",
87
+ "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive not-data-[variant=destructive]:focus:**:text-accent-foreground min-h-7 gap-2 rounded-md px-2 py-1 text-xs/relaxed [&_svg:not([class*='size-'])]:size-3.5 group/dropdown-menu-item relative flex cursor-pointer items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-[inset]:ps-8 [&_svg]:pointer-events-none [&_svg]:shrink-0",
88
88
  className,
89
89
  )}
90
90
  {...props}
@@ -109,13 +109,13 @@ function DropdownMenuSubTrigger({
109
109
  data-slot="dropdown-menu-sub-trigger"
110
110
  data-inset={inset}
111
111
  className={cn(
112
- "focus:bg-accent focus:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground min-h-7 gap-2 rounded-md px-2 py-1 text-xs [&_svg:not([class*='size-'])]:size-3.5 flex cursor-pointer items-center outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0",
112
+ "focus:bg-accent focus:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground min-h-7 gap-2 rounded-md px-2 py-1 text-xs [&_svg:not([class*='size-'])]:size-3.5 flex cursor-pointer items-center outline-hidden select-none data-[inset]:ps-8 [&_svg]:pointer-events-none [&_svg]:shrink-0",
113
113
  className,
114
114
  )}
115
115
  {...props}
116
116
  >
117
117
  {children}
118
- <ChevronRightIcon className="ml-auto" />
118
+ <ChevronRightIcon className="ms-auto rtl:rotate-180" />
119
119
  </MenuPrimitive.SubmenuTrigger>
120
120
  );
121
121
  }
@@ -123,7 +123,9 @@ function DropdownMenuSubTrigger({
123
123
  function DropdownMenuSubContent({
124
124
  align = "start",
125
125
  alignOffset = -3,
126
- side = "right",
126
+ // Base UI 1.6.0 `Side` accepts logical values: the submenu opens on the inline-end
127
+ // side of its trigger in both directions (identical to "right" under LTR).
128
+ side = "inline-end",
127
129
  sideOffset = 0,
128
130
  className,
129
131
  ...props
@@ -149,14 +151,14 @@ function DropdownMenuCheckboxItem({ className, children, checked, ...props }: Me
149
151
  <MenuPrimitive.CheckboxItem
150
152
  data-slot="dropdown-menu-checkbox-item"
151
153
  className={cn(
152
- "focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground min-h-7 gap-2 rounded-md py-1.5 pr-8 pl-2 text-xs [&_svg:not([class*='size-'])]:size-3.5 relative flex cursor-pointer items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
154
+ "focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground min-h-7 gap-2 rounded-md py-1.5 pe-8 ps-2 text-xs [&_svg:not([class*='size-'])]:size-3.5 relative flex cursor-pointer items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
153
155
  className,
154
156
  )}
155
157
  checked={checked}
156
158
  {...props}
157
159
  >
158
160
  <span
159
- className="pointer-events-none absolute right-2 flex items-center justify-center pointer-events-none"
161
+ className="pointer-events-none absolute end-2 flex items-center justify-center pointer-events-none"
160
162
  data-slot="dropdown-menu-checkbox-item-indicator"
161
163
  >
162
164
  <MenuPrimitive.CheckboxItemIndicator>
@@ -177,13 +179,13 @@ function DropdownMenuRadioItem({ className, children, ...props }: MenuPrimitive.
177
179
  <MenuPrimitive.RadioItem
178
180
  data-slot="dropdown-menu-radio-item"
179
181
  className={cn(
180
- "focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground min-h-7 gap-2 rounded-md py-1.5 pr-8 pl-2 text-xs [&_svg:not([class*='size-'])]:size-3.5 relative flex cursor-pointer items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
182
+ "focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground min-h-7 gap-2 rounded-md py-1.5 pe-8 ps-2 text-xs [&_svg:not([class*='size-'])]:size-3.5 relative flex cursor-pointer items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
181
183
  className,
182
184
  )}
183
185
  {...props}
184
186
  >
185
187
  <span
186
- className="pointer-events-none absolute right-2 flex items-center justify-center pointer-events-none"
188
+ className="pointer-events-none absolute end-2 flex items-center justify-center pointer-events-none"
187
189
  data-slot="dropdown-menu-radio-item-indicator"
188
190
  >
189
191
  <MenuPrimitive.RadioItemIndicator>
@@ -210,7 +212,7 @@ function DropdownMenuShortcut({ className, ...props }: React.ComponentProps<"spa
210
212
  <span
211
213
  data-slot="dropdown-menu-shortcut"
212
214
  className={cn(
213
- "text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground ml-auto text-xs tracking-widest",
215
+ "text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground ms-auto text-xs tracking-widest",
214
216
  className,
215
217
  )}
216
218
  {...props}
@@ -121,7 +121,7 @@ function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
121
121
  <p
122
122
  data-slot="field-description"
123
123
  className={cn(
124
- "text-muted-foreground text-left text-xs/relaxed [[data-variant=legend]+&]:-mt-1.5 leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance",
124
+ "text-muted-foreground text-start text-xs/relaxed [[data-variant=legend]+&]:-mt-1.5 leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance",
125
125
  "last:mt-0 nth-last-2:-mt-1",
126
126
  "[&>a:hover]:text-primary",
127
127
  className,
@@ -182,7 +182,7 @@ function FieldError({
182
182
  }
183
183
 
184
184
  return (
185
- <ul className="ml-4 flex list-disc flex-col gap-1">
185
+ <ul className="ms-4 flex list-disc flex-col gap-1">
186
186
  {uniqueErrors.map((error, index) => error?.message && <li key={index}>{error.message}</li>)}
187
187
  </ul>
188
188
  );
@@ -14,7 +14,7 @@ function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
14
14
  data-slot="input-group"
15
15
  role="group"
16
16
  className={cn(
17
- "border-input bg-input/20 dark:bg-input/30 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring/30 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 h-7 rounded-md border transition-colors has-data-[align=block-end]:rounded-md has-data-[align=block-start]:rounded-md has-[[data-slot=input-group-control]:focus-visible]:ring-[2px] has-[[data-slot][aria-invalid=true]]:ring-[2px] has-[textarea]:rounded-md has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5 [[data-slot=combobox-content]_&]:focus-within:border-inherit [[data-slot=combobox-content]_&]:focus-within:ring-0 group/input-group relative flex w-full min-w-0 items-center outline-none has-[>textarea]:h-auto",
17
+ "border-input bg-input/20 dark:bg-input/30 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring/30 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 h-7 rounded-md border transition-colors has-data-[align=block-end]:rounded-md has-data-[align=block-start]:rounded-md has-[[data-slot=input-group-control]:focus-visible]:ring-[2px] has-[[data-slot][aria-invalid=true]]:ring-[2px] has-[textarea]:rounded-md has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pe-1.5 has-[>[data-align=inline-start]]:[&>input]:ps-1.5 [[data-slot=combobox-content]_&]:focus-within:border-inherit [[data-slot=combobox-content]_&]:focus-within:ring-0 group/input-group relative flex w-full min-w-0 items-center outline-none has-[>textarea]:h-auto",
18
18
  className,
19
19
  )}
20
20
  {...props}
@@ -27,8 +27,8 @@ const inputGroupAddonVariants = cva(
27
27
  {
28
28
  variants: {
29
29
  align: {
30
- "inline-start": "pl-2 has-[>button]:ml-[-0.275rem] has-[>kbd]:ml-[-0.275rem] order-first",
31
- "inline-end": "pr-2 has-[>button]:mr-[-0.275rem] has-[>kbd]:mr-[-0.275rem] order-last",
30
+ "inline-start": "ps-2 has-[>button]:ms-[-0.275rem] has-[>kbd]:ms-[-0.275rem] order-first",
31
+ "inline-end": "pe-2 has-[>button]:me-[-0.275rem] has-[>kbd]:me-[-0.275rem] order-last",
32
32
  "block-start":
33
33
  "px-2 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2 order-first w-full justify-start",
34
34
  "block-end": "px-2 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2 order-last w-full justify-start",
@@ -28,6 +28,7 @@ function InputOTPGroup({ className, ...props }: React.ComponentProps<"div">) {
28
28
  return (
29
29
  <div
30
30
  data-slot="input-otp-group"
31
+ dir="ltr" /* rtl-ok: deliberate LTR island (otp) — one-time codes read LTR in RTL locales */
31
32
  className={cn(
32
33
  "has-aria-invalid:ring-destructive/20 dark:has-aria-invalid:ring-destructive/40 has-aria-invalid:border-destructive rounded-md has-aria-invalid:ring-[2px] flex items-center",
33
34
  className,
@@ -52,6 +53,7 @@ function InputOTPSlot({
52
53
  data-slot="input-otp-slot"
53
54
  data-active={isActive}
54
55
  className={cn(
56
+ // rtl-ok: LTR island — the group is dir="ltr", so first/last rounding and borders stay physical
55
57
  "bg-input/20 dark:bg-input/30 border-input data-[active=true]:border-ring data-[active=true]:ring-ring/30 data-[active=true]:aria-invalid:ring-destructive/20 dark:data-[active=true]:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[active=true]:aria-invalid:border-destructive size-7 border-y border-r text-xs/relaxed transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md data-[active=true]:ring-[2px] relative flex items-center justify-center data-[active=true]:z-10",
56
58
  className,
57
59
  )}
@@ -62,7 +62,7 @@ function NavigationMenuTrigger({
62
62
  <>
63
63
  {" "}
64
64
  <ChevronDownIcon
65
- className="relative top-[1px] ml-1 size-3 transition duration-300 group-data-open/navigation-menu-trigger:rotate-180 group-data-popup-open/navigation-menu-trigger:rotate-180"
65
+ className="relative top-[1px] ms-1 size-3 transition duration-300 group-data-open/navigation-menu-trigger:rotate-180 group-data-popup-open/navigation-menu-trigger:rotate-180"
66
66
  aria-hidden="true"
67
67
  />
68
68
  </>
@@ -139,7 +139,7 @@ function NavigationMenuIndicator({
139
139
  )}
140
140
  {...props}
141
141
  >
142
- <div className="bg-border rounded-tl-sm shadow-md relative top-[60%] h-2 w-2 rotate-45" />
142
+ <div className="bg-border rounded-ss-sm shadow-md relative top-[60%] h-2 w-2 rotate-45" />
143
143
  </NavigationMenuPrimitive.Icon>
144
144
  );
145
145
  }
@@ -48,7 +48,7 @@ function ProgressLabel({ className, ...props }: ProgressPrimitive.Label.Props) {
48
48
  function ProgressValue({ className, ...props }: ProgressPrimitive.Value.Props) {
49
49
  return (
50
50
  <ProgressPrimitive.Value
51
- className={cn("text-muted-foreground ml-auto text-xs/relaxed tabular-nums", className)}
51
+ className={cn("text-muted-foreground ms-auto text-xs/relaxed tabular-nums", className)}
52
52
  data-slot="progress-value"
53
53
  {...props}
54
54
  />
@@ -24,6 +24,7 @@ function RadioGroupItem({ className, ...props }: RadioPrimitive.Root.Props) {
24
24
  data-slot="radio-group-indicator"
25
25
  className="group-aria-invalid/radio-group-item:text-destructive flex size-4 items-center justify-center text-white"
26
26
  >
27
+ {/* rtl-ok: symmetric centering (`left-1/2` + `-translate-x-1/2`) — identical in LTR and RTL */}
27
28
  <CircleIcon className="absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2 fill-current" />
28
29
  </RadioPrimitive.Indicator>
29
30
  </RadioPrimitive.Root>
@@ -44,6 +44,7 @@ function ResizableHandle({
44
44
  "aria-[orientation=vertical]:h-full aria-[orientation=vertical]:w-px",
45
45
  "aria-[orientation=horizontal]:h-px aria-[orientation=horizontal]:w-full",
46
46
  // Hit area for dragging (wider than visible line)
47
+ // rtl-ok: symmetric centering — react-resizable-panels is order-based, the handle sits on the axis midpoint
47
48
  "after:absolute after:inset-y-0 after:left-1/2 after:w-3 after:-translate-x-1/2",
48
49
  "aria-[orientation=vertical]:after:inset-y-0 aria-[orientation=vertical]:after:w-3",
49
50
  "aria-[orientation=horizontal]:after:inset-x-0 aria-[orientation=horizontal]:after:h-3 aria-[orientation=horizontal]:after:inset-y-auto",
@@ -27,7 +27,7 @@ function ScrollBar({ className, orientation = "vertical", ...props }: ScrollArea
27
27
  data-orientation={orientation}
28
28
  orientation={orientation}
29
29
  className={cn(
30
- "data-horizontal:h-2.5 data-horizontal:flex-col data-horizontal:border-t data-horizontal:border-t-transparent data-vertical:h-full data-vertical:w-2.5 data-vertical:border-l data-vertical:border-l-transparent flex touch-none p-px transition-colors select-none",
30
+ "data-horizontal:h-2.5 data-horizontal:flex-col data-horizontal:border-t data-horizontal:border-t-transparent data-vertical:h-full data-vertical:w-2.5 data-vertical:border-s data-vertical:border-s-transparent flex touch-none p-px transition-colors select-none",
31
31
  className,
32
32
  )}
33
33
  {...props}
@@ -14,7 +14,7 @@ function SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {
14
14
 
15
15
  function SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {
16
16
  return (
17
- <SelectPrimitive.Value data-slot="select-value" className={cn("flex flex-1 text-left", className)} {...props} />
17
+ <SelectPrimitive.Value data-slot="select-value" className={cn("flex flex-1 text-start", className)} {...props} />
18
18
  );
19
19
  }
20
20
 
@@ -106,7 +106,7 @@ function SelectItem({ className, children, ...props }: SelectPrimitive.Item.Prop
106
106
  {children}
107
107
  </SelectPrimitive.ItemText>
108
108
  <SelectPrimitive.ItemIndicator
109
- render={<span className="pointer-events-none absolute right-2 flex items-center justify-center" />}
109
+ render={<span className="pointer-events-none absolute end-2 flex items-center justify-center" />}
110
110
  >
111
111
  <CheckIcon className="pointer-events-none" />
112
112
  </SelectPrimitive.ItemIndicator>
@@ -4,8 +4,10 @@ import * as React from "react";
4
4
  import { Dialog as SheetPrimitive } from "@base-ui/react/dialog";
5
5
 
6
6
  import { cn } from "@/lib/utils";
7
+ import { resolvePhysicalSide, type LogicalSide } from "@/lib/direction";
7
8
  import { Button } from "@/components/ui/button";
8
9
  import { XIcon } from "lucide-react";
10
+ import { useDir } from "../../contexts/DirectionContext";
9
11
 
10
12
  function Sheet({ ...props }: SheetPrimitive.Root.Props) {
11
13
  return <SheetPrimitive.Root data-slot="sheet" {...props} />;
@@ -39,13 +41,17 @@ function SheetOverlay({ className, ...props }: SheetPrimitive.Backdrop.Props) {
39
41
  function SheetContent({
40
42
  className,
41
43
  children,
42
- side = "right",
44
+ // Logical default: identical to "right" in LTR, mirrors to the left in RTL.
45
+ side = "end",
43
46
  showCloseButton = true,
44
47
  ...props
45
48
  }: SheetPrimitive.Popup.Props & {
46
- side?: "top" | "right" | "bottom" | "left";
49
+ side?: LogicalSide;
47
50
  showCloseButton?: boolean;
48
51
  }) {
52
+ const dir = useDir();
53
+ const physicalSide = resolvePhysicalSide(side, dir);
54
+
49
55
  return (
50
56
  <SheetPortal>
51
57
  {/* forceRender: Base UI skips the backdrop entirely for a NESTED dialog
@@ -57,8 +63,9 @@ function SheetContent({
57
63
  <SheetOverlay forceRender />
58
64
  <SheetPrimitive.Popup
59
65
  data-slot="sheet-content"
60
- data-side={side}
66
+ data-side={physicalSide}
61
67
  className={cn(
68
+ // rtl-ok: keyed to resolved physical side
62
69
  "bg-background data-open:animate-in data-closed:animate-out data-[side=right]:data-closed:slide-out-to-right-10 data-[side=right]:data-open:slide-in-from-right-10 data-[side=left]:data-closed:slide-out-to-left-10 data-[side=left]:data-open:slide-in-from-left-10 data-[side=top]:data-closed:slide-out-to-top-10 data-[side=top]:data-open:slide-in-from-top-10 data-closed:fade-out-0 data-open:fade-in-0 data-[side=bottom]:data-closed:slide-out-to-bottom-10 data-[side=bottom]:data-open:slide-in-from-bottom-10 fixed z-50 flex flex-col bg-clip-padding text-xs/relaxed shadow-lg transition duration-200 ease-in-out data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:border-r data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:rounded-l-lg data-[side=right]:border-l data-[side=left]:rounded-r-lg data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm",
63
70
  className,
64
71
  )}
@@ -68,7 +75,7 @@ function SheetContent({
68
75
  {showCloseButton && (
69
76
  <SheetPrimitive.Close
70
77
  data-slot="sheet-close"
71
- render={<Button variant="ghost" className="absolute top-4 right-4" size="icon-sm" />}
78
+ render={<Button variant="ghost" className="absolute top-4 end-4" size="icon-sm" />}
72
79
  >
73
80
  <XIcon />
74
81
  <span className="sr-only">Close</span>