From ce0beb4b39229ae6ac47254ab72e630257558ea8 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Wed, 22 Jul 2026 13:56:46 -0400 Subject: [PATCH 1/2] chore(repo): re-enable eslint for @clerk/ui The lint script was renamed to `lint:disabled` in 4da76688c8 so turbo's `lint` task would stop matching it. Rename it back and clear the 117 errors that had accumulated. Notable: - Physical `left`/`right`/`paddingLeft` replaced with logical properties in OrganizationMembers and SetupMfaStartScreen (broken in RTL). - Non-null assertions removed from slot-recipe and BaseRouter. - `no-navigate-useClerk` and `no-unstable-methods` now registered for packages/ui, where the migrated components live. No new violations. - a11y heading/label rules off for mosaic: its `render` prop indirection makes both rules report on every usage. --- .changeset/tall-donuts-repeat.md | 5 ++++ eslint.config.mjs | 30 ++++++++++++++----- .../__tests__/setupClerkTestingToken.test.ts | 1 - packages/ui/package.json | 2 +- packages/ui/src/Components.tsx | 6 ++-- .../common/__tests__/ProviderIcon.test.tsx | 9 +++--- .../APIKeys/__tests__/APIKeyModal.test.tsx | 6 ++-- .../AccountCredits/AccountCredits.tsx | 1 + .../src/components/Checkout/CheckoutForm.tsx | 2 +- .../elements/Wizard/__tests__/reducer.test.ts | 2 +- .../elements/Wizard/useWizardMachine.ts | 2 +- .../ui/src/components/OAuthConsent/utils.ts | 12 ++++++-- .../__tests__/OrganizationList.test.tsx | 2 +- .../OrganizationProfile/MembersActions.tsx | 3 +- .../OrganizationBillingPage.tsx | 2 +- .../OrganizationMembers.tsx | 3 +- .../OtherOrganizationActions.tsx | 5 ++-- .../__tests__/TaskChooseOrganization.test.tsx | 2 +- .../TaskSetupMfa/SetupMfaStartScreen.tsx | 3 +- .../SignIn/__tests__/SignInFactorOne.test.tsx | 2 +- .../SignUp/__tests__/SignUpStart.test.tsx | 1 - .../components/SubscriptionDetails/index.tsx | 2 +- .../UserButton/__tests__/UserButton.test.tsx | 2 +- .../components/UserProfile/BillingPage.tsx | 2 +- .../UserProfile/MfaBackupCodeList.tsx | 8 ++--- .../src/components/UserProfile/MfaSection.tsx | 2 +- .../UserProfile/__tests__/utils.test.ts | 1 + packages/ui/src/contexts/components/Plans.tsx | 2 +- packages/ui/src/elements/Header.tsx | 2 +- .../useCoreOrganizationList.test.tsx | 2 +- .../useEnabledThirdPartyProviders.test.tsx | 1 + packages/ui/src/hooks/usePopover.ts | 1 + packages/ui/src/hooks/useScrollLock.ts | 4 +++ .../__tests__/applyMarkupToNodes.test.tsx | 2 +- .../src/localization/applyMarkupToNodes.tsx | 20 +++++++++---- .../src/mosaic/__tests__/slot-recipe.test.ts | 12 ++++---- .../ui/src/mosaic/__tests__/utils.test.ts | 2 +- packages/ui/src/mosaic/components/heading.tsx | 3 +- packages/ui/src/mosaic/components/input.tsx | 2 +- .../ui/src/mosaic/components/skeleton.tsx | 1 + packages/ui/src/mosaic/components/text.tsx | 3 +- .../__tests__/wizard-migration.test.tsx | 8 +++-- packages/ui/src/mosaic/machine/assign.ts | 2 +- packages/ui/src/mosaic/machine/useMachine.ts | 11 ++++--- ...ganization-profile-delete-section.view.tsx | 7 +---- ...anization-profile-leave-section.machine.ts | 1 + ...rganization-profile-leave-section.view.tsx | 7 +---- packages/ui/src/mosaic/primitives/box.tsx | 2 +- packages/ui/src/mosaic/primitives/dialog.tsx | 2 +- packages/ui/src/mosaic/primitives/tabs.tsx | 2 +- packages/ui/src/mosaic/slot-recipe.ts | 27 +++++++++-------- packages/ui/src/mosaic/utils/context.ts | 8 +++-- packages/ui/src/router/BaseRouter.tsx | 12 ++++---- packages/ui/src/server.ts | 3 +- .../detectClerkStylesheetUsage.test.ts | 2 +- packages/ui/src/utils/billingPlanSeats.ts | 12 ++++++-- .../src/utils/detectClerkStylesheetUsage.ts | 2 +- .../warnAboutCustomizationWithoutPinning.ts | 2 +- packages/ui/tsconfig.json | 2 ++ 59 files changed, 168 insertions(+), 119 deletions(-) create mode 100644 .changeset/tall-donuts-repeat.md diff --git a/.changeset/tall-donuts-repeat.md b/.changeset/tall-donuts-repeat.md new file mode 100644 index 00000000000..1de2f8cb06e --- /dev/null +++ b/.changeset/tall-donuts-repeat.md @@ -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. diff --git a/eslint.config.mjs b/eslint.config.mjs index 5598e5426cf..67c4ccab1d3 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -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, }, @@ -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', @@ -534,8 +527,12 @@ 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', }, }, { @@ -543,6 +540,10 @@ export default tseslint.config([ files: ['packages/ui/src/mosaic/**/*'], ignores: ['packages/ui/src/mosaic/utils.ts', 'packages/ui/src/mosaic/__tests__/**'], rules: { + // Mosaic renders elements through `render={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', { @@ -566,6 +567,19 @@ export default tseslint.config([ files: ['packages/*/src/**/*.test.{ts,tsx}'], rules: { '@typescript-eslint/unbound-method': 'off', + // vitest's `importOriginal()` 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', }, }, { diff --git a/packages/testing/src/playwright/__tests__/setupClerkTestingToken.test.ts b/packages/testing/src/playwright/__tests__/setupClerkTestingToken.test.ts index 4f9a5bff6b9..b6b86d8d4fc 100644 --- a/packages/testing/src/playwright/__tests__/setupClerkTestingToken.test.ts +++ b/packages/testing/src/playwright/__tests__/setupClerkTestingToken.test.ts @@ -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( diff --git a/packages/ui/package.json b/packages/ui/package.json index ebbc7f6e00d..85eee2d3ac1 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -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", diff --git a/packages/ui/src/Components.tsx b/packages/ui/src/Components.tsx index 9b290c5ca9a..d28d93d83ea 100644 --- a/packages/ui/src/Components.tsx +++ b/packages/ui/src/Components.tsx @@ -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'; diff --git a/packages/ui/src/common/__tests__/ProviderIcon.test.tsx b/packages/ui/src/common/__tests__/ProviderIcon.test.tsx index c4e85fa618e..370016475e2 100644 --- a/packages/ui/src/common/__tests__/ProviderIcon.test.tsx +++ b/packages/ui/src/common/__tests__/ProviderIcon.test.tsx @@ -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({ @@ -225,7 +224,7 @@ describe('ProviderIcon', () => { id='google' iconUrl={null} name='Google' - isLoading={true} + isLoading />, { wrapper }, ); @@ -242,7 +241,7 @@ describe('ProviderIcon', () => { id='google' iconUrl={null} name='Google' - isDisabled={true} + isDisabled />, { wrapper }, ); @@ -261,7 +260,7 @@ describe('ProviderIcon', () => { id='google' iconUrl='https://example.com/google-icon.svg' name='Google' - isLoading={true} + isLoading />, { wrapper }, ); @@ -279,7 +278,7 @@ describe('ProviderIcon', () => { id='google' iconUrl='https://example.com/google-icon.svg' name='Google' - isDisabled={true} + isDisabled />, { wrapper }, ); diff --git a/packages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsx b/packages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsx index 00904507c08..c2789bdf8d6 100644 --- a/packages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsx +++ b/packages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsx @@ -15,7 +15,7 @@ describe('APIKeyModal modalRoot behavior', () => { modalRoot.current = container; document.body.appendChild(container); - const { container: testContainer } = render( + render( { modalRoot.current = container; document.body.appendChild(container); - const { container: testContainer } = render( + render( { const getContainer = () => container2; - const { container: testContainer } = render( + render( diff --git a/packages/ui/src/components/AccountCredits/AccountCredits.tsx b/packages/ui/src/components/AccountCredits/AccountCredits.tsx index 329d5571b06..ca238e801ca 100644 --- a/packages/ui/src/components/AccountCredits/AccountCredits.tsx +++ b/packages/ui/src/components/AccountCredits/AccountCredits.tsx @@ -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'; diff --git a/packages/ui/src/components/Checkout/CheckoutForm.tsx b/packages/ui/src/components/Checkout/CheckoutForm.tsx index 05c238ac8b7..00e23e79ff1 100644 --- a/packages/ui/src/components/Checkout/CheckoutForm.tsx +++ b/packages/ui/src/components/Checkout/CheckoutForm.tsx @@ -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 { diff --git a/packages/ui/src/components/ConfigureSSO/elements/Wizard/__tests__/reducer.test.ts b/packages/ui/src/components/ConfigureSSO/elements/Wizard/__tests__/reducer.test.ts index 00095f16437..88267d22e57 100644 --- a/packages/ui/src/components/ConfigureSSO/elements/Wizard/__tests__/reducer.test.ts +++ b/packages/ui/src/components/ConfigureSSO/elements/Wizard/__tests__/reducer.test.ts @@ -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 }); diff --git a/packages/ui/src/components/ConfigureSSO/elements/Wizard/useWizardMachine.ts b/packages/ui/src/components/ConfigureSSO/elements/Wizard/useWizardMachine.ts index c60f1f1cb7a..bd8699e70ef 100644 --- a/packages/ui/src/components/ConfigureSSO/elements/Wizard/useWizardMachine.ts +++ b/packages/ui/src/components/ConfigureSSO/elements/Wizard/useWizardMachine.ts @@ -1,8 +1,8 @@ import React from 'react'; import { - isStepReachable, initialState, + isStepReachable, reduce, type WizardConfig, type WizardEvent, diff --git a/packages/ui/src/components/OAuthConsent/utils.ts b/packages/ui/src/components/OAuthConsent/utils.ts index ea97f3bf115..f511c600df8 100644 --- a/packages/ui/src/components/OAuthConsent/utils.ts +++ b/packages/ui/src/components/OAuthConsent/utils.ts @@ -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('.'); } diff --git a/packages/ui/src/components/OrganizationList/__tests__/OrganizationList.test.tsx b/packages/ui/src/components/OrganizationList/__tests__/OrganizationList.test.tsx index a355535300b..60a48c3e5ad 100644 --- a/packages/ui/src/components/OrganizationList/__tests__/OrganizationList.test.tsx +++ b/packages/ui/src/components/OrganizationList/__tests__/OrganizationList.test.tsx @@ -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'); diff --git a/packages/ui/src/components/OrganizationProfile/MembersActions.tsx b/packages/ui/src/components/OrganizationProfile/MembersActions.tsx index c608eb031c7..6ec6daeea5f 100644 --- a/packages/ui/src/components/OrganizationProfile/MembersActions.tsx +++ b/packages/ui/src/components/OrganizationProfile/MembersActions.tsx @@ -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'; diff --git a/packages/ui/src/components/OrganizationProfile/OrganizationBillingPage.tsx b/packages/ui/src/components/OrganizationProfile/OrganizationBillingPage.tsx index f47d28b8aad..18e5aaa0568 100644 --- a/packages/ui/src/components/OrganizationProfile/OrganizationBillingPage.tsx +++ b/packages/ui/src/components/OrganizationProfile/OrganizationBillingPage.tsx @@ -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', diff --git a/packages/ui/src/components/OrganizationProfile/OrganizationMembers.tsx b/packages/ui/src/components/OrganizationProfile/OrganizationMembers.tsx index 0b103b559e0..d1bf7ec69ad 100644 --- a/packages/ui/src/components/OrganizationProfile/OrganizationMembers.tsx +++ b/packages/ui/src/components/OrganizationProfile/OrganizationMembers.tsx @@ -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, diff --git a/packages/ui/src/components/OrganizationSwitcher/OtherOrganizationActions.tsx b/packages/ui/src/components/OrganizationSwitcher/OtherOrganizationActions.tsx index 5d3fbd798d9..4c952b9fae8 100644 --- a/packages/ui/src/components/OrganizationSwitcher/OtherOrganizationActions.tsx +++ b/packages/ui/src/components/OrganizationSwitcher/OtherOrganizationActions.tsx @@ -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; diff --git a/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/__tests__/TaskChooseOrganization.test.tsx b/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/__tests__/TaskChooseOrganization.test.tsx index 8af236fcf18..c910527f0be 100644 --- a/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/__tests__/TaskChooseOrganization.test.tsx +++ b/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/__tests__/TaskChooseOrganization.test.tsx @@ -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; diff --git a/packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/SetupMfaStartScreen.tsx b/packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/SetupMfaStartScreen.tsx index 779819b5665..d12190b0329 100644 --- a/packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/SetupMfaStartScreen.tsx +++ b/packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/SetupMfaStartScreen.tsx @@ -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, })} > diff --git a/packages/ui/src/components/SignIn/__tests__/SignInFactorOne.test.tsx b/packages/ui/src/components/SignIn/__tests__/SignInFactorOne.test.tsx index 614f98fee40..57ba4fb7448 100644 --- a/packages/ui/src/components/SignIn/__tests__/SignInFactorOne.test.tsx +++ b/packages/ui/src/components/SignIn/__tests__/SignInFactorOne.test.tsx @@ -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'); diff --git a/packages/ui/src/components/SignUp/__tests__/SignUpStart.test.tsx b/packages/ui/src/components/SignUp/__tests__/SignUpStart.test.tsx index a4984f49263..73807b7968d 100644 --- a/packages/ui/src/components/SignUp/__tests__/SignUpStart.test.tsx +++ b/packages/ui/src/components/SignUp/__tests__/SignUpStart.test.tsx @@ -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'; diff --git a/packages/ui/src/components/SubscriptionDetails/index.tsx b/packages/ui/src/components/SubscriptionDetails/index.tsx index efeb7c85774..3fb2a352bcf 100644 --- a/packages/ui/src/components/SubscriptionDetails/index.tsx +++ b/packages/ui/src/components/SubscriptionDetails/index.tsx @@ -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'; diff --git a/packages/ui/src/components/UserButton/__tests__/UserButton.test.tsx b/packages/ui/src/components/UserButton/__tests__/UserButton.test.tsx index 8af062ee274..06d19655215 100644 --- a/packages/ui/src/components/UserButton/__tests__/UserButton.test.tsx +++ b/packages/ui/src/components/UserButton/__tests__/UserButton.test.tsx @@ -103,7 +103,7 @@ describe('UserButton', () => { }); }); - const { getByText, getByRole, userEvent } = render( + const { getByRole, userEvent } = render( , diff --git a/packages/ui/src/components/UserProfile/BillingPage.tsx b/packages/ui/src/components/UserProfile/BillingPage.tsx index 3d555676142..fa3d059dad9 100644 --- a/packages/ui/src/components/UserProfile/BillingPage.tsx +++ b/packages/ui/src/components/UserProfile/BillingPage.tsx @@ -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', diff --git a/packages/ui/src/components/UserProfile/MfaBackupCodeList.tsx b/packages/ui/src/components/UserProfile/MfaBackupCodeList.tsx index 557897eef9a..5cc29ebe135 100644 --- a/packages/ui/src/components/UserProfile/MfaBackupCodeList.tsx +++ b/packages/ui/src/components/UserProfile/MfaBackupCodeList.tsx @@ -61,16 +61,12 @@ export const MfaBackupCodeList = (props: MfaBackupCodeListProps) => { ({ - textAlign: 'start', - })} + sx={{ textAlign: 'start' }} /> ({ - textAlign: 'start', - })} + sx={{ textAlign: 'start' }} colorScheme='secondary' /> diff --git a/packages/ui/src/components/UserProfile/MfaSection.tsx b/packages/ui/src/components/UserProfile/MfaSection.tsx index cbb1bfbd17b..9a2b22a3dee 100644 --- a/packages/ui/src/components/UserProfile/MfaSection.tsx +++ b/packages/ui/src/components/UserProfile/MfaSection.tsx @@ -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'; diff --git a/packages/ui/src/components/UserProfile/__tests__/utils.test.ts b/packages/ui/src/components/UserProfile/__tests__/utils.test.ts index aeb1605ad5b..95d67d29ce1 100644 --- a/packages/ui/src/components/UserProfile/__tests__/utils.test.ts +++ b/packages/ui/src/components/UserProfile/__tests__/utils.test.ts @@ -2,6 +2,7 @@ import type { VerificationJSON } from '@clerk/shared/types'; import { describe, expect, it } from 'vitest'; import { EmailAddress, PhoneNumber } from '@/core/resources'; + import { sortIdentificationBasedOnVerification } from '../utils'; describe('UserProfile utils', () => { diff --git a/packages/ui/src/contexts/components/Plans.tsx b/packages/ui/src/contexts/components/Plans.tsx index 79a43c29d12..4d17c38455f 100644 --- a/packages/ui/src/contexts/components/Plans.tsx +++ b/packages/ui/src/contexts/components/Plans.tsx @@ -1,10 +1,10 @@ import { - __internal_useCreditBalanceQuery, __experimental_usePaymentAttempts, __experimental_usePaymentMethods, __experimental_usePlans, __experimental_useStatements, __experimental_useSubscription, + __internal_useCreditBalanceQuery, __internal_useCreditHistoryQuery, __internal_useOrganizationBase, useClerk, diff --git a/packages/ui/src/elements/Header.tsx b/packages/ui/src/elements/Header.tsx index efb92811df8..93b7346a4cb 100644 --- a/packages/ui/src/elements/Header.tsx +++ b/packages/ui/src/elements/Header.tsx @@ -8,9 +8,9 @@ import { Heading, Icon, Link, + type LocalizationKey, Text, useAppearance, - type LocalizationKey, } from '../customizables'; import { ArrowLeft } from '../icons'; import type { PropsOfComponent, ThemableCssProp } from '../styledSystem'; diff --git a/packages/ui/src/hooks/__tests__/useCoreOrganizationList.test.tsx b/packages/ui/src/hooks/__tests__/useCoreOrganizationList.test.tsx index dbe8147240e..6d2155bcabd 100644 --- a/packages/ui/src/hooks/__tests__/useCoreOrganizationList.test.tsx +++ b/packages/ui/src/hooks/__tests__/useCoreOrganizationList.test.tsx @@ -3,8 +3,8 @@ import { createDeferredPromise } from '@clerk/shared/utils/index'; import { describe, expect, it } from 'vitest'; import { bindCreateFixtures } from '@/test/create-fixtures'; - import { act, renderHook, waitFor } from '@/test/utils'; + import { createFakeUserOrganizationInvitation, createFakeUserOrganizationMembership, diff --git a/packages/ui/src/hooks/__tests__/useEnabledThirdPartyProviders.test.tsx b/packages/ui/src/hooks/__tests__/useEnabledThirdPartyProviders.test.tsx index b9a013d16d9..84d94bd6e12 100644 --- a/packages/ui/src/hooks/__tests__/useEnabledThirdPartyProviders.test.tsx +++ b/packages/ui/src/hooks/__tests__/useEnabledThirdPartyProviders.test.tsx @@ -3,6 +3,7 @@ import { renderHook } from '@testing-library/react'; import { describe, expect, it } from 'vitest'; import { bindCreateFixtures } from '@/test/utils'; + import { useEnabledThirdPartyProviders } from '../useEnabledThirdPartyProviders'; const { createFixtures } = bindCreateFixtures('SignUp'); diff --git a/packages/ui/src/hooks/usePopover.ts b/packages/ui/src/hooks/usePopover.ts index 805559111ac..fb89dc5eb87 100644 --- a/packages/ui/src/hooks/usePopover.ts +++ b/packages/ui/src/hooks/usePopover.ts @@ -114,6 +114,7 @@ export const usePopover = (props: UsePopoverProps = {}): UsePopoverReturn => { nodeId, close, isOpen, + // eslint-disable-next-line custom-rules/no-physical-css-properties -- floating-ui computes physical x/y for the chosen strategy styles: { position: strategy, top: y ?? 0, left: x ?? 0 }, context, getReferenceProps, diff --git a/packages/ui/src/hooks/useScrollLock.ts b/packages/ui/src/hooks/useScrollLock.ts index 0c637d32c7f..99156b804cd 100644 --- a/packages/ui/src/hooks/useScrollLock.ts +++ b/packages/ui/src/hooks/useScrollLock.ts @@ -41,7 +41,9 @@ function enableScrollLock() { Object.assign(bodyStyle, { position: 'fixed', top: `${-(scrollY - Math.floor(offsetTop))}px`, + // eslint-disable-next-line custom-rules/no-physical-css-properties -- mirrors physical viewport scroll offsets, which are not direction-aware left: `${-(scrollX - Math.floor(offsetLeft))}px`, + // eslint-disable-next-line custom-rules/no-physical-css-properties -- pairs with the physical `left` above right: '0', }); } @@ -56,7 +58,9 @@ function enableScrollLock() { Object.assign(bodyStyle, { position: '', top: '', + // eslint-disable-next-line custom-rules/no-physical-css-properties -- resets the physical properties set above left: '', + // eslint-disable-next-line custom-rules/no-physical-css-properties -- resets the physical properties set above right: '', }); window.scrollTo(scrollX, scrollY); diff --git a/packages/ui/src/localization/__tests__/applyMarkupToNodes.test.tsx b/packages/ui/src/localization/__tests__/applyMarkupToNodes.test.tsx index 04b1c13238b..1ff28954946 100644 --- a/packages/ui/src/localization/__tests__/applyMarkupToNodes.test.tsx +++ b/packages/ui/src/localization/__tests__/applyMarkupToNodes.test.tsx @@ -2,8 +2,8 @@ import type { ReactNode } from 'react'; import { renderToStaticMarkup } from 'react-dom/server'; import { describe, expect, it } from 'vitest'; -import type { Tokens } from '../applyTokensToString'; import { applyMarkupAndTokens, stripMarkup } from '../applyMarkupToNodes'; +import type { Tokens } from '../applyTokensToString'; const html = (node: ReactNode) => renderToStaticMarkup(node as any); diff --git a/packages/ui/src/localization/applyMarkupToNodes.tsx b/packages/ui/src/localization/applyMarkupToNodes.tsx index b17ad89179d..70eb047ff72 100644 --- a/packages/ui/src/localization/applyMarkupToNodes.tsx +++ b/packages/ui/src/localization/applyMarkupToNodes.tsx @@ -14,7 +14,9 @@ const TAG_RE = /<(\/?)(bold)>/g; export const stripMarkup = (s: string): string => s.replace(/<\/?(bold)>/g, ''); export const applyMarkupAndTokens = (template: string | undefined, tokens: Tokens): ReactNode => { - if (!template) return ''; + if (!template) { + return ''; + } const substitute = (s: string) => (s.includes('{{') ? applyTokensToString(s, tokens) : s); if (!template.includes('<')) { return substitute(template); @@ -29,7 +31,9 @@ export const applyMarkupAndTokens = (template: string | undefined, tokens: Token while ((match = TAG_RE.exec(template)) !== null) { const [full, slash, tag] = match; const text = template.slice(cursor, match.index); - if (text) stack[stack.length - 1].children.push(substitute(text)); + if (text) { + stack[stack.length - 1].children.push(substitute(text)); + } cursor = match.index + full.length; if (!slash) { @@ -51,10 +55,16 @@ export const applyMarkupAndTokens = (template: string | undefined, tokens: Token } const tail = template.slice(cursor); - if (tail) stack[0].children.push(substitute(tail)); + if (tail) { + stack[0].children.push(substitute(tail)); + } const out = stack[0].children; - if (out.length === 0) return ''; - if (out.length === 1) return out[0]; + if (out.length === 0) { + return ''; + } + if (out.length === 1) { + return out[0]; + } return createElement(Fragment, null, ...out); }; diff --git a/packages/ui/src/mosaic/__tests__/slot-recipe.test.ts b/packages/ui/src/mosaic/__tests__/slot-recipe.test.ts index 24758f8cd94..438780d75d6 100644 --- a/packages/ui/src/mosaic/__tests__/slot-recipe.test.ts +++ b/packages/ui/src/mosaic/__tests__/slot-recipe.test.ts @@ -236,18 +236,18 @@ describe('useSlot / slot sugar', () => { describe('type inference', () => { it('infers variant prop names and values', () => { // Type-only assertions — never executed, validated by `tsc` during type-check. - () => { + function _typeAssertions() { useRecipe(buttonRecipe, { variants: { color: 'danger', size: 'sm', loading: true } }); // @ts-expect-error 'nope' is not a valid `color` useRecipe(buttonRecipe, { variants: { color: 'nope' } }); // @ts-expect-error `loading` is a boolean variant, not a string useRecipe(buttonRecipe, { variants: { loading: 'true' } }); - }; + } expect(true).toBe(true); }); it('defaultVariants only accepts valid variant keys and values', () => { - () => { + function _typeAssertions() { defineSlotRecipe({ slot: 'button', variants: { color: { primary: {}, danger: {} } }, @@ -260,12 +260,12 @@ describe('type inference', () => { // @ts-expect-error 'unknown' is not a declared variant axis defaultVariants: { unknown: 'primary' }, }); - }; + } expect(true).toBe(true); }); it('compoundVariants entries only accept valid variant keys and values', () => { - () => { + function _typeAssertions() { defineSlotRecipe({ slot: 'button', variants: { color: { primary: {}, danger: {} }, size: { sm: {}, md: {} } }, @@ -282,7 +282,7 @@ describe('type inference', () => { { unknown: 'primary', css: {} }, ], }); - }; + } expect(true).toBe(true); }); }); diff --git a/packages/ui/src/mosaic/__tests__/utils.test.ts b/packages/ui/src/mosaic/__tests__/utils.test.ts index a5c45258128..fd655819edf 100644 --- a/packages/ui/src/mosaic/__tests__/utils.test.ts +++ b/packages/ui/src/mosaic/__tests__/utils.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest'; -import { defaultMosaicVariables, resolveVariables } from '../variables'; import { alpha } from '../utils'; +import { defaultMosaicVariables, resolveVariables } from '../variables'; describe('alpha', () => { it('produces a color-mix expression', () => { diff --git a/packages/ui/src/mosaic/components/heading.tsx b/packages/ui/src/mosaic/components/heading.tsx index 8c473b08e52..d09c777b064 100644 --- a/packages/ui/src/mosaic/components/heading.tsx +++ b/packages/ui/src/mosaic/components/heading.tsx @@ -1,7 +1,6 @@ -import React from 'react'; - import type { ComponentProps } from '@clerk/headless/utils'; import { renderElement } from '@clerk/headless/utils'; +import React from 'react'; import type { RecipeVariantProps } from '../slot-recipe'; import { defineSlotRecipe, useRecipe } from '../slot-recipe'; diff --git a/packages/ui/src/mosaic/components/input.tsx b/packages/ui/src/mosaic/components/input.tsx index d3d1686bd97..146deb77ad9 100644 --- a/packages/ui/src/mosaic/components/input.tsx +++ b/packages/ui/src/mosaic/components/input.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import { defineSlotRecipe, useRecipe } from '../slot-recipe'; import type { RecipeVariantProps } from '../slot-recipe'; +import { defineSlotRecipe, useRecipe } from '../slot-recipe'; export const inputRecipe = defineSlotRecipe(theme => ({ slot: 'input', diff --git a/packages/ui/src/mosaic/components/skeleton.tsx b/packages/ui/src/mosaic/components/skeleton.tsx index 274c999cd6e..0e3029e31db 100644 --- a/packages/ui/src/mosaic/components/skeleton.tsx +++ b/packages/ui/src/mosaic/components/skeleton.tsx @@ -1,3 +1,4 @@ +// eslint-disable-next-line no-restricted-imports import { keyframes } from '@emotion/react'; import type { ReactNode } from 'react'; diff --git a/packages/ui/src/mosaic/components/text.tsx b/packages/ui/src/mosaic/components/text.tsx index e34421ec358..45d5a6c80d4 100644 --- a/packages/ui/src/mosaic/components/text.tsx +++ b/packages/ui/src/mosaic/components/text.tsx @@ -1,7 +1,6 @@ -import React from 'react'; - import type { ComponentProps } from '@clerk/headless/utils'; import { renderElement } from '@clerk/headless/utils'; +import React from 'react'; import type { RecipeVariantProps } from '../slot-recipe'; import { defineSlotRecipe, useRecipe } from '../slot-recipe'; diff --git a/packages/ui/src/mosaic/machine/__tests__/wizard-migration.test.tsx b/packages/ui/src/mosaic/machine/__tests__/wizard-migration.test.tsx index 06286b1081e..516d20c3263 100644 --- a/packages/ui/src/mosaic/machine/__tests__/wizard-migration.test.tsx +++ b/packages/ui/src/mosaic/machine/__tests__/wizard-migration.test.tsx @@ -94,9 +94,13 @@ function createWizardMachine(descriptors: StepDescriptor[]): StateMachine { - if (descriptors.length === 0) return ''; + if (descriptors.length === 0) { + return ''; + } let i = 0; - while (i + 1 < descriptors.length && guardHolds(descriptors[i + 1])) i++; + while (i + 1 < descriptors.length && guardHolds(descriptors[i + 1])) { + i++; + } return descriptors[i].id; }; diff --git a/packages/ui/src/mosaic/machine/assign.ts b/packages/ui/src/mosaic/machine/assign.ts index 9822f37d416..a59754212ba 100644 --- a/packages/ui/src/mosaic/machine/assign.ts +++ b/packages/ui/src/mosaic/machine/assign.ts @@ -1,5 +1,5 @@ -import { ASSIGN } from './types'; import type { AssignAction, EventObject } from './types'; +import { ASSIGN } from './types'; /** * Context-update action creator. The returned object is recognised by the diff --git a/packages/ui/src/mosaic/machine/useMachine.ts b/packages/ui/src/mosaic/machine/useMachine.ts index 065677726df..df2b11e7fb9 100644 --- a/packages/ui/src/mosaic/machine/useMachine.ts +++ b/packages/ui/src/mosaic/machine/useMachine.ts @@ -63,7 +63,9 @@ export function useMachine( const onDoneRef = useRef(options?.onDone); onDoneRef.current = options?.onDone; useEffect(() => { - snapshot.status === 'done' && onDoneRef.current?.(); + if (snapshot.status === 'done') { + onDoneRef.current?.(); + } }, [snapshot.status]); return [snapshot, actor.send, actor]; @@ -126,9 +128,10 @@ export function useMachineLogger(label: string, snapsho const previous = prevValue.current; prevValue.current = current; - previous === undefined && console.log(`[${label}] ${current}`, snapshot.context); - previous !== undefined && - previous !== current && + if (previous === undefined) { + console.log(`[${label}] ${current}`, snapshot.context); + } else if (previous !== current) { console.log(`[${label}] ${previous} → ${current}`, snapshot.context); + } }); } diff --git a/packages/ui/src/mosaic/organization/organization-profile-delete-section.view.tsx b/packages/ui/src/mosaic/organization/organization-profile-delete-section.view.tsx index 035f97cd8de..d37a595a835 100644 --- a/packages/ui/src/mosaic/organization/organization-profile-delete-section.view.tsx +++ b/packages/ui/src/mosaic/organization/organization-profile-delete-section.view.tsx @@ -21,12 +21,7 @@ export function OrganizationProfileDeleteSectionView({ const isDeleting = snapshot.value === 'deleting'; return ( - ({ - width: '100%', - containerType: 'inline-size', - })} - > + ({ display: 'flex', diff --git a/packages/ui/src/mosaic/organization/organization-profile-leave-section.machine.ts b/packages/ui/src/mosaic/organization/organization-profile-leave-section.machine.ts index 9a2014dc5dc..f7b9040b8a1 100644 --- a/packages/ui/src/mosaic/organization/organization-profile-leave-section.machine.ts +++ b/packages/ui/src/mosaic/organization/organization-profile-leave-section.machine.ts @@ -55,6 +55,7 @@ export const organizationProfileLeaveSectionMachine = createMachine({ }, }), }, + // eslint-disable-next-line custom-rules/no-physical-css-properties -- machine state name ("has left the org"), not CSS left: { type: 'final' }, }, }); diff --git a/packages/ui/src/mosaic/organization/organization-profile-leave-section.view.tsx b/packages/ui/src/mosaic/organization/organization-profile-leave-section.view.tsx index 807c2f2b4eb..1fb132b241c 100644 --- a/packages/ui/src/mosaic/organization/organization-profile-leave-section.view.tsx +++ b/packages/ui/src/mosaic/organization/organization-profile-leave-section.view.tsx @@ -21,12 +21,7 @@ export function OrganizationProfileLeaveSectionView({ const isLeaving = snapshot.value === 'leaving'; return ( - ({ - width: '100%', - containerType: 'inline-size', - })} - > + ({ display: 'flex', diff --git a/packages/ui/src/mosaic/primitives/box.tsx b/packages/ui/src/mosaic/primitives/box.tsx index 5f07dc5d0d8..fda9910749e 100644 --- a/packages/ui/src/mosaic/primitives/box.tsx +++ b/packages/ui/src/mosaic/primitives/box.tsx @@ -1,4 +1,4 @@ -import { type ComponentProps, type RenderProp, renderElement } from '@clerk/headless/utils'; +import { type ComponentProps, renderElement, type RenderProp } from '@clerk/headless/utils'; import React from 'react'; /** diff --git a/packages/ui/src/mosaic/primitives/dialog.tsx b/packages/ui/src/mosaic/primitives/dialog.tsx index 9c4ed985c8e..bb7d0f9cec4 100644 --- a/packages/ui/src/mosaic/primitives/dialog.tsx +++ b/packages/ui/src/mosaic/primitives/dialog.tsx @@ -1,5 +1,5 @@ -import { Dialog as HeadlessDialog } from '@clerk/headless/dialog'; import type { DialogPortalProps, DialogProps } from '@clerk/headless/dialog'; +import { Dialog as HeadlessDialog } from '@clerk/headless/dialog'; import type { FunctionComponent } from 'react'; import { withMosaicSlot } from './withMosaicSlot'; diff --git a/packages/ui/src/mosaic/primitives/tabs.tsx b/packages/ui/src/mosaic/primitives/tabs.tsx index 69cb2ff21b5..ee19d168a9d 100644 --- a/packages/ui/src/mosaic/primitives/tabs.tsx +++ b/packages/ui/src/mosaic/primitives/tabs.tsx @@ -1,5 +1,5 @@ -import { Tabs as HeadlessTabs } from '@clerk/headless/tabs'; import type { TabsProps } from '@clerk/headless/tabs'; +import { Tabs as HeadlessTabs } from '@clerk/headless/tabs'; import type { FunctionComponent } from 'react'; import { withMosaicSlot } from './withMosaicSlot'; diff --git a/packages/ui/src/mosaic/slot-recipe.ts b/packages/ui/src/mosaic/slot-recipe.ts index 74880262085..4d89f02e519 100644 --- a/packages/ui/src/mosaic/slot-recipe.ts +++ b/packages/ui/src/mosaic/slot-recipe.ts @@ -140,18 +140,21 @@ export function defineSlotRecipe SlotRecipeConfig), ): SlotRecipe { - const cache = typeof configOrFn === 'function' ? new WeakMap() : null; - const resolveConfig = (theme: MosaicTheme): SlotRecipeConfig => { - if (typeof configOrFn !== 'function') { - return configOrFn; - } - let cached = cache!.get(theme); - if (!cached) { - cached = configOrFn(theme); - cache!.set(theme, cached); - } - return cached; - }; + let resolveConfig: (theme: MosaicTheme) => SlotRecipeConfig; + if (typeof configOrFn === 'function') { + const buildConfig = configOrFn; + const cache = new WeakMap(); + resolveConfig = theme => { + let cached = cache.get(theme); + if (!cached) { + cached = buildConfig(theme); + cache.set(theme, cached); + } + return cached; + }; + } else { + resolveConfig = () => configOrFn; + } // Slot identity is theme-independent, so probe once against the default theme. const probe = resolveConfig(resolveVariables(defaultMosaicVariables)); diff --git a/packages/ui/src/mosaic/utils/context.ts b/packages/ui/src/mosaic/utils/context.ts index 94e17b9c693..0859ac2d272 100644 --- a/packages/ui/src/mosaic/utils/context.ts +++ b/packages/ui/src/mosaic/utils/context.ts @@ -2,10 +2,14 @@ import React from 'react'; export function useContextProps(props: T, context: React.Context | null>): T { const ctx = React.useContext(context); - if (!ctx) return props; + if (!ctx) { + return props; + } const result = { ...ctx } as T; for (const key of Object.keys(props) as (keyof T)[]) { - if (props[key] !== undefined) result[key] = props[key]; + if (props[key] !== undefined) { + result[key] = props[key]; + } } return result; } diff --git a/packages/ui/src/router/BaseRouter.tsx b/packages/ui/src/router/BaseRouter.tsx index 38ad113b559..8d401d38a4e 100644 --- a/packages/ui/src/router/BaseRouter.tsx +++ b/packages/ui/src/router/BaseRouter.tsx @@ -1,8 +1,8 @@ import { getQueryParams, stringifyQueryParams } from '@clerk/shared/internal/clerk-js/querystring'; import { trimTrailingSlash } from '@clerk/shared/internal/clerk-js/url'; import { useClerk } from '@clerk/shared/react'; -import { withLeadingSlash } from '@clerk/shared/url'; import type { NavigateOptions } from '@clerk/shared/types'; +import { withLeadingSlash } from '@clerk/shared/url'; import React from 'react'; import { flushSync } from 'react-dom'; @@ -40,9 +40,10 @@ function ensurePushStatePatched(): void { if (originalPushState) { return; } - originalPushState = history.pushState.bind(history); + const original = history.pushState.bind(history); + originalPushState = original; history.pushState = (...args: Parameters) => { - originalPushState!(...args); + original(...args); pushStateSubscribers.forEach(fn => fn()); }; } @@ -51,9 +52,10 @@ function ensureReplaceStatePatched(): void { if (originalReplaceState) { return; } - originalReplaceState = history.replaceState.bind(history); + const original = history.replaceState.bind(history); + originalReplaceState = original; history.replaceState = (...args: Parameters) => { - originalReplaceState!(...args); + original(...args); replaceStateSubscribers.forEach(fn => fn()); }; } diff --git a/packages/ui/src/server.ts b/packages/ui/src/server.ts index f7281c15251..59db2171dda 100644 --- a/packages/ui/src/server.ts +++ b/packages/ui/src/server.ts @@ -1,9 +1,8 @@ +import { ClerkUI } from './entry'; import type { Ui } from './internal'; import { UI_BRAND } from './internal'; import type { Appearance } from './internal/appearance'; -import { ClerkUI } from './entry'; - declare const PACKAGE_VERSION: string; /** diff --git a/packages/ui/src/utils/__tests__/detectClerkStylesheetUsage.test.ts b/packages/ui/src/utils/__tests__/detectClerkStylesheetUsage.test.ts index 1b194fc4ec2..9b8a6158e23 100644 --- a/packages/ui/src/utils/__tests__/detectClerkStylesheetUsage.test.ts +++ b/packages/ui/src/utils/__tests__/detectClerkStylesheetUsage.test.ts @@ -1,4 +1,4 @@ -import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; +import { afterEach, beforeEach, describe, expect, test } from 'vitest'; import { detectStructuralClerkCss } from '../detectClerkStylesheetUsage'; diff --git a/packages/ui/src/utils/billingPlanSeats.ts b/packages/ui/src/utils/billingPlanSeats.ts index 876999c9797..57df0e7aba6 100644 --- a/packages/ui/src/utils/billingPlanSeats.ts +++ b/packages/ui/src/utils/billingPlanSeats.ts @@ -61,13 +61,19 @@ export type SeatChargeSummary = { * Returns `{ totalSeats, included, paidTier }` otherwise. */ export const summarizeSeatCharges = (seatsTotal: BillingPerUnitTotal | null | undefined): SeatChargeSummary | null => { - if (!seatsTotal) return null; + if (!seatsTotal) { + return null; + } const paidTier = seatsTotal.tiers.find(tier => tier.feePerBlock.amount > 0); - if (!paidTier) return null; + if (!paidTier) { + return null; + } let totalSeats = 0; let included = 0; for (const tier of seatsTotal.tiers) { - if (tier.quantity === null) continue; + if (tier.quantity === null) { + continue; + } totalSeats += tier.quantity; if (tier.feePerBlock.amount === 0) { included += tier.quantity; diff --git a/packages/ui/src/utils/detectClerkStylesheetUsage.ts b/packages/ui/src/utils/detectClerkStylesheetUsage.ts index 04a4839c2ce..7d5cc1e0ccd 100644 --- a/packages/ui/src/utils/detectClerkStylesheetUsage.ts +++ b/packages/ui/src/utils/detectClerkStylesheetUsage.ts @@ -36,7 +36,7 @@ function splitSelectorList(selectorText: string): string[] { function hasClerkWithAdjacency(selector: string): boolean { // Pattern: .cl-class followed by combinator/space and another selector // Or: another selector followed by combinator/space and .cl-class - const clerkWithDescendant = /\.cl-[A-Za-z0-9_-]+[\s>+~]+[.#\w\[:]/; + const clerkWithDescendant = /\.cl-[A-Za-z0-9_-]+[\s>+~]+[.#\w[:]/; const descendantWithClerk = /[.#\w\]:][\s>+~]+\.cl-[A-Za-z0-9_-]+/; return clerkWithDescendant.test(selector) || descendantWithClerk.test(selector); diff --git a/packages/ui/src/utils/warnAboutCustomizationWithoutPinning.ts b/packages/ui/src/utils/warnAboutCustomizationWithoutPinning.ts index 7e7cde13278..674efe4a7b4 100644 --- a/packages/ui/src/utils/warnAboutCustomizationWithoutPinning.ts +++ b/packages/ui/src/utils/warnAboutCustomizationWithoutPinning.ts @@ -6,7 +6,7 @@ import { CLERK_CLASS_RE, HAS_RE, POSITIONAL_PSEUDO_RE } from './cssPatterns'; import { detectStructuralClerkCss } from './detectClerkStylesheetUsage'; // Regex patterns for hasAdjacencyWithOtherSelector -const OTHER_SELECTOR_RE = /[.#\w\[:]/; +const OTHER_SELECTOR_RE = /[.#\w[:]/; const CLERK_CLASS_GLOBAL_RE = /\.cl-[A-Za-z0-9_-]+/g; const COMBINATOR_RE = /[>+~\s]/; diff --git a/packages/ui/tsconfig.json b/packages/ui/tsconfig.json index caa880cc04e..f6bc61c6e53 100644 --- a/packages/ui/tsconfig.json +++ b/packages/ui/tsconfig.json @@ -22,6 +22,8 @@ "declarationMap": true, "allowJs": true, "paths": { + // Test-only imports; vitest.config.mts aliases these to clerk-js at runtime. + "@/core/*": ["../clerk-js/src/core/*"], "@/*": ["./src/*"], // Adding this to avoid changes in the ui files // in order to make git merges easier From 59c4ef2c88cbfa806cfc7c239b3548c0312bb66c Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Wed, 22 Jul 2026 14:14:11 -0400 Subject: [PATCH 2/2] fix(repo): drop two unused eslint-disable directives in @clerk/ui --- packages/ui/src/elements/Action/ActionTrigger.tsx | 1 - packages/ui/src/index.legacy.browser.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/ui/src/elements/Action/ActionTrigger.tsx b/packages/ui/src/elements/Action/ActionTrigger.tsx index 641951fc17a..132e40d7d3d 100644 --- a/packages/ui/src/elements/Action/ActionTrigger.tsx +++ b/packages/ui/src/elements/Action/ActionTrigger.tsx @@ -22,7 +22,6 @@ export const ActionTrigger = (props: ActionTriggerProps) => { return cloneElement(validChildren, { //@ts-ignore - // eslint-disable-next-line @typescript-eslint/no-misused-promises onClick: async () => { await validChildren.props.onClick?.(); open(value); diff --git a/packages/ui/src/index.legacy.browser.ts b/packages/ui/src/index.legacy.browser.ts index a073f7d89d2..fe698c2d965 100644 --- a/packages/ui/src/index.legacy.browser.ts +++ b/packages/ui/src/index.legacy.browser.ts @@ -1,6 +1,6 @@ // It's crucial this is the first import, // otherwise chunk loading will not work -// eslint-disable-next-line + import './utils/setWebpackChunkPublicPath'; import { ClerkUI } from './ClerkUI';