@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.
- package/dist/{BlockNoteEditor-BYMRWUVG.mjs → BlockNoteEditor-3D5D7YL7.mjs} +2 -2
- package/dist/{BlockNoteEditor-USRMFZGP.js → BlockNoteEditor-F6TGKMTW.js} +9 -9
- package/dist/{BlockNoteEditor-USRMFZGP.js.map → BlockNoteEditor-F6TGKMTW.js.map} +1 -1
- package/dist/billing/index.js +347 -347
- package/dist/billing/index.js.map +1 -1
- package/dist/billing/index.mjs +27 -27
- package/dist/billing/index.mjs.map +1 -1
- package/dist/{chunk-CFZW6GU4.mjs → chunk-G5LSXG5K.mjs} +2572 -2482
- package/dist/chunk-G5LSXG5K.mjs.map +1 -0
- package/dist/{chunk-5WFYQE22.js → chunk-KT4CTWCV.js} +825 -735
- package/dist/chunk-KT4CTWCV.js.map +1 -0
- package/dist/client/index.d.mts +5 -0
- package/dist/client/index.d.ts +5 -0
- package/dist/client/index.js +2 -2
- package/dist/client/index.mjs +1 -1
- package/dist/components/index.d.mts +13 -7
- package/dist/components/index.d.ts +13 -7
- package/dist/components/index.js +2 -2
- package/dist/components/index.mjs +1 -1
- package/dist/contexts/index.d.mts +13 -1
- package/dist/contexts/index.d.ts +13 -1
- package/dist/contexts/index.js +6 -2
- package/dist/contexts/index.js.map +1 -1
- package/dist/contexts/index.mjs +5 -1
- package/dist/features/help/index.js +35 -35
- package/dist/features/help/index.js.map +1 -1
- package/dist/features/help/index.mjs +6 -6
- package/dist/features/help/index.mjs.map +1 -1
- package/dist/features/tokenusage/index.js +135 -129
- package/dist/features/tokenusage/index.js.map +1 -1
- package/dist/features/tokenusage/index.mjs +74 -68
- package/dist/features/tokenusage/index.mjs.map +1 -1
- package/package.json +35 -35
- package/scripts/check-rtl-classes.mjs +60 -0
- package/scripts/check-rtl-classes.test.ts +25 -0
- package/src/components/TableCellAvatar.tsx +1 -1
- package/src/components/containers/PageContainer.tsx +1 -1
- package/src/components/containers/ReactMarkdownContainer.tsx +20 -4
- package/src/components/containers/RoundPageContainer.tsx +7 -7
- package/src/components/contents/AttributeElement.tsx +1 -1
- package/src/components/editors/BlockNoteDiffInlineContent.tsx +2 -2
- package/src/components/editors/BlockNoteEditorMentionHoverCard.tsx +1 -0
- package/src/components/forms/CommonDeleter.tsx +1 -1
- package/src/components/forms/CommonEditorButtons.tsx +1 -1
- package/src/components/forms/DatePickerPopover.tsx +2 -2
- package/src/components/forms/DateRangeSelector.tsx +2 -2
- package/src/components/forms/EditorSheet.tsx +18 -9
- package/src/components/forms/EntityMultiSelector.tsx +7 -7
- package/src/components/forms/EntitySelector.tsx +8 -8
- package/src/components/forms/FileUploader.tsx +1 -0
- package/src/components/forms/FormCheckbox.tsx +3 -3
- package/src/components/forms/FormDateTime.tsx +3 -3
- package/src/components/forms/FormFieldWrapper.tsx +1 -1
- package/src/components/forms/FormPlaceAutocomplete.tsx +2 -2
- package/src/components/forms/GdprConsentCheckbox.tsx +1 -1
- package/src/components/forms/MultiFileUploader.tsx +1 -1
- package/src/components/forms/PasswordInput.tsx +1 -1
- package/src/components/forms/__tests__/FormCheckbox.test.tsx +4 -4
- package/src/components/navigations/Header.tsx +2 -2
- package/src/components/navigations/PageSection.tsx +1 -1
- package/src/components/pages/PageContainerContentDetails.tsx +1 -1
- package/src/components/pages/PageContentContainer.tsx +5 -2
- package/src/components/tables/ContentListTable.tsx +4 -4
- package/src/components/tables/ContentTableSearch.tsx +5 -5
- package/src/contexts/DirectionContext.tsx +25 -0
- package/src/contexts/__tests__/DirectionContext.test.tsx +22 -0
- package/src/contexts/index.ts +1 -0
- package/src/features/ai-connection/components/lists/AiConnectionTypeCard.tsx +1 -1
- package/src/features/assistant/components/containers/AssistantPageContainer.tsx +1 -1
- package/src/features/assistant/components/parts/AssistantBlockNoteComposer.tsx +2 -2
- package/src/features/assistant/components/parts/AssistantComposer.tsx +2 -2
- package/src/features/assistant/components/parts/AssistantEmptyState.tsx +1 -1
- package/src/features/assistant/components/parts/AssistantSidebar.tsx +3 -3
- package/src/features/assistant-action/components/ApprovalActionCard.tsx +1 -1
- package/src/features/assistant-action/components/AssistantContainerWithApprovals.tsx +1 -1
- package/src/features/assistant-message/components/MessageItem.tsx +3 -3
- package/src/features/assistant-message/components/__tests__/MessageItem.spec.tsx +1 -0
- package/src/features/assistant-message/components/parts/MessageSourcesPanel.tsx +2 -2
- package/src/features/assistant-message/components/parts/RelevanceMeter.tsx +1 -1
- package/src/features/assistant-message/components/parts/tabs/SuggestedQuestionsTab.tsx +1 -1
- package/src/features/auth/components/buttons/GoogleSignInButton.tsx +1 -1
- package/src/features/auth/components/forms/Register.tsx +2 -2
- package/src/features/auth/components/two-factor/BackupCodesDialog.tsx +3 -3
- package/src/features/auth/utils/__tests__/clearClientStorage.spec.ts +4 -4
- package/src/features/billing/components/cards/BillingUsageSummaryCard.tsx +1 -1
- package/src/features/billing/components/cards/CustomerInfoCard.tsx +1 -1
- package/src/features/billing/components/cards/InvoicesSummaryCard.tsx +1 -1
- package/src/features/billing/components/cards/PaymentMethodSummaryCard.tsx +1 -1
- package/src/features/billing/components/cards/SubscriptionSummaryCard.tsx +1 -1
- package/src/features/billing/components/containers/BillingDashboardContainer.tsx +1 -1
- package/src/features/billing/stripe-customer/components/details/PaymentMethodCard.tsx +1 -1
- package/src/features/billing/stripe-invoice/components/details/InvoiceDetails.tsx +6 -6
- package/src/features/billing/stripe-invoice/components/lists/InvoicesList.tsx +2 -2
- package/src/features/billing/stripe-price/hooks/useStripePriceTableStructure.tsx +3 -3
- package/src/features/billing/stripe-product/hooks/useStripeProductTableStructure.tsx +2 -2
- package/src/features/billing/stripe-subscription/components/lists/SubscriptionsList.tsx +4 -4
- package/src/features/billing/stripe-subscription/components/widgets/PricingCard.tsx +2 -2
- package/src/features/billing/stripe-subscription/components/widgets/SubscriptionConfirmation.tsx +1 -1
- package/src/features/billing/stripe-subscription/components/wizards/WizardStepReview.tsx +2 -2
- package/src/features/billing/stripe-usage/components/lists/UsageHistoryTable.tsx +2 -2
- package/src/features/company/components/forms/CompanyDeleter.tsx +4 -4
- package/src/features/content/hooks/useContentTableStructure.tsx +1 -1
- package/src/features/feature/components/forms/FormFeatures.tsx +2 -2
- package/src/features/help/components/HelpAssistantSheet.tsx +3 -2
- package/src/features/help/components/HelpHint.tsx +2 -2
- package/src/features/help/components/HelpTOC.tsx +1 -1
- package/src/features/how-to/components/containers/HowToCommand.tsx +2 -2
- package/src/features/how-to/components/containers/HowToCommandViewer.tsx +5 -5
- package/src/features/how-to/components/forms/HowToEditor.tsx +3 -3
- package/src/features/how-to/components/forms/HowToSelector.tsx +5 -5
- package/src/features/notification/components/lists/NotificationsList.tsx +2 -2
- package/src/features/notification/components/modals/NotificationModal.tsx +1 -1
- package/src/features/oauth/components/OAuthClientCard.tsx +2 -2
- package/src/features/oauth/components/OAuthClientDetail.tsx +2 -2
- package/src/features/oauth/components/OAuthClientList.tsx +3 -3
- package/src/features/oauth/components/OAuthRedirectUriInput.tsx +1 -1
- package/src/features/oauth/components/OAuthScopeSelector.tsx +2 -2
- package/src/features/onboarding/components/OnboardingCard.tsx +1 -7
- package/src/features/rbac/components/RbacByRoleContainer.tsx +3 -3
- package/src/features/rbac/components/RbacContainer.tsx +3 -3
- package/src/features/referral/components/ReferralWidget.tsx +1 -0
- package/src/features/role/components/forms/FormRoles.tsx +1 -1
- package/src/features/role/components/forms/RemoveUserFromRole.tsx +1 -1
- package/src/features/role/components/forms/UserRoleAdd.tsx +1 -1
- package/src/features/tokenusage/components/TokenUsageBreakdownTable.tsx +9 -9
- package/src/features/tokenusage/components/TokenUsageRankedBar.tsx +8 -3
- package/src/features/tokenusage/components/TokenUsageTimelineChart.tsx +5 -1
- package/src/features/user/components/details/UserStandaloneDetails.tsx +1 -1
- package/src/features/user/components/forms/UserReactivator.tsx +2 -2
- package/src/features/user/components/forms/UserResentInvitationEmail.tsx +1 -1
- package/src/features/user/components/forms/UserSelector.tsx +7 -7
- package/src/features/user/components/lists/ContributorsList.tsx +1 -1
- package/src/features/user/components/lists/UserListInAdd.tsx +1 -1
- package/src/features/user/components/widgets/UserSearchPopover.tsx +5 -5
- package/src/features/user/hooks/useUserTableStructure.tsx +1 -1
- package/src/features/waitlist/components/forms/WaitlistQuestionnaireRenderer.tsx +1 -1
- package/src/features/waitlist/hooks/useWaitlistTableStructure.tsx +1 -1
- package/src/hooks/useCustomD3Graph.tsx +16 -0
- package/src/lib/__tests__/direction.test.ts +18 -0
- package/src/lib/direction.ts +8 -0
- package/src/shadcnui/custom/multi-select.tsx +6 -6
- package/src/shadcnui/custom/multiple-selector.tsx +6 -2
- package/src/shadcnui/ui/accordion.tsx +1 -1
- package/src/shadcnui/ui/alert-dialog.tsx +2 -1
- package/src/shadcnui/ui/alert.tsx +2 -2
- package/src/shadcnui/ui/avatar.tsx +1 -1
- package/src/shadcnui/ui/badge.tsx +1 -1
- package/src/shadcnui/ui/breadcrumb.tsx +1 -1
- package/src/shadcnui/ui/button.tsx +4 -4
- package/src/shadcnui/ui/calendar.tsx +9 -8
- package/src/shadcnui/ui/carousel.tsx +18 -11
- package/src/shadcnui/ui/combobox.tsx +3 -3
- package/src/shadcnui/ui/command.tsx +2 -2
- package/src/shadcnui/ui/context-menu.tsx +15 -11
- package/src/shadcnui/ui/dialog.tsx +2 -1
- package/src/shadcnui/ui/drawer.tsx +19 -3
- package/src/shadcnui/ui/dropdown-menu.tsx +12 -10
- package/src/shadcnui/ui/field.tsx +2 -2
- package/src/shadcnui/ui/input-group.tsx +3 -3
- package/src/shadcnui/ui/input-otp.tsx +2 -0
- package/src/shadcnui/ui/navigation-menu.tsx +2 -2
- package/src/shadcnui/ui/progress.tsx +1 -1
- package/src/shadcnui/ui/radio-group.tsx +1 -0
- package/src/shadcnui/ui/resizable.tsx +1 -0
- package/src/shadcnui/ui/scroll-area.tsx +1 -1
- package/src/shadcnui/ui/select.tsx +2 -2
- package/src/shadcnui/ui/sheet.tsx +11 -4
- package/src/shadcnui/ui/sidebar.tsx +37 -20
- package/src/shadcnui/ui/sonner.tsx +6 -1
- package/src/shadcnui/ui/switch.tsx +1 -1
- package/src/shadcnui/ui/table.tsx +2 -2
- package/src/shadcnui/ui/tabs.tsx +1 -1
- package/dist/chunk-5WFYQE22.js.map +0 -1
- package/dist/chunk-CFZW6GU4.mjs.map +0 -1
- /package/dist/{BlockNoteEditor-BYMRWUVG.mjs.map → BlockNoteEditor-3D5D7YL7.mjs.map} +0 -0
|
@@ -15,7 +15,7 @@ type FormCheckboxProps = {
|
|
|
15
15
|
export function FormCheckbox({ form, id, name, labelBefore, description, isRequired }: FormCheckboxProps) {
|
|
16
16
|
const simpleLabel = () => {
|
|
17
17
|
return (
|
|
18
|
-
<FieldLabel htmlFor={id} className={`font-normal ${labelBefore ? "" : "
|
|
18
|
+
<FieldLabel htmlFor={id} className={`font-normal ${labelBefore ? "" : "ms-3"}`}>
|
|
19
19
|
{name}
|
|
20
20
|
</FieldLabel>
|
|
21
21
|
);
|
|
@@ -38,10 +38,10 @@ export function FormCheckbox({ form, id, name, labelBefore, description, isRequi
|
|
|
38
38
|
{(field) => (
|
|
39
39
|
<div className="flex gap-x-4">
|
|
40
40
|
{labelBefore && label()}
|
|
41
|
-
{labelBefore && isRequired && <span className="
|
|
41
|
+
{labelBefore && isRequired && <span className="ms-1 text-destructive">*</span>}
|
|
42
42
|
<Checkbox id={id} checked={field.value ?? false} onCheckedChange={field.onChange} />
|
|
43
43
|
{!labelBefore && label()}
|
|
44
|
-
{!labelBefore && isRequired && <span className="
|
|
44
|
+
{!labelBefore && isRequired && <span className="ms-1 text-destructive">*</span>}
|
|
45
45
|
</div>
|
|
46
46
|
)}
|
|
47
47
|
</FormFieldWrapper>
|
|
@@ -117,7 +117,7 @@ export function FormDateTime({
|
|
|
117
117
|
render={
|
|
118
118
|
<Button
|
|
119
119
|
variant={"outline"}
|
|
120
|
-
className={cn("w-full
|
|
120
|
+
className={cn("w-full ps-3 text-start font-normal", !field.value && "text-muted-foreground")}
|
|
121
121
|
/>
|
|
122
122
|
}
|
|
123
123
|
>
|
|
@@ -126,11 +126,11 @@ export function FormDateTime({
|
|
|
126
126
|
) : (
|
|
127
127
|
<span>{placeholder ? placeholder : t(`common.pick_date_time`)}</span>
|
|
128
128
|
)}
|
|
129
|
-
<CalendarIcon className="
|
|
129
|
+
<CalendarIcon className="ms-auto h-4 w-4 opacity-50" />
|
|
130
130
|
</PopoverTrigger>
|
|
131
131
|
{field.value && allowEmpty !== false && (
|
|
132
132
|
<CircleXIcon
|
|
133
|
-
className="text-muted hover:text-destructive
|
|
133
|
+
className="text-muted hover:text-destructive ms-2 h-6 w-6 cursor-pointer"
|
|
134
134
|
onClick={() => {
|
|
135
135
|
if (onChange) onChange(undefined);
|
|
136
136
|
form.setValue(id, "");
|
|
@@ -43,7 +43,7 @@ export function FormFieldWrapper<T extends FieldValues>({
|
|
|
43
43
|
{label && (
|
|
44
44
|
<FieldLabel>
|
|
45
45
|
{label}
|
|
46
|
-
{isRequired && <span className="
|
|
46
|
+
{isRequired && <span className="ms-1 text-destructive">*</span>}
|
|
47
47
|
</FieldLabel>
|
|
48
48
|
)}
|
|
49
49
|
{children(field, fieldState)}
|
|
@@ -315,14 +315,14 @@ export function FormPlaceAutocomplete({
|
|
|
315
315
|
|
|
316
316
|
{/* Loading indicator */}
|
|
317
317
|
{isLoading && (
|
|
318
|
-
<div className="absolute
|
|
318
|
+
<div className="absolute end-3 top-1/2 -translate-y-1/2">
|
|
319
319
|
<div className="border-primary h-4 w-4 animate-spin rounded-full border-2 border-t-transparent"></div>
|
|
320
320
|
</div>
|
|
321
321
|
)}
|
|
322
322
|
|
|
323
323
|
{/* Suggestions dropdown */}
|
|
324
324
|
{showSuggestions && suggestions.length > 0 && (
|
|
325
|
-
<div className="bg-background absolute
|
|
325
|
+
<div className="bg-background absolute start-0 end-0 top-full z-50 mt-1 max-h-60 overflow-auto rounded-md border shadow-lg">
|
|
326
326
|
{suggestions.map((suggestion, index) => (
|
|
327
327
|
<div
|
|
328
328
|
key={suggestion.place_id || index}
|
|
@@ -28,7 +28,7 @@ export function GdprConsentCheckbox<T extends FieldValues>({
|
|
|
28
28
|
<div className="space-y-1 leading-none">
|
|
29
29
|
<span className="text-sm font-normal">
|
|
30
30
|
{label}
|
|
31
|
-
{required && <span className="
|
|
31
|
+
{required && <span className="ms-1 text-destructive">*</span>}
|
|
32
32
|
</span>
|
|
33
33
|
{description && <FieldDescription className="text-xs">{description}</FieldDescription>}
|
|
34
34
|
</div>
|
|
@@ -87,7 +87,7 @@ const FilePreviewItem = ({ file, onRemoveClick }: { file: File; onRemoveClick: (
|
|
|
87
87
|
<div className="text-muted-foreground group relative aspect-square h-full overflow-hidden rounded-lg border">
|
|
88
88
|
<button
|
|
89
89
|
onClick={onRemoveClick}
|
|
90
|
-
className="absolute top-1.5
|
|
90
|
+
className="absolute top-1.5 end-1.5 z-20 rounded-full bg-black/60 p-1 text-white opacity-0 transition-all duration-200 ease-in-out group-hover:opacity-100 hover:bg-red-600 focus:outline-none"
|
|
91
91
|
aria-label="Remove file"
|
|
92
92
|
>
|
|
93
93
|
<XIcon className="h-3.5 w-3.5" />
|
|
@@ -20,7 +20,7 @@ const PasswordInput = React.forwardRef<HTMLInputElement, PasswordInputProps>(
|
|
|
20
20
|
type="button"
|
|
21
21
|
variant="ghost"
|
|
22
22
|
size="sm"
|
|
23
|
-
className="absolute
|
|
23
|
+
className="absolute end-0 top-0 h-full px-3 py-2 hover:bg-transparent"
|
|
24
24
|
onClick={() => setShowPassword((prev) => !prev)}
|
|
25
25
|
disabled={disabled}
|
|
26
26
|
>
|
|
@@ -107,7 +107,7 @@ describe.skip("FormCheckbox", () => {
|
|
|
107
107
|
expect(labelIndex).toBeLessThan(checkboxIndex);
|
|
108
108
|
});
|
|
109
109
|
|
|
110
|
-
it("should apply
|
|
110
|
+
it("should apply ms-3 class to label when label is after checkbox", () => {
|
|
111
111
|
render(
|
|
112
112
|
<FormWrapper defaultValues={{ active: false }}>
|
|
113
113
|
{(form) => <FormCheckbox form={form} id="active" name="Active" />}
|
|
@@ -115,10 +115,10 @@ describe.skip("FormCheckbox", () => {
|
|
|
115
115
|
);
|
|
116
116
|
|
|
117
117
|
const label = screen.getByText("Active");
|
|
118
|
-
expect(label).toHaveClass("
|
|
118
|
+
expect(label).toHaveClass("ms-3");
|
|
119
119
|
});
|
|
120
120
|
|
|
121
|
-
it("should not apply
|
|
121
|
+
it("should not apply ms-3 class when labelBefore is true", () => {
|
|
122
122
|
render(
|
|
123
123
|
<FormWrapper defaultValues={{ active: false }}>
|
|
124
124
|
{(form) => <FormCheckbox form={form} id="active" name="Active" labelBefore={true} />}
|
|
@@ -126,7 +126,7 @@ describe.skip("FormCheckbox", () => {
|
|
|
126
126
|
);
|
|
127
127
|
|
|
128
128
|
const label = screen.getByText("Active");
|
|
129
|
-
expect(label).not.toHaveClass("
|
|
129
|
+
expect(label).not.toHaveClass("ms-3");
|
|
130
130
|
});
|
|
131
131
|
});
|
|
132
132
|
|
|
@@ -37,8 +37,8 @@ export function Header({ children, mobileChildren, leftContent, logo, className
|
|
|
37
37
|
<header
|
|
38
38
|
className={`bg-sidebar sticky top-0 z-10 flex h-[var(--app-header-h,calc(3rem_+_env(safe-area-inset-top)))] flex-col items-center justify-start gap-x-4 pt-[env(safe-area-inset-top)] ${className ?? ""}`}
|
|
39
39
|
>
|
|
40
|
-
<div className="bg-sidebar flex h-12 w-full flex-row items-center justify-between
|
|
41
|
-
{isMobile && logo && <div className="flex shrink-0 flex-row items-center
|
|
40
|
+
<div className="bg-sidebar flex h-12 w-full flex-row items-center justify-between ps-2 pe-4">
|
|
41
|
+
{isMobile && logo && <div className="flex shrink-0 flex-row items-center pe-1">{logo}</div>}
|
|
42
42
|
<SidebarTrigger aria-label="Toggle sidebar" id="sidebar-trigger" />
|
|
43
43
|
{leftContent}
|
|
44
44
|
<div className="flex w-full flex-row items-center justify-start">
|
|
@@ -47,7 +47,7 @@ export function PageSection({ children, title, options, open, small, onToggle }:
|
|
|
47
47
|
{isOpen ? (
|
|
48
48
|
<ChevronDownIcon className={`text-primary h-4 w-4`} />
|
|
49
49
|
) : (
|
|
50
|
-
<ChevronRightIcon className="text-primary h-4 w-4" />
|
|
50
|
+
<ChevronRightIcon className="text-primary h-4 w-4 rtl:rotate-180" />
|
|
51
51
|
)}
|
|
52
52
|
<h2 className={`flex w-full ${small === true ? `text-sm` : `text-lg`} text-primary font-semibold`}>
|
|
53
53
|
{title}
|
|
@@ -30,6 +30,7 @@ export function PageContentContainer({ header, details, footer, content, fullBle
|
|
|
30
30
|
// Returns saved layout and callback to save changes
|
|
31
31
|
const { defaultLayout, onLayoutChanged } = useDefaultLayout({
|
|
32
32
|
id: layoutId,
|
|
33
|
+
// rtl-ok: persisted panel identifiers, not CSS
|
|
33
34
|
panelIds: ["left-panel", "right-panel"],
|
|
34
35
|
storage: typeof window !== "undefined" ? localStorage : undefined,
|
|
35
36
|
});
|
|
@@ -47,7 +48,7 @@ export function PageContentContainer({ header, details, footer, content, fullBle
|
|
|
47
48
|
|
|
48
49
|
return (
|
|
49
50
|
<div className="flex h-[calc(100dvh-4rem)] w-full flex-col transition-opacity duration-150 animate-in fade-in">
|
|
50
|
-
{header && <div className="mb-4 flex w-full shrink-0 border-b
|
|
51
|
+
{header && <div className="mb-4 flex w-full shrink-0 border-b pe-4">{header}</div>}
|
|
51
52
|
<div className="min-h-0 flex-1">
|
|
52
53
|
{details || footer ? (
|
|
53
54
|
<ResizablePanelGroup
|
|
@@ -57,18 +58,20 @@ export function PageContentContainer({ header, details, footer, content, fullBle
|
|
|
57
58
|
onLayoutChanged={onLayoutChanged}
|
|
58
59
|
>
|
|
59
60
|
<ResizablePanel
|
|
61
|
+
// rtl-ok: persisted panel identifier, not CSS
|
|
60
62
|
id="left-panel"
|
|
61
63
|
defaultSize={leftPanelDefaultSize}
|
|
62
64
|
minSize={leftPanelMinSize}
|
|
63
65
|
maxSize={leftPanelMaxSize}
|
|
64
66
|
>
|
|
65
|
-
<div className={`@container flex h-full flex-col ${isMobile ? "pb-4" : "
|
|
67
|
+
<div className={`@container flex h-full flex-col ${isMobile ? "pb-4" : "pe-4"}`}>
|
|
66
68
|
<div className="flex-1 overflow-y-auto">{details}</div>
|
|
67
69
|
{footer && <div className="flex flex-col gap-y-2 pt-2 pb-2">{footer}</div>}
|
|
68
70
|
</div>
|
|
69
71
|
</ResizablePanel>
|
|
70
72
|
<ResizableHandle withHandle />
|
|
71
73
|
<ResizablePanel
|
|
74
|
+
// rtl-ok: persisted panel identifier, not CSS
|
|
72
75
|
id="right-panel"
|
|
73
76
|
defaultSize={rightPanelDefaultSize}
|
|
74
77
|
className={cn("w-full", isMobile ? "pt-4" : "")}
|
|
@@ -159,7 +159,7 @@ export const ContentListTable = memo(function ContentListTable(props: ContentLis
|
|
|
159
159
|
{props.title && (
|
|
160
160
|
<TableRow>
|
|
161
161
|
<TableHead
|
|
162
|
-
className="bg-card rounded-t-lg text-primary p-4 text-
|
|
162
|
+
className="bg-card rounded-t-lg text-primary p-4 text-start font-bold"
|
|
163
163
|
colSpan={tableColumns.length}
|
|
164
164
|
>
|
|
165
165
|
<div className="flex w-full items-center justify-between gap-x-2">
|
|
@@ -271,7 +271,7 @@ export const ContentListTable = memo(function ContentListTable(props: ContentLis
|
|
|
271
271
|
{showFooter && (
|
|
272
272
|
<TableFooter>
|
|
273
273
|
<TableRow>
|
|
274
|
-
<TableCell colSpan={tableColumns.length} className="bg-card py-4 text-
|
|
274
|
+
<TableCell colSpan={tableColumns.length} className="bg-card py-4 text-end">
|
|
275
275
|
<div className="flex items-center justify-end space-x-2">
|
|
276
276
|
<Button
|
|
277
277
|
variant="outline"
|
|
@@ -286,7 +286,7 @@ export const ContentListTable = memo(function ContentListTable(props: ContentLis
|
|
|
286
286
|
{pendingDirection === "prev" ? (
|
|
287
287
|
<Loader2 className="h-4 w-4 animate-spin" />
|
|
288
288
|
) : (
|
|
289
|
-
<ChevronLeft className="h-4 w-4" />
|
|
289
|
+
<ChevronLeft className="h-4 w-4 rtl:rotate-180" />
|
|
290
290
|
)}
|
|
291
291
|
</Button>
|
|
292
292
|
{data.pageInfo && (
|
|
@@ -307,7 +307,7 @@ export const ContentListTable = memo(function ContentListTable(props: ContentLis
|
|
|
307
307
|
{pendingDirection === "next" ? (
|
|
308
308
|
<Loader2 className="h-4 w-4 animate-spin" />
|
|
309
309
|
) : (
|
|
310
|
-
<ChevronRight className="h-4 w-4" />
|
|
310
|
+
<ChevronRight className="h-4 w-4 rtl:rotate-180" />
|
|
311
311
|
)}
|
|
312
312
|
</Button>
|
|
313
313
|
</div>
|
|
@@ -74,13 +74,13 @@ export function ContentTableSearch({ data }: ContentTableSearchProps) {
|
|
|
74
74
|
Labels reuse keys that exist in every consuming app; a new package-level key would
|
|
75
75
|
throw MISSING_MESSAGE wherever it had not been added. */}
|
|
76
76
|
{isExpanded ? (
|
|
77
|
-
<Search aria-hidden className="text-muted-foreground absolute top-1
|
|
77
|
+
<Search aria-hidden className="text-muted-foreground absolute top-1 start-1 size-4" />
|
|
78
78
|
) : (
|
|
79
79
|
<Button
|
|
80
80
|
data-testid="content-table-search-trigger"
|
|
81
81
|
variant="ghost"
|
|
82
82
|
size="icon-sm"
|
|
83
|
-
className="text-muted-foreground hover:text-foreground absolute top-0
|
|
83
|
+
className="text-muted-foreground hover:text-foreground absolute top-0 start-0"
|
|
84
84
|
onClick={handleSearchIconClick}
|
|
85
85
|
>
|
|
86
86
|
<Search className="size-4" />
|
|
@@ -100,19 +100,19 @@ export function ContentTableSearch({ data }: ContentTableSearchProps) {
|
|
|
100
100
|
onBlur={handleBlur}
|
|
101
101
|
placeholder={t(`ui.search.placeholder_global`)}
|
|
102
102
|
type="text"
|
|
103
|
-
className="border-border/50 focus-visible:border-border h-10 w-full
|
|
103
|
+
className="border-border/50 focus-visible:border-border h-10 w-full pe-8 ps-8 text-xs shadow-none focus-visible:ring-0"
|
|
104
104
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
105
105
|
value={searchTerm}
|
|
106
106
|
/>
|
|
107
107
|
)}
|
|
108
108
|
{isExpanded && isSearching && (
|
|
109
|
-
<RefreshCw className="text-muted-foreground absolute top-1
|
|
109
|
+
<RefreshCw className="text-muted-foreground absolute top-1 end-1 h-4 w-4 animate-spin" />
|
|
110
110
|
)}
|
|
111
111
|
{isExpanded && !isSearching && searchTermRef.current && (
|
|
112
112
|
<Button
|
|
113
113
|
variant="ghost"
|
|
114
114
|
size="icon-sm"
|
|
115
|
-
className="text-muted-foreground hover:text-foreground absolute top-0
|
|
115
|
+
className="text-muted-foreground hover:text-foreground absolute top-0 end-0"
|
|
116
116
|
onClick={handleClear}
|
|
117
117
|
>
|
|
118
118
|
<X className="size-4" />
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { DirectionProvider as BaseUIDirectionProvider } from "@base-ui/react/direction-provider";
|
|
5
|
+
|
|
6
|
+
export type Direction = "ltr" | "rtl";
|
|
7
|
+
|
|
8
|
+
const DirectionContext = React.createContext<Direction>("ltr");
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* App-owned direction. The consumer sets <html dir> AND mounts this provider
|
|
12
|
+
* with the same value; the package never derives direction from locale.
|
|
13
|
+
* Wraps Base UI's DirectionProvider so popups resolve align/side logically.
|
|
14
|
+
*/
|
|
15
|
+
export function DirectionProvider({ dir = "ltr", children }: { dir?: Direction; children: React.ReactNode }) {
|
|
16
|
+
return (
|
|
17
|
+
<DirectionContext.Provider value={dir}>
|
|
18
|
+
<BaseUIDirectionProvider direction={dir}>{children}</BaseUIDirectionProvider>
|
|
19
|
+
</DirectionContext.Provider>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function useDir(): Direction {
|
|
24
|
+
return React.useContext(DirectionContext);
|
|
25
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { DirectionProvider, useDir } from "../DirectionContext";
|
|
4
|
+
|
|
5
|
+
function Probe() {
|
|
6
|
+
return <span data-testid="dir">{useDir()}</span>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
describe("DirectionContext", () => {
|
|
10
|
+
it("defaults to ltr without a provider", () => {
|
|
11
|
+
render(<Probe />);
|
|
12
|
+
expect(screen.getByTestId("dir").textContent).toBe("ltr");
|
|
13
|
+
});
|
|
14
|
+
it("provides rtl", () => {
|
|
15
|
+
render(
|
|
16
|
+
<DirectionProvider dir="rtl">
|
|
17
|
+
<Probe />
|
|
18
|
+
</DirectionProvider>,
|
|
19
|
+
);
|
|
20
|
+
expect(screen.getByTestId("dir").textContent).toBe("rtl");
|
|
21
|
+
});
|
|
22
|
+
});
|
package/src/contexts/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ export * from "../features/how-to/contexts/HowToContext";
|
|
|
8
8
|
export * from "../features/rbac/contexts/RbacContext";
|
|
9
9
|
export * from "../features/assistant/contexts/AssistantContext";
|
|
10
10
|
export * from "./CommonContext";
|
|
11
|
+
export * from "./DirectionContext";
|
|
11
12
|
export * from "./HeaderChildrenContext";
|
|
12
13
|
export * from "./HeaderLeftContentContext";
|
|
13
14
|
export * from "./HeaderLogoContext";
|
|
@@ -123,7 +123,7 @@ export function AiConnectionTypeCard({ connectionType }: AiConnectionTypeCardPro
|
|
|
123
123
|
className={cn("flex items-center gap-3 px-3 py-2", readOnly && "opacity-50")}
|
|
124
124
|
data-testid={`ai-connection-row-${connection.id}`}
|
|
125
125
|
>
|
|
126
|
-
<span className="text-muted-foreground w-5 text-
|
|
126
|
+
<span className="text-muted-foreground w-5 text-end text-xs tabular-nums">{index + 1}</span>
|
|
127
127
|
<div className="flex min-w-0 flex-1 flex-col">
|
|
128
128
|
<span className="truncate text-sm">{connection.name}</span>
|
|
129
129
|
<span className="text-muted-foreground truncate text-xs">
|
|
@@ -70,7 +70,7 @@ export function AssistantPageContainer({
|
|
|
70
70
|
details={
|
|
71
71
|
// The panel supplies its own padding and border; cancel both so the
|
|
72
72
|
// sidebar reads as the panel body rather than a card inside it.
|
|
73
|
-
<div className="-m-4 flex h-full min-h-0 [&>aside]:w-full [&>aside]:border-
|
|
73
|
+
<div className="-m-4 flex h-full min-h-0 [&>aside]:w-full [&>aside]:border-e-0 [&>aside]:bg-transparent">
|
|
74
74
|
<AssistantSidebar
|
|
75
75
|
threads={ctx.threads}
|
|
76
76
|
activeId={ctx.assistant?.id}
|
|
@@ -126,10 +126,10 @@ export function AssistantBlockNoteComposer({
|
|
|
126
126
|
/>
|
|
127
127
|
</div>
|
|
128
128
|
<Button onClick={submit} disabled={!canSend} size="sm" className="h-8">
|
|
129
|
-
<ArrowUp className="
|
|
129
|
+
<ArrowUp className="me-1 h-4 w-4" /> {t("features.assistant.send")}
|
|
130
130
|
</Button>
|
|
131
131
|
</div>
|
|
132
|
-
<div className="text-muted-foreground text-
|
|
132
|
+
<div className="text-muted-foreground text-end text-xs">{t("features.assistant.keyboard_hint")}</div>
|
|
133
133
|
</div>
|
|
134
134
|
);
|
|
135
135
|
}
|
|
@@ -47,10 +47,10 @@ export function AssistantComposer({ onSend, disabled, value: controlled, onValue
|
|
|
47
47
|
className="min-h-[48px] resize-none border-0 bg-transparent focus-visible:ring-0"
|
|
48
48
|
/>
|
|
49
49
|
<Button onClick={submit} disabled={!canSend} size="sm" className="h-8">
|
|
50
|
-
<ArrowUp className="
|
|
50
|
+
<ArrowUp className="me-1 h-4 w-4" /> {t("ui.buttons.save")}
|
|
51
51
|
</Button>
|
|
52
52
|
</div>
|
|
53
|
-
<div className="text-muted-foreground text-
|
|
53
|
+
<div className="text-muted-foreground text-end text-xs">{t("features.assistant.keyboard_hint")}</div>
|
|
54
54
|
</div>
|
|
55
55
|
);
|
|
56
56
|
}
|
|
@@ -55,7 +55,7 @@ export function AssistantEmptyState({ onSend, operatorMode = false, onOperatorMo
|
|
|
55
55
|
<button
|
|
56
56
|
key={k}
|
|
57
57
|
type="button"
|
|
58
|
-
className="border-border bg-muted/30 hover:bg-muted rounded-lg border p-3 text-
|
|
58
|
+
className="border-border bg-muted/30 hover:bg-muted rounded-lg border p-3 text-start text-sm"
|
|
59
59
|
onClick={() => setDraft(text)}
|
|
60
60
|
>
|
|
61
61
|
{text}
|
|
@@ -29,7 +29,7 @@ export function AssistantSidebar({ threads, activeId, onSelect, onNew }: Props)
|
|
|
29
29
|
type="button"
|
|
30
30
|
onClick={() => onSelect(thread.id)}
|
|
31
31
|
className={
|
|
32
|
-
"block w-full truncate rounded-md px-2 py-1.5 text-
|
|
32
|
+
"block w-full truncate rounded-md px-2 py-1.5 text-start text-sm " +
|
|
33
33
|
(activeId === thread.id ? "bg-primary/10 text-primary" : "hover:bg-muted text-foreground")
|
|
34
34
|
}
|
|
35
35
|
>
|
|
@@ -41,10 +41,10 @@ export function AssistantSidebar({ threads, activeId, onSelect, onNew }: Props)
|
|
|
41
41
|
};
|
|
42
42
|
|
|
43
43
|
return (
|
|
44
|
-
<aside className="bg-muted/30 flex w-64 flex-col border-
|
|
44
|
+
<aside className="bg-muted/30 flex w-64 flex-col border-e">
|
|
45
45
|
<div className="border-b p-3">
|
|
46
46
|
<Button onClick={onNew} className="w-full" size="sm">
|
|
47
|
-
<Plus className="
|
|
47
|
+
<Plus className="me-1 h-4 w-4" /> {t("features.assistant.new")}
|
|
48
48
|
</Button>
|
|
49
49
|
</div>
|
|
50
50
|
<div className="flex-1 overflow-y-auto p-2">
|
|
@@ -103,7 +103,7 @@ export function ApprovalActionCard({ actionId, summary, onResolved }: Props) {
|
|
|
103
103
|
<ShieldAlert className="text-warning h-3.5 w-3.5" />
|
|
104
104
|
<span>{t("features.assistant.approval.title")}</span>
|
|
105
105
|
{status && status !== "pending" && (
|
|
106
|
-
<Badge variant={STATUS_VARIANT[status]} className="
|
|
106
|
+
<Badge variant={STATUS_VARIANT[status]} className="ms-auto tracking-normal normal-case">
|
|
107
107
|
{t(STATUS_LABEL_KEY[status])}
|
|
108
108
|
</Badge>
|
|
109
109
|
)}
|
|
@@ -24,7 +24,7 @@ export function AssistantContainerWithApprovals() {
|
|
|
24
24
|
onResolved={ctx.appendResolvedMessage}
|
|
25
25
|
/>
|
|
26
26
|
) : (
|
|
27
|
-
<div className="bg-muted text-foreground rounded-2xl rounded-
|
|
27
|
+
<div className="bg-muted text-foreground rounded-2xl rounded-es-sm px-3.5 py-2.5 text-sm leading-relaxed">
|
|
28
28
|
{message.content}
|
|
29
29
|
</div>
|
|
30
30
|
)
|
|
@@ -82,7 +82,7 @@ export function MessageItem({
|
|
|
82
82
|
renders invisible here, so force the on-primary colour and lean on
|
|
83
83
|
weight for the affordance (links are never underlined in this UI).
|
|
84
84
|
*/}
|
|
85
|
-
<div className="bg-primary text-primary-foreground max-w-[72%] rounded-2xl rounded-
|
|
85
|
+
<div className="bg-primary text-primary-foreground max-w-[72%] rounded-2xl rounded-ee-sm px-3.5 py-2 text-sm [&_a]:text-primary-foreground [&_a]:font-semibold [&_p]:m-0">
|
|
86
86
|
<ReactMarkdown remarkPlugins={[remarkGfm]} urlTransform={mentionUrlTransform} components={markdownComponents}>
|
|
87
87
|
{message.content}
|
|
88
88
|
</ReactMarkdown>
|
|
@@ -104,7 +104,7 @@ export function MessageItem({
|
|
|
104
104
|
|
|
105
105
|
return (
|
|
106
106
|
<div className="flex min-w-0 max-w-[78%] flex-col gap-1.5">
|
|
107
|
-
<div className="text-muted-foreground flex items-center gap-2
|
|
107
|
+
<div className="text-muted-foreground flex items-center gap-2 ps-1 text-xs">
|
|
108
108
|
<span className="flex h-4 w-4 items-center justify-center rounded-full bg-gradient-to-br from-blue-400 to-violet-500 text-white">
|
|
109
109
|
<Sparkles className="h-2.5 w-2.5" />
|
|
110
110
|
</span>
|
|
@@ -117,7 +117,7 @@ export function MessageItem({
|
|
|
117
117
|
// zeroed): restore flow spacing between sibling blocks and give
|
|
118
118
|
// blockquotes a callout treatment — assistant answers use them for
|
|
119
119
|
// proposals and asides that must stand apart from factual prose.
|
|
120
|
-
<div className="bg-muted text-foreground rounded-2xl rounded-
|
|
120
|
+
<div className="bg-muted text-foreground rounded-2xl rounded-es-sm px-3.5 py-2.5 text-sm leading-relaxed [&>*+*]:mt-3 [&_blockquote]:border-s-2 [&_blockquote]:border-primary/50 [&_blockquote]:bg-primary/5 [&_blockquote]:rounded-md [&_blockquote]:px-3 [&_blockquote]:py-1.5">
|
|
121
121
|
<ReactMarkdown remarkPlugins={[remarkGfm]} components={markdownComponents} urlTransform={mentionUrlTransform}>
|
|
122
122
|
{message.content}
|
|
123
123
|
</ReactMarkdown>
|
|
@@ -59,6 +59,7 @@ function buildMessageStub(p: {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
describe("MessageItem", () => {
|
|
62
|
+
// rtl-ok: prose in the test name, not a CSS class
|
|
62
63
|
it("user message is in a right-aligned bubble with plain text", () => {
|
|
63
64
|
const msg = buildMessageStub({ role: "user", content: "hello" });
|
|
64
65
|
render(<MessageItem message={msg} isLatestAssistant={false} onSelectFollowUp={vi.fn()} />);
|
|
@@ -96,7 +96,7 @@ export function MessageSourcesPanel({ message, isLatestAssistant, onSelectFollow
|
|
|
96
96
|
aria-controls={panelId}
|
|
97
97
|
className="text-primary inline-flex items-center gap-1 text-xs font-medium"
|
|
98
98
|
>
|
|
99
|
-
{open ? <ChevronDown className="h-3 w-3" /> : <ChevronRight className="h-3 w-3" />}
|
|
99
|
+
{open ? <ChevronDown className="h-3 w-3" /> : <ChevronRight className="h-3 w-3 rtl:rotate-180" />}
|
|
100
100
|
{open
|
|
101
101
|
? t("features.assistant.message.sources.toggle_hide")
|
|
102
102
|
: t("features.assistant.message.sources.toggle", { count: total })}
|
|
@@ -109,7 +109,7 @@ export function MessageSourcesPanel({ message, isLatestAssistant, onSelectFollow
|
|
|
109
109
|
{visibleTabs.map((key) => (
|
|
110
110
|
<TabsTrigger key={key} value={key}>
|
|
111
111
|
{t(`features.assistant.message.sources.tabs.${key === "suggested" ? "suggested_questions" : key}`)}
|
|
112
|
-
<span className="text-muted-foreground
|
|
112
|
+
<span className="text-muted-foreground ms-1.5 text-xs">
|
|
113
113
|
{key === "suggested" && suggestionsCount}
|
|
114
114
|
{key === "references" && refsCount}
|
|
115
115
|
{key === "citations" && citationsCount}
|
|
@@ -18,7 +18,7 @@ export function RelevanceMeter({ value, className = "" }: Props) {
|
|
|
18
18
|
aria-label={`Relevance ${label}`}
|
|
19
19
|
className={`bg-muted relative mx-auto flex h-5 w-20 items-center justify-center overflow-hidden rounded border ${className}`}
|
|
20
20
|
>
|
|
21
|
-
<div className="bg-accent absolute top-0
|
|
21
|
+
<div className="bg-accent absolute top-0 start-0 h-full" style={{ width: `${pct}%` }} />
|
|
22
22
|
<span
|
|
23
23
|
className={`relative text-xs ${pct < 40 ? "text-muted-foreground" : "text-accent-foreground font-semibold"}`}
|
|
24
24
|
>
|
|
@@ -14,7 +14,7 @@ export function SuggestedQuestionsTab({ questions, onSelect }: Props) {
|
|
|
14
14
|
key={q}
|
|
15
15
|
type="button"
|
|
16
16
|
onClick={() => onSelect(q)}
|
|
17
|
-
className="border-border bg-muted/30 hover:bg-muted rounded-md border px-3 py-1.5 text-
|
|
17
|
+
className="border-border bg-muted/30 hover:bg-muted rounded-md border px-3 py-1.5 text-start text-sm"
|
|
18
18
|
>
|
|
19
19
|
{q}
|
|
20
20
|
</button>
|
|
@@ -27,7 +27,7 @@ export function GoogleSignInButton({ referralCode }: GoogleSignInButtonProps) {
|
|
|
27
27
|
variant="outline"
|
|
28
28
|
data-testid="page-login-button-google"
|
|
29
29
|
>
|
|
30
|
-
<svg className="
|
|
30
|
+
<svg className="me-2 h-5 w-5" viewBox="0 0 24 24">
|
|
31
31
|
<path
|
|
32
32
|
fill="#4285F4"
|
|
33
33
|
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
|
|
@@ -304,7 +304,7 @@ export default function Register() {
|
|
|
304
304
|
variant="outline"
|
|
305
305
|
type="button"
|
|
306
306
|
>
|
|
307
|
-
<svg className="
|
|
307
|
+
<svg className="me-2 h-5 w-5" viewBox="0 0 24 24">
|
|
308
308
|
<path
|
|
309
309
|
fill="#4285F4"
|
|
310
310
|
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
|
|
@@ -332,7 +332,7 @@ export default function Register() {
|
|
|
332
332
|
className="flex w-full"
|
|
333
333
|
>
|
|
334
334
|
<Button className="w-full" variant="outline" type="button">
|
|
335
|
-
<svg className="
|
|
335
|
+
<svg className="me-2 h-5 w-5" viewBox="0 0 24 24" fill="currentColor">
|
|
336
336
|
<path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028 14.09 14.09 0 0 0 1.226-1.994.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z" />
|
|
337
337
|
</svg>
|
|
338
338
|
{t("waitlist.buttons.register_with_discord")}
|
|
@@ -101,7 +101,7 @@ export function BackupCodesDialog({ remainingCodes, onRegenerate, trigger }: Bac
|
|
|
101
101
|
<AlertDialogTrigger
|
|
102
102
|
render={
|
|
103
103
|
<Button variant="outline" className="w-full">
|
|
104
|
-
<RefreshCw className="h-4 w-4
|
|
104
|
+
<RefreshCw className="h-4 w-4 me-2" />
|
|
105
105
|
{t("auth.two_factor.generate_new_codes")}
|
|
106
106
|
</Button>
|
|
107
107
|
}
|
|
@@ -131,11 +131,11 @@ export function BackupCodesDialog({ remainingCodes, onRegenerate, trigger }: Bac
|
|
|
131
131
|
</div>
|
|
132
132
|
<div className="flex gap-2">
|
|
133
133
|
<Button variant="outline" className="flex-1" onClick={handleCopyAll}>
|
|
134
|
-
<Copy className="h-4 w-4
|
|
134
|
+
<Copy className="h-4 w-4 me-2" />
|
|
135
135
|
{t("auth.two_factor.copy_all")}
|
|
136
136
|
</Button>
|
|
137
137
|
<Button variant="outline" className="flex-1" onClick={handleDownload}>
|
|
138
|
-
<Download className="h-4 w-4
|
|
138
|
+
<Download className="h-4 w-4 me-2" />
|
|
139
139
|
{t("auth.two_factor.download")}
|
|
140
140
|
</Button>
|
|
141
141
|
</div>
|
|
@@ -63,18 +63,18 @@ describe("clearClientStorage", () => {
|
|
|
63
63
|
describe("Scenario: Handles keys with special characters", () => {
|
|
64
64
|
it("should properly remove keys containing colons", () => {
|
|
65
65
|
const keys = [
|
|
66
|
-
"react-resizable-panels:page-content-container-desktop:left-panel:right-panel",
|
|
67
|
-
"react-resizable-panels:page-content-container-mobile:left-panel:right-panel",
|
|
66
|
+
"react-resizable-panels:page-content-container-desktop:left-panel:right-panel", // rtl-ok: localStorage key, not a CSS class
|
|
67
|
+
"react-resizable-panels:page-content-container-mobile:left-panel:right-panel", // rtl-ok: localStorage key, not a CSS class
|
|
68
68
|
];
|
|
69
69
|
|
|
70
70
|
clearClientStorage(keys);
|
|
71
71
|
|
|
72
72
|
expect(window.localStorage.removeItem).toHaveBeenCalledTimes(2);
|
|
73
73
|
expect(window.localStorage.removeItem).toHaveBeenCalledWith(
|
|
74
|
-
"react-resizable-panels:page-content-container-desktop:left-panel:right-panel",
|
|
74
|
+
"react-resizable-panels:page-content-container-desktop:left-panel:right-panel", // rtl-ok: localStorage key, not a CSS class
|
|
75
75
|
);
|
|
76
76
|
expect(window.localStorage.removeItem).toHaveBeenCalledWith(
|
|
77
|
-
"react-resizable-panels:page-content-container-mobile:left-panel:right-panel",
|
|
77
|
+
"react-resizable-panels:page-content-container-mobile:left-panel:right-panel", // rtl-ok: localStorage key, not a CSS class
|
|
78
78
|
);
|
|
79
79
|
});
|
|
80
80
|
});
|
|
@@ -87,7 +87,7 @@ export function BillingUsageSummaryCard({
|
|
|
87
87
|
{meters.length > 1 && <p className="text-xs text-muted-foreground">Across {meters.length} meters</p>}
|
|
88
88
|
<span className="flex items-center text-xs text-muted-foreground">
|
|
89
89
|
View details
|
|
90
|
-
<ChevronRight className="h-3 w-3
|
|
90
|
+
<ChevronRight className="h-3 w-3 ms-1 rtl:rotate-180" />
|
|
91
91
|
</span>
|
|
92
92
|
</div>
|
|
93
93
|
)}
|