@carlonicora/nextjs-jsonapi 3.3.9 → 3.4.1

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 +13 -7
  17. package/dist/components/index.d.ts +13 -7
  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 +35 -35
  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
@@ -156,7 +156,7 @@ export default function RbacByRoleContainer() {
156
156
  return (
157
157
  <RoundPageContainer fullWidth forceHeader>
158
158
  <div className="flex h-full w-full">
159
- <aside className="w-60 shrink-0 overflow-y-auto border-r bg-muted/20">
159
+ <aside className="w-60 shrink-0 overflow-y-auto border-e bg-muted/20">
160
160
  <ul className="py-1">
161
161
  {sortedRoleIds.map((id) => (
162
162
  <li key={id}>
@@ -165,7 +165,7 @@ export default function RbacByRoleContainer() {
165
165
  onClick={() => handleSelectRole(id)}
166
166
  aria-current={id === selectedRoleId ? "true" : undefined}
167
167
  className={cn(
168
- "block w-full px-4 py-1.5 text-left text-sm hover:bg-muted",
168
+ "block w-full px-4 py-1.5 text-start text-sm hover:bg-muted",
169
169
  id === selectedRoleId && "bg-muted font-medium text-foreground",
170
170
  id !== selectedRoleId && "text-muted-foreground",
171
171
  )}
@@ -184,7 +184,7 @@ export default function RbacByRoleContainer() {
184
184
  <table className="w-full text-sm">
185
185
  <thead className="sticky top-0 z-10">
186
186
  <tr className="border-b bg-muted/80 backdrop-blur-sm">
187
- <th className="w-40 px-4 py-2 text-left text-xs font-medium text-muted-foreground">
187
+ <th className="w-40 px-4 py-2 text-start text-xs font-medium text-muted-foreground">
188
188
  {t("rbac.module")}
189
189
  </th>
190
190
  {ACTION_TYPES.map((action) => (
@@ -118,7 +118,7 @@ const ModuleEditor = memo(function ModuleEditor({
118
118
  <table className="w-full text-sm">
119
119
  <thead>
120
120
  <tr className="border-b bg-muted/50">
121
- <th className="w-40 px-4 py-2 text-left text-xs font-medium text-muted-foreground">{t("rbac.role")}</th>
121
+ <th className="w-40 px-4 py-2 text-start text-xs font-medium text-muted-foreground">{t("rbac.role")}</th>
122
122
  {ACTION_TYPES.map((action) => (
123
123
  <th key={action} className="min-w-28 px-2 py-2 text-center text-xs font-medium text-muted-foreground">
124
124
  {ACTION_LABELS[action]}
@@ -295,7 +295,7 @@ export default function RbacContainer() {
295
295
  <RoundPageContainer fullWidth forceHeader>
296
296
  <div className="flex h-full w-full">
297
297
  {/* Sidebar: module list */}
298
- <aside className="w-60 shrink-0 overflow-y-auto border-r bg-muted/20">
298
+ <aside className="w-60 shrink-0 overflow-y-auto border-e bg-muted/20">
299
299
  <ul className="py-1">
300
300
  {sortedModuleIds.map((id) => (
301
301
  <li key={id}>
@@ -304,7 +304,7 @@ export default function RbacContainer() {
304
304
  onClick={() => handleSelectModule(id)}
305
305
  aria-current={id === selectedModuleId ? "true" : undefined}
306
306
  className={cn(
307
- "block w-full px-4 py-1.5 text-left text-sm hover:bg-muted",
307
+ "block w-full px-4 py-1.5 text-start text-sm hover:bg-muted",
308
308
  id === selectedModuleId && "bg-muted font-medium text-foreground",
309
309
  id !== selectedModuleId && "text-muted-foreground",
310
310
  )}
@@ -110,6 +110,7 @@ async function copyToClipboard(text: string): Promise<boolean> {
110
110
  const textArea = document.createElement("textarea");
111
111
  textArea.value = text;
112
112
  textArea.style.position = "fixed";
113
+ // rtl-ok: off-screen clipboard fallback, not layout — physical offset is deliberate
113
114
  textArea.style.left = "-999999px";
114
115
  textArea.style.top = "-999999px";
115
116
  document.body.appendChild(textArea);
@@ -47,7 +47,7 @@ export function FormRoles({ form, id, name, roles }: FormRolesProps) {
47
47
  />
48
48
  <Tooltip>
49
49
  <TooltipTrigger>
50
- <FieldLabel htmlFor={`role-${role.id}`} className="ml-3 cursor-pointer font-normal">
50
+ <FieldLabel htmlFor={`role-${role.id}`} className="ms-3 cursor-pointer font-normal">
51
51
  {t(`role.roles`, { role: role.id.replaceAll(`-`, ``) })}
52
52
  </FieldLabel>
53
53
  </TooltipTrigger>
@@ -84,7 +84,7 @@ export function RemoveUserFromRole({ role, user, refresh }: RemoveUserFromRolePr
84
84
  <>
85
85
  {t(`role.remove_user.description_allowed`, { role: roleName, user: user.name })}
86
86
  <div className="flex justify-end">
87
- <Button className="mr-2" variant={"outline"} type={`button`} onClick={() => setOpen(false)}>
87
+ <Button className="me-2" variant={"outline"} type={`button`} onClick={() => setOpen(false)}>
88
88
  {t(`ui.buttons.cancel`)}
89
89
  </Button>
90
90
  <Button
@@ -82,7 +82,7 @@ export function UserRoleAdd({ user, refresh }: UserRoleAddProps) {
82
82
  return (
83
83
  <>
84
84
  <Button size="sm" onClick={() => setOpen(true)}>
85
- <PlusCircle className="mr-3 h-3.5 w-3.5" />
85
+ <PlusCircle className="me-3 h-3.5 w-3.5" />
86
86
  {t(`common.association.label`, {
87
87
  source: t(`entities.roles`, { count: 1 }),
88
88
  destination: t(`entities.users`, { count: 1 }),
@@ -104,7 +104,7 @@ export function TokenUsageBreakdownTable({ rows, metric }: Props) {
104
104
  <TableHeader>
105
105
  <TableRow>
106
106
  {columns.map((column) => (
107
- <TableHead key={column.key} className={cn(column.numeric && "text-right")}>
107
+ <TableHead key={column.key} className={cn(column.numeric && "text-end")}>
108
108
  <button
109
109
  type="button"
110
110
  onClick={() => toggleSort(column.key)}
@@ -137,19 +137,19 @@ export function TokenUsageBreakdownTable({ rows, metric }: Props) {
137
137
  </TableCell>
138
138
  <TableCell className="text-muted-foreground text-xs">{row.sublabel ?? ""}</TableCell>
139
139
  {showActiveUsers && (
140
- <TableCell className="text-right text-xs tabular-nums">
140
+ <TableCell className="text-end text-xs tabular-nums">
141
141
  {row.activeUsers === undefined ? "" : decimal(row.activeUsers, 0)}
142
142
  </TableCell>
143
143
  )}
144
- <TableCell className="text-right text-xs tabular-nums">{decimal(row.calls, 0)}</TableCell>
145
- <TableCell className="text-right text-xs tabular-nums">{decimal(row.tokensIn, 0)}</TableCell>
146
- <TableCell className="text-right text-xs tabular-nums">{decimal(row.tokensOut, 0)}</TableCell>
147
- <TableCell className="text-right text-xs tabular-nums">
144
+ <TableCell className="text-end text-xs tabular-nums">{decimal(row.calls, 0)}</TableCell>
145
+ <TableCell className="text-end text-xs tabular-nums">{decimal(row.tokensIn, 0)}</TableCell>
146
+ <TableCell className="text-end text-xs tabular-nums">{decimal(row.tokensOut, 0)}</TableCell>
147
+ <TableCell className="text-end text-xs tabular-nums">
148
148
  {percent(cacheHitPercentage(row.cached, row.tokensIn))}
149
149
  </TableCell>
150
- <TableCell className="text-right text-xs tabular-nums">{formatValue(row.cost, "cost")}</TableCell>
151
- <TableCell className="text-right text-xs tabular-nums">{formatValue(row.credits, "credits")}</TableCell>
152
- <TableCell className="text-right text-xs tabular-nums">{percent(share)}</TableCell>
150
+ <TableCell className="text-end text-xs tabular-nums">{formatValue(row.cost, "cost")}</TableCell>
151
+ <TableCell className="text-end text-xs tabular-nums">{formatValue(row.credits, "credits")}</TableCell>
152
+ <TableCell className="text-end text-xs tabular-nums">{percent(share)}</TableCell>
153
153
  </TableRow>
154
154
  );
155
155
  })}
@@ -90,21 +90,26 @@ export function TokenUsageRankedBar({ rows, metric, emptyLabel, labelsAreOperati
90
90
  <div key={row.id} className="grid grid-cols-[minmax(0,1fr)_auto_auto] items-center gap-x-3 gap-y-0.5 py-0.5">
91
91
  <div className="grid min-w-0 gap-0.5">
92
92
  <span className="text-muted-foreground truncate text-xs">{renderLabel(row)}</span>
93
- <div className="bg-muted h-2 w-full overflow-hidden rounded-r-[4px]">
93
+ {/* The bar itself is a magnitude drawn on an axis, not prose: it always grows
94
+ rightward and only its trailing corner is rounded, so the track and its fill
95
+ stay an LTR island. The label, value and share around it mirror normally.
96
+ rtl-ok: deliberate LTR island (chart) */}
97
+ <div className="bg-muted h-2 w-full overflow-hidden rounded-r-[4px]" dir="ltr">
94
98
  <div
95
99
  data-testid={`ranked-fill-${row.id}`}
96
100
  data-ramp-step={String(step)}
101
+ /* rtl-ok: inside the LTR island above */
97
102
  className="h-full rounded-r-[4px]"
98
103
  style={{ width: `${width}%`, backgroundColor: SEQUENTIAL_RAMP[step] }}
99
104
  />
100
105
  </div>
101
106
  </div>
102
- <span data-testid={`ranked-value-${row.id}`} className="text-right text-xs tabular-nums">
107
+ <span data-testid={`ranked-value-${row.id}`} className="text-end text-xs tabular-nums">
103
108
  {formatValue(value, metric)}
104
109
  </span>
105
110
  <span
106
111
  data-testid={`ranked-share-${row.id}`}
107
- className="text-muted-foreground w-16 text-right text-xs tabular-nums"
112
+ className="text-muted-foreground w-16 text-end text-xs tabular-nums"
108
113
  >
109
114
  {percent(share)}
110
115
  </span>
@@ -161,7 +161,11 @@ export function TokenUsageTimelineChart({ rows, metric, stackBy, className }: To
161
161
  series === OTHER_SERIES ? OTHER_COLOR : seriesColor(index, mode);
162
162
 
163
163
  return (
164
- <div className={className}>
164
+ // The chart is a coordinate system, not prose: recharts lays the axes and the
165
+ // stacked bars out left-to-right, so the whole chart stays an LTR island even
166
+ // under dir="rtl". Only the labels inside it are translated.
167
+ // rtl-ok: deliberate LTR island (chart)
168
+ <div className={className} dir="ltr">
165
169
  {/* The pivot, exposed for assertions and for screen readers that would
166
170
  otherwise get nothing from the SVG. */}
167
171
  <span className="sr-only" data-testid="timeline-data">
@@ -29,7 +29,7 @@ export function UserStanadaloneDetails({ user }: UserDetailsProps) {
29
29
  <div className="flex flex-wrap gap-2">
30
30
  {user.roles.map((role: RoleInterface, _index: number) => (
31
31
  <Link key={role.id} href={generateUrl({ page: Modules.Role, id: role.id })}>
32
- <Badge className="mr-2" variant={`default`}>
32
+ <Badge className="me-2" variant={`default`}>
33
33
  {t(`role.roles`, { role: role.id.replaceAll(`-`, ``) })}
34
34
  </Badge>
35
35
  </Link>
@@ -43,7 +43,7 @@ function UserReactivatorInterface({ user, propagateChanges }: UserReactivatorPro
43
43
  <Dialog open={open} onOpenChange={setOpen}>
44
44
  <DialogTrigger>
45
45
  <Button size="sm">
46
- <UserCheckIcon className="mr-3 h-3.5 w-3.5" />
46
+ <UserCheckIcon className="me-3 h-3.5 w-3.5" />
47
47
  {t(`user.buttons.reactivate`)}
48
48
  </Button>
49
49
  </DialogTrigger>
@@ -54,7 +54,7 @@ function UserReactivatorInterface({ user, propagateChanges }: UserReactivatorPro
54
54
  </DialogHeader>
55
55
  {t(`user.reactivate.description`, { name: user.name })}
56
56
  <div className="flex justify-end">
57
- <Button className="mr-2" variant={"outline"} type={`button`} onClick={() => setOpen(false)}>
57
+ <Button className="me-2" variant={"outline"} type={`button`} onClick={() => setOpen(false)}>
58
58
  {t(`ui.buttons.cancel`)}
59
59
  </Button>
60
60
  <Button
@@ -63,7 +63,7 @@ function UserResentInvitationEmailInternal({ user }: UserResentInvitationEmailPr
63
63
  </DialogHeader>
64
64
  {t(`user.resend_activation.description`, { email: user.email })}
65
65
  <div className="flex justify-end">
66
- <Button className="mr-2" variant={"outline"} type={`button`} onClick={() => setOpen(false)}>
66
+ <Button className="me-2" variant={"outline"} type={`button`} onClick={() => setOpen(false)}>
67
67
  {t(`ui.buttons.cancel`)}
68
68
  </Button>
69
69
  <Button
@@ -97,7 +97,7 @@ export function UserSelector({ id, form, label, placeholder, onChange, isRequire
97
97
  {field.value ? (
98
98
  <div className="bg-input/20 dark:bg-input/30 border-input flex h-7 w-full flex-row items-center justify-start rounded-md border px-2 py-0.5 text-sm md:text-xs/relaxed">
99
99
  <div className="*:ring-border *:ring-1">
100
- <Avatar className={`mr-2 h-4 w-4`}>
100
+ <Avatar className={`me-2 h-4 w-4`}>
101
101
  <AvatarImage src={field.value?.avatar} />
102
102
  <AvatarFallback>
103
103
  {field.value?.name
@@ -117,7 +117,7 @@ export function UserSelector({ id, form, label, placeholder, onChange, isRequire
117
117
  </PopoverTrigger>
118
118
  {field.value && (
119
119
  <CircleX
120
- className="text-muted hover:text-destructive ml-2 h-4 w-4 shrink-0 cursor-pointer"
120
+ className="text-muted hover:text-destructive ms-2 h-4 w-4 shrink-0 cursor-pointer"
121
121
  onClick={() => setUser()}
122
122
  />
123
123
  )}
@@ -125,19 +125,19 @@ export function UserSelector({ id, form, label, placeholder, onChange, isRequire
125
125
  <PopoverContent align="start" className="w-(--anchor-width)">
126
126
  <Command shouldFilter={false}>
127
127
  <div className="relative mb-2 w-full">
128
- <SearchIcon className="text-muted-foreground absolute top-2.5 left-2.5 h-4 w-4" />
128
+ <SearchIcon className="text-muted-foreground absolute top-2.5 start-2.5 h-4 w-4" />
129
129
  <Input
130
130
  placeholder={t(`ui.search.placeholder`, { type: t(`entities.users`, { count: 1 }) })}
131
131
  type="text"
132
- className="w-full pr-8 pl-8"
132
+ className="w-full pe-8 ps-8"
133
133
  onChange={(e) => setSearchTerm(e.target.value)}
134
134
  value={searchTerm}
135
135
  />
136
136
  {isSearching ? (
137
- <RefreshCwIcon className="text-muted-foreground absolute top-2.5 right-2.5 h-4 w-4 animate-spin" />
137
+ <RefreshCwIcon className="text-muted-foreground absolute top-2.5 end-2.5 h-4 w-4 animate-spin" />
138
138
  ) : searchTermRef.current ? (
139
139
  <XIcon
140
- className={`absolute top-2.5 right-2.5 h-4 w-4 ${searchTermRef.current ? "cursor-pointer" : "text-muted-foreground"}`}
140
+ className={`absolute top-2.5 end-2.5 h-4 w-4 ${searchTermRef.current ? "cursor-pointer" : "text-muted-foreground"}`}
141
141
  onClick={() => {
142
142
  setSearchTerm("");
143
143
  search("");
@@ -156,7 +156,7 @@ export function UserSelector({ id, form, label, placeholder, onChange, isRequire
156
156
  key={user.id}
157
157
  onSelect={() => setUser(user)}
158
158
  >
159
- <UserAvatar user={user} className={`mr-2 h-4 w-4`} />
159
+ <UserAvatar user={user} className={`me-2 h-4 w-4`} />
160
160
  <span className="">{user.name}</span>
161
161
  </CommandItem>
162
162
  ))}
@@ -21,7 +21,7 @@ export function ContributorsList({ content }: ContributorsListProps) {
21
21
  href={generateUrl({ page: Modules.User, id: content.author.id })}
22
22
  onClick={(e: React.MouseEvent<HTMLAnchorElement>) => e.stopPropagation()}
23
23
  >
24
- <UserAvatar user={content.author} className="mr-1 h-6 w-6" />
24
+ <UserAvatar user={content.author} className="me-1 h-6 w-6" />
25
25
  </Link>
26
26
  <div className="flex flex-row-reverse justify-end -space-x-1 space-x-reverse">
27
27
  {content.editors
@@ -39,7 +39,7 @@ export function UserListInAdd({ data, existingUsers, setSelectedUser, setLevelOp
39
39
  >
40
40
  <div className="flex w-full flex-row items-center justify-between px-4 py-1">
41
41
  <UserAvatar user={user} />
42
- <div className="ml-5 flex w-full flex-col">
42
+ <div className="ms-5 flex w-full flex-col">
43
43
  <div className="text-sm font-medium">{user.name}</div>
44
44
  <div className="text-xs font-normal">{user.email}</div>
45
45
  </div>
@@ -45,20 +45,20 @@ export const UserSearchPopover = ({ children, onSelect, align = "start", classNa
45
45
  <PopoverContent align={align} onClick={(e) => e.stopPropagation()} className={className ?? "w-80"}>
46
46
  <Command shouldFilter={false}>
47
47
  <div className="relative mb-2 w-full">
48
- <SearchIcon className="text-muted-foreground absolute top-2.5 left-2.5 h-4 w-4" />
48
+ <SearchIcon className="text-muted-foreground absolute top-2.5 start-2.5 h-4 w-4" />
49
49
  <Input
50
50
  placeholder={t(`ui.search.placeholder`, { type: t(`entities.users`, { count: 1 }) })}
51
51
  type="text"
52
- className="w-full pr-8 pl-8"
52
+ className="w-full pe-8 ps-8"
53
53
  onChange={(e) => setSearchQuery(e.target.value)}
54
54
  value={searchQuery}
55
55
  onClick={(e) => e.stopPropagation()}
56
56
  />
57
57
  {isLoading ? (
58
- <RefreshCwIcon className="text-muted-foreground absolute top-2.5 right-2.5 h-4 w-4 animate-spin" />
58
+ <RefreshCwIcon className="text-muted-foreground absolute top-2.5 end-2.5 h-4 w-4 animate-spin" />
59
59
  ) : searchQuery ? (
60
60
  <XIcon
61
- className="text-muted-foreground hover:text-foreground absolute top-2.5 right-2.5 h-4 w-4 cursor-pointer"
61
+ className="text-muted-foreground hover:text-foreground absolute top-2.5 end-2.5 h-4 w-4 cursor-pointer"
62
62
  onClick={(e) => {
63
63
  e.stopPropagation();
64
64
  clearSearch();
@@ -74,7 +74,7 @@ export const UserSearchPopover = ({ children, onSelect, align = "start", classNa
74
74
  className="cursor-pointer hover:bg-muted data-selected:hover:bg-muted bg-transparent data-selected:bg-transparent"
75
75
  onSelect={() => handleSelectUser(user.id)}
76
76
  >
77
- <UserAvatar user={user} className="mr-2 h-4 w-4" showLink={false} />
77
+ <UserAvatar user={user} className="me-2 h-4 w-4" showLink={false} />
78
78
  <span>{user.name}</span>
79
79
  </CommandItem>
80
80
  ))
@@ -99,7 +99,7 @@ export const useUserTableStructure: UseTableStructureHook<UserInterface, UserFie
99
99
  return (
100
100
  <div className="relative flex h-5 w-20 items-center justify-center overflow-clip rounded border text-center">
101
101
  <div
102
- className={`bg-accent absolute top-0 left-0 h-full opacity-${Math.round(user.relevance)}`}
102
+ className={`bg-accent absolute top-0 start-0 h-full opacity-${Math.round(user.relevance)}`}
103
103
  style={{ width: `${user.relevance}%` }}
104
104
  ></div>
105
105
  <span
@@ -84,7 +84,7 @@ export function WaitlistQuestionnaireRenderer<T extends FieldValues>({
84
84
  <div key={field.id} className="space-y-2">
85
85
  <span className="text-sm font-medium">
86
86
  {field.label}
87
- {field.required && <span className="ml-1 text-destructive">*</span>}
87
+ {field.required && <span className="ms-1 text-destructive">*</span>}
88
88
  </span>
89
89
  {field.description && <p className="text-muted-foreground text-xs">{field.description}</p>}
90
90
  <div className="space-y-2">
@@ -94,7 +94,7 @@ export function useWaitlistTableStructure({
94
94
  if (entry.status === "confirmed") {
95
95
  return (
96
96
  <Button size="sm" variant="outline" onClick={() => onInvite(entry)}>
97
- <Send className="mr-2 h-4 w-4" />
97
+ <Send className="me-2 h-4 w-4" />
98
98
  {t("waitlist.admin.actions.invite")}
99
99
  </Button>
100
100
  );
@@ -30,6 +30,11 @@ function estimateLabelWidth(node: D3Node): number {
30
30
 
31
31
  /**
32
32
  * Custom hook for D3 graph visualization with larger circles and more interactive features
33
+ *
34
+ * Direction: the hook stamps `dir="ltr"` (and `direction: ltr`) on the SVG it
35
+ * owns — the layout is coordinate-based, so it is a deliberate LTR island and
36
+ * never mirrors under `dir="rtl"`. Consumers do not need to set anything; a
37
+ * wrapping container may still mirror around the graph.
33
38
  */
34
39
  export function useCustomD3Graph(
35
40
  nodes: D3Node[],
@@ -199,6 +204,17 @@ export function useCustomD3Graph(
199
204
  );
200
205
 
201
206
  useEffect(() => {
207
+ // The graph lives in SVG user space: node positions, link endpoints and label
208
+ // offsets are all computed coordinates, so an RTL ancestor must not mirror it.
209
+ // Stamped on the element the hook owns rather than left to each caller, so no
210
+ // consumer can forget it. Both the attribute and the inline property are set
211
+ // because `dir` is an HTML attribute and does not reliably reach SVG.
212
+ // rtl-ok: deliberate LTR island (chart)
213
+ if (svgRef.current) {
214
+ svgRef.current.setAttribute("dir", "ltr");
215
+ svgRef.current.style.direction = "ltr";
216
+ }
217
+
202
218
  if (!nodes.length || !svgRef.current) return;
203
219
 
204
220
  const visibleNodes = visibleNodeIds
@@ -0,0 +1,18 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { resolvePhysicalSide } from "../direction";
3
+
4
+ describe("resolvePhysicalSide", () => {
5
+ it("maps start/end by direction", () => {
6
+ expect(resolvePhysicalSide("start", "ltr")).toBe("left");
7
+ expect(resolvePhysicalSide("start", "rtl")).toBe("right");
8
+ expect(resolvePhysicalSide("end", "ltr")).toBe("right");
9
+ expect(resolvePhysicalSide("end", "rtl")).toBe("left");
10
+ });
11
+ it("passes physical sides through unchanged in both directions", () => {
12
+ for (const dir of ["ltr", "rtl"] as const) {
13
+ for (const side of ["top", "bottom", "left", "right"] as const) {
14
+ expect(resolvePhysicalSide(side, dir)).toBe(side);
15
+ }
16
+ }
17
+ });
18
+ });
@@ -0,0 +1,8 @@
1
+ export type PhysicalSide = "top" | "bottom" | "left" | "right";
2
+ export type LogicalSide = PhysicalSide | "start" | "end";
3
+
4
+ export function resolvePhysicalSide(side: LogicalSide, dir: "ltr" | "rtl"): PhysicalSide {
5
+ if (side === "start") return dir === "rtl" ? "right" : "left";
6
+ if (side === "end") return dir === "rtl" ? "left" : "right";
7
+ return side;
8
+ }
@@ -291,11 +291,11 @@ export const MultiSelect = React.forwardRef<HTMLButtonElement, MultiSelectProps>
291
291
  className={cn(isAnimating ? "animate-bounce" : "", multiSelectVariants({ variant }))}
292
292
  style={{ animationDuration: `${animation}s` }}
293
293
  >
294
- {IconComponent && <IconComponent className="mr-2 h-4 w-4" />}
294
+ {IconComponent && <IconComponent className="me-2 h-4 w-4" />}
295
295
  {option?.selectedLabel ?? option?.label}
296
296
  <XCircle
297
297
  data-remove-button
298
- className="ml-2 h-4 w-4 cursor-pointer"
298
+ className="ms-2 h-4 w-4 cursor-pointer"
299
299
  onPointerDown={(event) => {
300
300
  event.stopPropagation();
301
301
  event.preventDefault();
@@ -321,7 +321,7 @@ export const MultiSelect = React.forwardRef<HTMLButtonElement, MultiSelectProps>
321
321
  {`+ ${selectedValues.length - maxCount} more`}
322
322
  <XCircle
323
323
  data-remove-button
324
- className="ml-2 h-4 w-4 cursor-pointer"
324
+ className="ms-2 h-4 w-4 cursor-pointer"
325
325
  onPointerDown={(event) => {
326
326
  event.stopPropagation();
327
327
  event.preventDefault();
@@ -388,7 +388,7 @@ export const MultiSelect = React.forwardRef<HTMLButtonElement, MultiSelectProps>
388
388
  >
389
389
  <div
390
390
  className={cn(
391
- "border-primary mr-2 flex h-4 w-4 items-center justify-center rounded-sm border",
391
+ "border-primary me-2 flex h-4 w-4 items-center justify-center rounded-sm border",
392
392
  selectedValues.length === options.length
393
393
  ? "bg-primary text-primary-foreground"
394
394
  : "opacity-50 [&_svg]:invisible",
@@ -411,14 +411,14 @@ export const MultiSelect = React.forwardRef<HTMLButtonElement, MultiSelectProps>
411
411
  >
412
412
  <div
413
413
  className={cn(
414
- "border-primary mr-2 flex h-4 w-4 items-center justify-center rounded-sm border",
414
+ "border-primary me-2 flex h-4 w-4 items-center justify-center rounded-sm border",
415
415
  isSelected ? "bg-primary text-primary-foreground" : "opacity-50 [&_svg]:invisible",
416
416
  )}
417
417
  >
418
418
  <CheckIcon className="h-4 w-4" />
419
419
  </div>
420
420
  {option.icon && (
421
- <option.icon className={option.iconClassName ?? "text-muted-foreground mr-2 h-4 w-4"} />
421
+ <option.icon className={option.iconClassName ?? "text-muted-foreground me-2 h-4 w-4"} />
422
422
  )}
423
423
  <span>{option.label}</span>
424
424
  </CommandItem>
@@ -213,6 +213,7 @@ const MultipleSelector = React.forwardRef<MultipleSelectorRef, MultipleSelectorP
213
213
  const [onScrollbar, setOnScrollbar] = React.useState(false);
214
214
  const [isLoading, setIsLoading] = React.useState(false);
215
215
  const dropdownRef = React.useRef<HTMLDivElement>(null);
216
+ // rtl-ok: viewport-anchored floating position (physical viewport coordinates, not a layout inset)
216
217
  const [dropdownPosition, setDropdownPosition] = React.useState({ top: 0, left: 0, width: 0 });
217
218
 
218
219
  const [selected, setSelected] = React.useState<Option[]>(value || []);
@@ -331,6 +332,8 @@ const MultipleSelector = React.forwardRef<MultipleSelectorRef, MultipleSelectorP
331
332
  const rect = containerRef.current.getBoundingClientRect();
332
333
  setDropdownPosition({
333
334
  top: rect.bottom + window.scrollY + 4,
335
+ // rtl-ok: viewport-anchored floating position — the portal is placed at the
336
+ // container's measured rect and spans its full width, so it matches in RTL too
334
337
  left: rect.left + window.scrollX,
335
338
  width: rect.width,
336
339
  });
@@ -502,7 +505,7 @@ const MultipleSelector = React.forwardRef<MultipleSelectorRef, MultipleSelectorP
502
505
  <button
503
506
  type="button"
504
507
  className={cn(
505
- "ml-1 rounded-full outline-none ring-offset-background focus:ring-2 focus:ring-ring focus:ring-offset-2",
508
+ "ms-1 rounded-full outline-none ring-offset-background focus:ring-2 focus:ring-ring focus:ring-offset-2",
506
509
  (disabled || option.fixed) && "hidden",
507
510
  )}
508
511
  onKeyDown={(e) => {
@@ -550,7 +553,7 @@ const MultipleSelector = React.forwardRef<MultipleSelectorRef, MultipleSelectorP
550
553
  "flex-1 self-baseline bg-transparent outline-none placeholder:text-muted-foreground text-foreground caret-foreground",
551
554
  {
552
555
  "w-full": hidePlaceholderWhenSelected,
553
- "ml-1": selected.length !== 0,
556
+ "ms-1": selected.length !== 0,
554
557
  },
555
558
  inputProps?.className,
556
559
  )}
@@ -653,6 +656,7 @@ const MultipleSelector = React.forwardRef<MultipleSelectorRef, MultipleSelectorP
653
656
  className="fixed z-50 max-h-80 rounded-md border bg-background text-foreground shadow-md outline-none animate-in"
654
657
  style={{
655
658
  top: dropdownPosition.top,
659
+ // rtl-ok: viewport-anchored floating position (measured px, not a logical inset)
656
660
  left: dropdownPosition.left,
657
661
  width: dropdownPosition.width,
658
662
  }}
@@ -31,7 +31,7 @@ function AccordionTrigger({ className, children, ...props }: AccordionPrimitive.
31
31
  <AccordionPrimitive.Trigger
32
32
  data-slot="accordion-trigger"
33
33
  className={cn(
34
- "**:data-[slot=accordion-trigger-icon]:text-muted-foreground gap-6 p-2 text-left text-xs/relaxed font-medium hover:underline **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 group/accordion-trigger relative flex flex-1 items-start justify-between border border-transparent transition-all outline-none disabled:pointer-events-none disabled:opacity-50",
34
+ "**:data-[slot=accordion-trigger-icon]:text-muted-foreground gap-6 p-2 text-start text-xs/relaxed font-medium hover:underline **:data-[slot=accordion-trigger-icon]:ms-auto **:data-[slot=accordion-trigger-icon]:size-4 group/accordion-trigger relative flex flex-1 items-start justify-between border border-transparent transition-all outline-none disabled:pointer-events-none disabled:opacity-50",
35
35
  className,
36
36
  )}
37
37
  {...props}
@@ -45,6 +45,7 @@ function AlertDialogContent({
45
45
  data-slot="alert-dialog-content"
46
46
  data-size={size}
47
47
  className={cn(
48
+ // rtl-ok: symmetric centering (`left-1/2` + `-translate-x-1/2`) — identical in LTR and RTL
48
49
  "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 bg-background ring-foreground/10 gap-3 rounded-xl p-4 ring-1 duration-100 data-[size=default]:max-w-xs data-[size=sm]:max-w-64 data-[size=default]:sm:max-w-sm group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 outline-none",
49
50
  className,
50
51
  )}
@@ -59,7 +60,7 @@ function AlertDialogHeader({ className, ...props }: React.ComponentProps<"div">)
59
60
  <div
60
61
  data-slot="alert-dialog-header"
61
62
  className={cn(
62
- "grid grid-rows-[auto_1fr] place-items-center gap-1 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]",
63
+ "grid grid-rows-[auto_1fr] place-items-center gap-1 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-start sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]",
63
64
  className,
64
65
  )}
65
66
  {...props}
@@ -4,7 +4,7 @@ import { cva, type VariantProps } from "class-variance-authority";
4
4
  import { cn } from "@/lib/utils";
5
5
 
6
6
  const alertVariants = cva(
7
- "grid gap-0.5 rounded-lg border px-2 py-1.5 text-left text-xs/relaxed has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-1.5 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-3.5 w-full relative group/alert",
7
+ "grid gap-0.5 rounded-lg border px-2 py-1.5 text-start text-xs/relaxed has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pe-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-1.5 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-3.5 w-full relative group/alert",
8
8
  {
9
9
  variants: {
10
10
  variant: {
@@ -47,7 +47,7 @@ function AlertDescription({ className, ...props }: React.ComponentProps<"div">)
47
47
  }
48
48
 
49
49
  function AlertAction({ className, ...props }: React.ComponentProps<"div">) {
50
- return <div data-slot="alert-action" className={cn("absolute top-1.5 right-2", className)} {...props} />;
50
+ return <div data-slot="alert-action" className={cn("absolute top-1.5 end-2", className)} {...props} />;
51
51
  }
52
52
 
53
53
  export { Alert, AlertTitle, AlertDescription, AlertAction };
@@ -53,7 +53,7 @@ function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
53
53
  <span
54
54
  data-slot="avatar-badge"
55
55
  className={cn(
56
- "bg-primary text-primary-foreground ring-background absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-blend-color ring-2 select-none",
56
+ "bg-primary text-primary-foreground ring-background absolute end-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-blend-color ring-2 select-none",
57
57
  "group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
58
58
  "group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
59
59
  "group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
@@ -5,7 +5,7 @@ import { cva, type VariantProps } from "class-variance-authority";
5
5
  import { cn } from "@/lib/utils";
6
6
 
7
7
  const badgeVariants = cva(
8
- "h-5 gap-1 rounded-full border border-transparent px-2 py-0.5 text-xs font-medium transition-all has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-2.5! inline-flex items-center justify-center w-fit whitespace-nowrap shrink-0 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-colors overflow-hidden group/badge",
8
+ "h-5 gap-1 rounded-full border border-transparent px-2 py-0.5 text-xs font-medium transition-all has-data-[icon=inline-end]:pe-1.5 has-data-[icon=inline-start]:ps-1.5 [&>svg]:size-2.5! inline-flex items-center justify-center w-fit whitespace-nowrap shrink-0 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-colors overflow-hidden group/badge",
9
9
  {
10
10
  variants: {
11
11
  variant: {
@@ -61,7 +61,7 @@ function BreadcrumbSeparator({ children, className, ...props }: React.ComponentP
61
61
  className={cn("[&>svg]:size-3.5", className)}
62
62
  {...props}
63
63
  >
64
- {children ?? <ChevronRightIcon />}
64
+ {children ?? <ChevronRightIcon className="rtl:rotate-180" />}
65
65
  </li>
66
66
  );
67
67
  }
@@ -21,10 +21,10 @@ const buttonVariants = cva(
21
21
  },
22
22
  size: {
23
23
  default:
24
- "h-7 gap-1 px-2 text-xs/relaxed has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
25
- xs: "h-5 gap-1 rounded-sm px-2 text-xs/relaxed has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-2.5",
26
- sm: "h-6 gap-1 px-2 text-xs/relaxed has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
27
- lg: "h-8 gap-1 px-2.5 text-xs/relaxed has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 [&_svg:not([class*='size-'])]:size-4",
24
+ "h-7 gap-1 px-2 text-xs/relaxed has-data-[icon=inline-end]:pe-1.5 has-data-[icon=inline-start]:ps-1.5 [&_svg:not([class*='size-'])]:size-3.5",
25
+ xs: "h-5 gap-1 rounded-sm px-2 text-xs/relaxed has-data-[icon=inline-end]:pe-1.5 has-data-[icon=inline-start]:ps-1.5 [&_svg:not([class*='size-'])]:size-2.5",
26
+ sm: "h-6 gap-1 px-2 text-xs/relaxed has-data-[icon=inline-end]:pe-1.5 has-data-[icon=inline-start]:ps-1.5 [&_svg:not([class*='size-'])]:size-3",
27
+ lg: "h-8 gap-1 px-2.5 text-xs/relaxed has-data-[icon=inline-end]:pe-2 has-data-[icon=inline-start]:ps-2 [&_svg:not([class*='size-'])]:size-4",
28
28
  icon: "size-7 [&_svg:not([class*='size-'])]:size-3.5",
29
29
  "icon-xs": "size-5 rounded-sm [&_svg:not([class*='size-'])]:size-2.5",
30
30
  "icon-sm": "size-6 [&_svg:not([class*='size-'])]:size-3",