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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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