From 57deefcf5b0be4ea34970fb0fd01ccf1043edef4 Mon Sep 17 00:00:00 2001 From: Jeremy Wright Date: Thu, 3 Sep 2026 04:09:09 +0400 Subject: [PATCH] fix(ui): fit OAuth device code input in raised cards --- .changeset/device-code-input-overflow.md | 5 ++ packages/clerk-js/sandbox/app.ts | 6 +++ packages/clerk-js/sandbox/template.html | 5 ++ .../OAuthDeviceVerificationCodeInput.tsx | 5 +- packages/ui/src/elements/CodeControl.tsx | 47 ++++++++++--------- .../elements/__tests__/CodeControl.test.tsx | 21 ++++++++- 6 files changed, 64 insertions(+), 25 deletions(-) create mode 100644 .changeset/device-code-input-overflow.md diff --git a/.changeset/device-code-input-overflow.md b/.changeset/device-code-input-overflow.md new file mode 100644 index 00000000000..fae703e6229 --- /dev/null +++ b/.changeset/device-code-input-overflow.md @@ -0,0 +1,5 @@ +--- +'@clerk/ui': patch +--- + +Fix the `` code input overflowing the card when the card is not flush. diff --git a/packages/clerk-js/sandbox/app.ts b/packages/clerk-js/sandbox/app.ts index 682f9b2f53e..11bdbbe370a 100644 --- a/packages/clerk-js/sandbox/app.ts +++ b/packages/clerk-js/sandbox/app.ts @@ -35,6 +35,7 @@ const AVAILABLE_COMPONENTS = [ 'apiKeys', 'configureSSO', 'oauthConsent', + 'oauthDeviceVerification', 'taskChooseOrganization', 'taskResetPassword', 'taskSetupMFA', @@ -154,6 +155,7 @@ const componentControls: Record = { apiKeys: buildComponentControls('apiKeys'), configureSSO: buildComponentControls('configureSSO'), oauthConsent: buildComponentControls('oauthConsent'), + oauthDeviceVerification: buildComponentControls('oauthDeviceVerification'), taskChooseOrganization: buildComponentControls('taskChooseOrganization'), taskResetPassword: buildComponentControls('taskResetPassword'), taskSetupMFA: buildComponentControls('taskSetupMFA'), @@ -425,6 +427,10 @@ void (async () => { '/pricing-table': { mount: 'mountPricingTable', component: 'pricingTable' }, '/api-keys': { mount: 'mountAPIKeys', component: 'apiKeys' }, '/configure-sso': { mount: '__internal_mountConfigureSSO', component: 'configureSSO' }, + '/oauth-device-verification': { + mount: '__internal_mountOAuthDeviceVerification', + component: 'oauthDeviceVerification', + }, '/task-choose-organization': { mount: 'mountTaskChooseOrganization', component: 'taskChooseOrganization', diff --git a/packages/clerk-js/sandbox/template.html b/packages/clerk-js/sandbox/template.html index f59c231dceb..e7c5d8d344e 100644 --- a/packages/clerk-js/sandbox/template.html +++ b/packages/clerk-js/sandbox/template.html @@ -259,6 +259,11 @@ label="OAuth Consent" component="" > + ({ gap: t.space.$1x5 })} hasError={hasError} > {slots.map((slot, index) => ( @@ -27,6 +27,7 @@ function CodeGroup({ slots, hasError }: { slots: SlotProps[]; hasError: boolean key={index} elementDescriptor={descriptors.otpCodeFieldInput} hasError={hasError} + sx={t => ({ width: t.space.$8, height: t.space.$8 })} {...slot} /> ))} @@ -70,7 +71,7 @@ export function OAuthDeviceVerificationCodeInput({ control }: OAuthDeviceVerific & { isSuccessfullyFilled?: boolean }, ) { const { isSuccessfullyFilled, ...otpProps } = props; - const { char, hasFakeCaret, isActive, placeholderChar, ...rest } = otpProps; + const { char, hasFakeCaret, isActive, placeholderChar, sx, ...rest } = otpProps; return ( ({ - textAlign: 'center', - ...common.textVariants(t).h2, - padding: `${t.space.$0x5} 0`, - boxSizing: 'border-box', - display: 'flex', - position: 'relative', - alignItems: 'center', - justifyContent: 'center', - height: t.space.$10, - width: t.space.$10, - color: t.colors.$colorInputForeground, - borderWidth: t.borderWidths.$normal, - borderRadius: t.radii.$md, - ...(isSuccessfullyFilled ? { borderColor: t.colors.$success500 } : common.borderColor(t, props)), - backgroundColor: 'unset', - [mqu.sm]: { - height: t.space.$8, - width: t.space.$8, - }, - })} + sx={[ + t => ({ + textAlign: 'center', + ...common.textVariants(t).h2, + padding: `${t.space.$0x5} 0`, + boxSizing: 'border-box', + display: 'flex', + position: 'relative', + alignItems: 'center', + justifyContent: 'center', + height: t.space.$10, + width: t.space.$10, + color: t.colors.$colorInputForeground, + borderWidth: t.borderWidths.$normal, + borderRadius: t.radii.$md, + ...(isSuccessfullyFilled ? { borderColor: t.colors.$success500 } : common.borderColor(t, props)), + backgroundColor: 'unset', + [mqu.sm]: { + height: t.space.$8, + width: t.space.$8, + }, + }), + sx, + ]} > {char !== null &&
{char}
} {hasFakeCaret && } diff --git a/packages/ui/src/elements/__tests__/CodeControl.test.tsx b/packages/ui/src/elements/__tests__/CodeControl.test.tsx index 2ffbbf6d0ca..ae3f994bbf8 100644 --- a/packages/ui/src/elements/__tests__/CodeControl.test.tsx +++ b/packages/ui/src/elements/__tests__/CodeControl.test.tsx @@ -6,7 +6,7 @@ import { describe, expect, it, vi } from 'vitest'; import { bindCreateFixtures } from '@/test/create-fixtures'; -import { OTPCodeControl, OTPRoot, useFieldOTP } from '../CodeControl'; +import { OTPCodeControl, OTPInputSlot, OTPRoot, useFieldOTP } from '../CodeControl'; import { withCardStateProvider } from '../contexts'; const { createFixtures } = bindCreateFixtures('UserProfile'); @@ -51,6 +51,25 @@ const typeCode = async (input: HTMLElement, user: UserEvent, code = '123456') => }; describe('CodeControl', () => { + describe('OTPInputSlot', () => { + it('allows its dimensions to be overridden', async () => { + const { wrapper } = await createFixtures(); + + const { getByTestId } = render( + , + { wrapper }, + ); + + expect(getByTestId(testId)).toHaveStyle({ width: '2rem', height: '2rem' }); + }); + }); + describe('OTPCodeControl', () => { it('renders 6 "fake" input fields by default', async () => { const { wrapper } = await createFixtures();