Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tall-donuts-repeat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/ui': patch
---

Use logical CSS properties for the organization members footer and the MFA setup task header, so both lay out correctly in right-to-left locales.
30 changes: 22 additions & 8 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ export default tseslint.config([
'custom-rules': {
rules: {
'no-global-object': noGlobalObject,
'no-navigate-useClerk': noNavigateUseClerk,
'no-unstable-methods': noUnstableMethods,
'no-physical-css-properties': noPhysicalCssProperties,
},
Expand Down Expand Up @@ -518,14 +519,6 @@ export default tseslint.config([
{
name: 'packages/clerk-js',
files: ['packages/clerk-js/src/ui/**/*'],
plugins: {
'custom-rules': {
rules: {
'no-navigate-useClerk': noNavigateUseClerk,
'no-unstable-methods': noUnstableMethods,
},
},
},
rules: {
'custom-rules/no-navigate-useClerk': 'error',
'custom-rules/no-unstable-methods': 'error',
Expand All @@ -534,15 +527,23 @@ export default tseslint.config([
{
name: 'packages/ui',
files: ['packages/ui/src/**/*'],
// Tests assert on style values they receive; they are not authoring styles.
ignores: ['packages/ui/src/**/__tests__/**', 'packages/ui/src/**/*.test.{ts,tsx}'],
rules: {
'custom-rules/no-physical-css-properties': 'error',
'custom-rules/no-navigate-useClerk': 'error',
'custom-rules/no-unstable-methods': 'error',
},
},
{
name: 'packages/ui/mosaic',
files: ['packages/ui/src/mosaic/**/*'],
ignores: ['packages/ui/src/mosaic/utils.ts', 'packages/ui/src/mosaic/__tests__/**'],
rules: {
// Mosaic renders elements through `render={p => <el {...p} />}`, so children and controls sit on
// the outer component. Both rules only see the empty inner element and always report.
'jsx-a11y/heading-has-content': 'off',
'jsx-a11y/label-has-associated-control': 'off',
'no-restricted-syntax': [
'error',
{
Expand All @@ -566,6 +567,19 @@ export default tseslint.config([
files: ['packages/*/src/**/*.test.{ts,tsx}'],
rules: {
'@typescript-eslint/unbound-method': 'off',
// vitest's `importOriginal<typeof import('...')>()` has no type-import equivalent.
'@typescript-eslint/consistent-type-imports': [
'error',
{ prefer: 'type-imports', disallowTypeAnnotations: false, fixStyle: 'separate-type-imports' },
],
},
},
{
name: 'packages - vitest type tests',
files: ['packages/*/src/**/*.test-d.ts'],
rules: {
// Type tests bind values only to read their type back off with `typeof`.
'@typescript-eslint/no-unused-vars': 'off',
},
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { ERROR_MISSING_FRONTEND_API_URL } from '../../common/errors';

// We need to reset the module-level WeakSet between tests
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
let setupClerkTestingToken: (typeof import('../setupClerkTestingToken'))['setupClerkTestingToken'];

function createMockRoute(
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@
"dev:sandbox:serve": "rspack serve --config rspack.config.js --env devOrigin=http://localhost:${UI_PORT:-4011}",
"format": "node ../../scripts/format-package.mjs",
"format:check": "node ../../scripts/format-package.mjs --check",
"lint": "eslint src",
"lint:attw": "attw --pack . --exclude-entrypoints themes/shadcn.css --profile esm-only",
"lint:disabled": "eslint src",
"lint:publint": "publint",
"showerrors": "tsc",
"test": "vitest run",
Expand Down
6 changes: 3 additions & 3 deletions packages/ui/src/Components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ import type {
EnvironmentResource,
GoogleOneTapProps,
OrganizationProfileModalProps,
SignInProps,
SignInModalProps,
SignUpProps,
SignInProps,
SignUpModalProps,
SignUpProps,
UserProfileModalProps,
UserProfileProps,
WaitlistProps,
WaitlistModalProps,
WaitlistProps,
} from '@clerk/shared/types';
import { createDeferredPromise } from '@clerk/shared/utils';
import React, { Suspense, useCallback, useRef, useSyncExternalStore } from 'react';
Expand Down
9 changes: 4 additions & 5 deletions packages/ui/src/common/__tests__/ProviderIcon.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ describe('ProviderIcon', () => {
);

const icon = screen.getByLabelText('Apple icon');
const styles = window.getComputedStyle(icon);

// Check that mask-image is applied (via inline styles)
expect(icon).toHaveStyle({
Expand Down Expand Up @@ -225,7 +224,7 @@ describe('ProviderIcon', () => {
id='google'
iconUrl={null}
name='Google'
isLoading={true}
isLoading
/>,
{ wrapper },
);
Expand All @@ -242,7 +241,7 @@ describe('ProviderIcon', () => {
id='google'
iconUrl={null}
name='Google'
isDisabled={true}
isDisabled
/>,
{ wrapper },
);
Expand All @@ -261,7 +260,7 @@ describe('ProviderIcon', () => {
id='google'
iconUrl='https://example.com/google-icon.svg'
name='Google'
isLoading={true}
isLoading
/>,
{ wrapper },
);
Expand All @@ -279,7 +278,7 @@ describe('ProviderIcon', () => {
id='google'
iconUrl='https://example.com/google-icon.svg'
name='Google'
isDisabled={true}
isDisabled
/>,
{ wrapper },
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe('APIKeyModal modalRoot behavior', () => {
modalRoot.current = container;
document.body.appendChild(container);

const { container: testContainer } = render(
render(
<AppearanceProvider appearanceKey='signIn'>
<InternalThemeProvider>
<APIKeyModal
Expand Down Expand Up @@ -43,7 +43,7 @@ describe('APIKeyModal modalRoot behavior', () => {
modalRoot.current = container;
document.body.appendChild(container);

const { container: testContainer } = render(
render(
<AppearanceProvider appearanceKey='signIn'>
<InternalThemeProvider>
<APIKeyModal
Expand Down Expand Up @@ -76,7 +76,7 @@ describe('APIKeyModal modalRoot behavior', () => {

const getContainer = () => container2;

const { container: testContainer } = render(
render(
<AppearanceProvider appearanceKey='signIn'>
<InternalThemeProvider>
<UNSAFE_PortalProvider getContainer={getContainer}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ProfileSection } from '@/ui/elements/Section';

import { useCreditBalance, useSubscriberTypeLocalizationRoot } from '../../contexts';
import { localizationKeys, Text, useLocalizations } from '../../customizables';
import { useRouter } from '../../router';
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/components/Checkout/CheckoutForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import { useMemo, useState } from 'react';

import { Card } from '@/ui/elements/Card';
import { useCardState, withCardStateProvider } from '@/ui/elements/contexts';
import { DevModeOverlay } from '@/ui/elements/DevModeNotice';
import { Drawer } from '@/ui/elements/Drawer';
import { LineItems } from '@/ui/elements/LineItems';
import { SegmentedControl } from '@/ui/elements/SegmentedControl';
import { Select, SelectButton, SelectOptionList } from '@/ui/elements/Select';
import { DevModeOverlay } from '@/ui/elements/DevModeNotice';
import { Tooltip } from '@/ui/elements/Tooltip';
import { toNegativeAmount } from '@/ui/utils/billing';
import {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest';

import { isStepReachable, initialState, reduce, type WizardConfig, type WizardState } from '../reducer';
import { initialState, isStepReachable, reduce, type WizardConfig, type WizardState } from '../reducer';
import type { WizardStepDescriptor } from '../types';

const cfg = (descriptors: WizardStepDescriptor[]): WizardConfig => ({ descriptors });
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from 'react';

import {
isStepReachable,
initialState,
isStepReachable,
reduce,
type WizardConfig,
type WizardEvent,
Expand Down
12 changes: 9 additions & 3 deletions packages/ui/src/components/OAuthConsent/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,20 @@ export function getRedirectDisplay(url: string): string {
} catch {
return '';
}
if (!hostname) return '';
if (!hostname) {
return '';
}

// WHATWG URL.hostname includes surrounding brackets for IPv6 literals on some
// platforms; strip them so detection and output formatting are uniform.
const host = hostname.startsWith('[') && hostname.endsWith(']') ? hostname.slice(1, -1) : hostname;

if (IPV4_REGEX.test(host)) return host;
if (host.includes(':')) return `[${host}]`;
if (IPV4_REGEX.test(host)) {
return host;
}
if (host.includes(':')) {
return `[${host}]`;
}
return host.split('.').slice(-2).join('.');
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import { describe, expect, it, vi } from 'vitest';
import { bindCreateFixtures } from '@/test/create-fixtures';
import { render, waitFor } from '@/test/utils';

import { OrganizationList } from '../';
import { createFakeOrganization } from '../../CreateOrganization/__tests__/CreateOrganization.test';
import {
createFakeUserOrganizationInvitation,
createFakeUserOrganizationMembership,
} from '../../OrganizationSwitcher/__tests__/test-utils';
import { OrganizationList } from '../';

const { createFixtures } = bindCreateFixtures('OrganizationList');

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useMemo, type ReactNode } from 'react';
import { useOrganization } from '@clerk/shared/react';
import { type ReactNode, useMemo } from 'react';

import { Animated } from '@/ui/elements/Animated';
import { Tooltip } from '@/ui/elements/Tooltip';
import { isPlanWithPerSeatCosts } from '@/ui/utils/billingPlanSeats';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import { Protect } from '../../common';
import { SubscriberTypeContext } from '../../contexts';
import { Col, descriptors, localizationKeys } from '../../customizables';
import { useTabState } from '../../hooks/useTabState';
import { AccountCredits } from '../AccountCredits';
import { PaymentAttemptsList } from '../PaymentAttempts';
import { PaymentMethods } from '../PaymentMethods';
import { StatementsList } from '../Statements';
import { SubscriptionsList } from '../Subscriptions';
import { AccountCredits } from '../AccountCredits';

const orgTabMap = {
0: 'subscriptions',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,7 @@ export const OrganizationMembers = withCardStateProvider(() => {
sx={theme => ({
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
insetInline: 0,
backgroundColor: theme.colors.$colorBackground,
borderTop: `1px solid ${theme.colors.$borderAlpha100}`,
paddingInline: theme.space.$4,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React from 'react';

import { CreateOrganizationAction } from '@/common/CreateOrganizationAction';
import { Add } from '@/icons';

import { descriptors, localizationKeys } from '../../customizables';
import { UserInvitationSuggestionList } from './UserInvitationSuggestionList';
import type { UserMembershipListProps } from './UserMembershipList';
import { UserMembershipList } from './UserMembershipList';
import { CreateOrganizationAction } from '@/common/CreateOrganizationAction';
import { Add } from '@/icons';

export interface OrganizationActionListProps extends UserMembershipListProps {
onCreateOrganizationClick: React.MouseEventHandler;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import {
} from '@/ui/components/OrganizationSwitcher/__tests__/test-utils';
import { clearFetchCache } from '@/ui/hooks/useFetch';

import { TaskChooseOrganization } from '..';
import type { FakeOrganizationParams } from '../../../../CreateOrganization/__tests__/CreateOrganization.test';
import { TaskChooseOrganization } from '..';

type FakeOrganizationParams = {
id: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ export const SetupMfaStartScreen = withCardStateProvider((props: SetupMfaStartSc
showLogo
sx={t => ({
paddingTop: t.space.$8,
paddingLeft: t.space.$8,
paddingRight: t.space.$8,
paddingInline: t.space.$8,
})}
>
<Header.Title localizationKey={localizationKeys('taskSetupMfa.start.title')} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { describe, expect, it, vi } from 'vitest';
import { bindCreateFixtures } from '@/test/create-fixtures';
import { act, mockWebAuthn, render, screen } from '@/test/utils';

import { SignInFactorOne } from '../SignInFactorOne';
import { SIGN_IN_RESET_PASSWORD_INTENT_PARAM } from '../shared';
import { SignInFactorOne } from '../SignInFactorOne';

const { createFixtures } = bindCreateFixtures('SignIn');

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { ClerkAPIResponseError } from '@clerk/shared/error';
import { CAPTCHA_ELEMENT_ID } from '@clerk/shared/internal/clerk-js/constants';
import { OAUTH_PROVIDERS } from '@clerk/shared/oauth';
import type { SignUpResource } from '@clerk/shared/types';
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/components/SubscriptionDetails/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ import { CardAlert } from '@/ui/elements/Card/CardAlert';
import { useCardState, withCardStateProvider } from '@/ui/elements/contexts';
import { Drawer, useDrawerContext } from '@/ui/elements/Drawer';
import { LineItems } from '@/ui/elements/LineItems';
import { getSeatLimitAndIncludedSeatsLocalizationKey } from '@/ui/utils/billingPlanSeats';
import { isManageableSubscriptionItem } from '@/ui/utils/billingSubscription';
import { handleError } from '@/ui/utils/errorHandler';
import { getSeatLimitAndIncludedSeatsLocalizationKey } from '@/ui/utils/billingPlanSeats';
import { formatDate } from '@/ui/utils/formatDate';

import { SubscriberTypeContext, usePlansContext, useSubscriberTypeContext, useSubscription } from '../../contexts';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ describe('UserButton', () => {
});
});

const { getByText, getByRole, userEvent } = render(
const { getByRole, userEvent } = render(
<UNSAFE_PortalProvider getContainer={getContainer}>
<UserButton />
</UNSAFE_PortalProvider>,
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/components/UserProfile/BillingPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import { Tab, TabPanel, TabPanels, Tabs, TabsList } from '@/ui/elements/Tabs';
import { SubscriberTypeContext } from '../../contexts';
import { Col, descriptors, localizationKeys } from '../../customizables';
import { useTabState } from '../../hooks/useTabState';
import { AccountCredits } from '../AccountCredits';
import { PaymentAttemptsList } from '../PaymentAttempts';
import { PaymentMethods } from '../PaymentMethods';
import { StatementsList } from '../Statements';
import { SubscriptionsList } from '../Subscriptions';
import { AccountCredits } from '../AccountCredits';

const tabMap = {
0: 'subscriptions',
Expand Down
8 changes: 2 additions & 6 deletions packages/ui/src/components/UserProfile/MfaBackupCodeList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,16 +61,12 @@ export const MfaBackupCodeList = (props: MfaBackupCodeListProps) => {
<Text
localizationKey={localizationKeys('userProfile.backupCodePage.title__codelist')}
variant='subtitle'
sx={t => ({
textAlign: 'start',
})}
sx={{ textAlign: 'start' }}
/>
<Text
localizationKey={subtitle}
variant='body'
sx={t => ({
textAlign: 'start',
})}
sx={{ textAlign: 'start' }}
colorScheme='secondary'
/>
</Col>
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/components/UserProfile/MfaSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { useEnvironment } from '../../contexts';
import { Badge, Flex, Icon, localizationKeys, Text } from '../../customizables';
import { Action } from '../../elements/Action';
import { useActionContext } from '../../elements/Action/ActionRoot';
import { AuthApp, ThreeDotsCircle, Mobile } from '../../icons';
import { AuthApp, Mobile, ThreeDotsCircle } from '../../icons';
import type { PropsOfComponent } from '../../styledSystem';
import { MfaBackupCodeCreateScreen, MfaScreen, RemoveMfaPhoneCodeScreen, RemoveMfaTOTPScreen } from './MfaScreens';

Expand Down
Loading
Loading