Skip to content
Draft
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
1 change: 1 addition & 0 deletions packages/clerk-js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
"dequal": "2.0.3",
"input-otp": "1.4.2",
"qrcode.react": "4.2.0",
"react-aria": "3.34.0",
"regenerator-runtime": "0.14.1",
"swr": "2.3.4"
},
Expand Down
38 changes: 11 additions & 27 deletions packages/clerk-js/src/ui/baseTheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,14 @@ const inputShadowStyles = (
'&:hover': {
boxShadow: hoverShadow,
},
'&:focus-within,&[data-focus-within="true"]': {
// Mouse users - show focus ring (works for both direct inputs and containers)
'&:focus:not([data-focus-visible="true"]), &:has(input:focus:not([data-focus-visible="true"]))': {
boxShadow: [hoverShadow, theme.shadows.$focusRing.replace('{{color}}', colors.focus)].toString(),
},
// Keyboard users - show hover state only (outline is defined separately)
'&[data-focus-visible="true"], &:has(input[data-focus-visible="true"])': {
boxShadow: hoverShadow,
},
};
};

Expand All @@ -55,8 +60,11 @@ const checkboxShadowStyles = (
'&:hover': {
boxShadow: hoverShadow,
},
'&:focus-visible': {
boxShadow: [hoverShadow, theme.shadows.$focusRing.replace('{{color}}', colors.focus)].toString(),
'&[data-focus-visible="true"]': {
outlineWidth: '2px',
outlineStyle: 'solid',
outlineColor: theme.colors.$primary500,
outlineOffset: '3px',
},
};
};
Expand Down Expand Up @@ -118,42 +126,18 @@ const clerkTheme: Appearance = {
},
'&[data-color="primary"]': {
boxShadow: BUTTON_SOLID_SHADOW(theme.colors.$primary500),
'&:focus': {
boxShadow: [
BUTTON_SOLID_SHADOW(theme.colors.$primary500),
theme.shadows.$focusRing.replace('{{color}}', theme.colors.$colorRing),
].toString(),
},
},
'&[data-color="danger"]': {
boxShadow: BUTTON_SOLID_SHADOW(theme.colors.$danger500),
'&:focus': {
boxShadow: [
BUTTON_SOLID_SHADOW(theme.colors.$danger500),
theme.shadows.$focusRing.replace('{{color}}', theme.colors.$dangerAlpha200),
].toString(),
},
},
},
'&[data-variant="outline"]': {
borderWidth: 0,
boxShadow: BUTTON_OUTLINE_SHADOW(theme.colors.$borderAlpha100),
'&:focus': {
boxShadow: [
BUTTON_OUTLINE_SHADOW(theme.colors.$borderAlpha100),
theme.shadows.$focusRing.replace('{{color}}', theme.colors.$colorRing),
].toString(),
},
},
'&[data-variant="bordered"]': {
borderWidth: 0,
boxShadow: BUTTON_OUTLINE_SHADOW(theme.colors.$borderAlpha100),
'&:focus': {
boxShadow: [
BUTTON_OUTLINE_SHADOW(theme.colors.$borderAlpha100),
theme.shadows.$focusRing.replace('{{color}}', theme.colors.$colorRing),
].toString(),
},
},
},
badge: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ export const OrganizationSwitcherTrigger = withAvatarShimmer(
variant='ghost'
colorScheme='neutral'
hoverAsFocus
focusRing={false}
sx={[
t => ({
padding: `${t.space.$1} ${t.space.$2}`,
Expand Down
1 change: 0 additions & 1 deletion packages/clerk-js/src/ui/elements/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,6 @@ const NavButton = (props: NavButtonProps) => {
textVariant='buttonLarge'
size='md'
isActive={isActive}
focusRing={false}
{...rest}
sx={[
t => ({
Expand Down
14 changes: 12 additions & 2 deletions packages/clerk-js/src/ui/elements/PhoneInput/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,16 @@ const PhoneInputBase = forwardRef<HTMLInputElement, PhoneInputProps & { feedback
position: 'relative',
borderRadius: theme.radii.$md,
zIndex: 1,
'&:focus-within,&[data-focus-within="true"]': {
...common.borderVariants(theme, { hasError: rest.hasError }).normal['&:focus'],
'&:has(input:focus:not([data-focus-visible="true"]))': {
...common.borderVariants(theme, {
focusRing: true,
}).normal['&:focus'],
},
'&:has(input:focus[data-focus-visible="true"]), &:has(button:focus-visible)': {
outlineWidth: '2px',
outlineStyle: 'solid',
outlineColor: theme.colors.$primary500,
outlineOffset: '3px',
},
})}
>
Expand Down Expand Up @@ -128,6 +136,7 @@ const PhoneInputBase = forwardRef<HTMLInputElement, PhoneInputProps & { feedback
},
})}
hoverAsFocus
focusRing={false}
isDisabled={rest.isDisabled}
icon={ChevronUpDown}
iconSx={t => ({
Expand Down Expand Up @@ -199,6 +208,7 @@ const PhoneInputBase = forwardRef<HTMLInputElement, PhoneInputProps & { feedback
}),
sx,
]}
focusRing={false}
//use our internal ref while forwarding
//@ts-expect-error
ref={mergeRefs(phoneInputRef, ref)}
Expand Down
5 changes: 4 additions & 1 deletion packages/clerk-js/src/ui/elements/Switch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ export const Switch = forwardRef<HTMLDivElement, SwitchProps>(
isolation: 'isolate',
width: 'fit-content',
'&:has(input:focus-visible) > input + span': {
...common.focusRingStyles(t),
outlineWidth: '2px',
outlineStyle: 'solid',
outlineColor: t.colors.$primary500,
outlineOffset: '3px',
},
})}
>
Expand Down
9 changes: 8 additions & 1 deletion packages/clerk-js/src/ui/primitives/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,14 @@ const { applyVariants, filterProps } = createVariants(
true: { width: '100%' },
},
focusRing: {
true: { ...common.focusRing(theme) },
true: {
'&:focus-visible': {
outlineWidth: '2px',
outlineStyle: 'solid',
outlineColor: theme.colors.$primary500,
outlineOffset: '3px',
},
},
},
},
defaultVariants: {
Expand Down
24 changes: 20 additions & 4 deletions packages/clerk-js/src/ui/primitives/Input.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import { useFocusRing } from 'react-aria';

import { localizationKeys, useLocalizations } from '../localization';
import type { PrimitiveProps, RequiredProp, StyleVariants } from '../styledSystem';
Expand All @@ -13,9 +14,6 @@ const { applyVariants, filterProps } = createVariants((theme, props) => ({
padding: `${theme.space.$1x5} ${theme.space.$3}`,
backgroundColor: theme.colors.$colorInput,
color: theme.colors.$colorInputForeground,
// outline support for Windows contrast themes
outline: 'transparent solid 2px',
outlineOffset: '2px',
maxHeight: theme.sizes.$9,
width: props.type === 'checkbox' ? theme.sizes.$4 : '100%',
aspectRatio: props.type === 'checkbox' ? '1/1' : 'unset',
Expand All @@ -35,6 +33,19 @@ const { applyVariants, filterProps } = createVariants((theme, props) => ({
'::placeholder': {
color: theme.colors.$colorMutedForeground,
},
// outline support for Windows contrast themes
outlineWidth: '2px',
outlineStyle: 'solid',
outlineColor: 'transparent',
outlineOffset: '3px',
'&[data-focus-visible="true"]':
props.focusRing !== false
? {
outlineColor: theme.colors.$primary500,
}
: {
outlineColor: 'transparent',
},
},
variants: {
variant: {
Expand Down Expand Up @@ -64,6 +75,7 @@ export type InputProps = PrimitiveProps<'input'> & StyleVariants<typeof applyVar

export const Input = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {
const fieldControl = useFormField() || {};
const { isFocusVisible, focusProps } = useFocusRing({ isTextInput: true });
const { t } = useLocalizations();
// @ts-expect-error Typescript is complaining that `feedbackMessageId` does not exist. We are clearly passing them from above.
const { feedbackMessageId, ignorePasswordManager, feedbackType, ...fieldControlProps } = sanitizeInputProps(
Expand Down Expand Up @@ -107,6 +119,7 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>((props, ref)
{...rest}
{...typeProps}
{...passwordManagerProps}
{...focusProps}
ref={ref}
onChange={onChange}
disabled={isDisabled}
Expand All @@ -118,6 +131,7 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>((props, ref)
aria-disabled={_disabled}
data-feedback={feedbackType}
data-variant={props.variant || 'default'}
data-focus-visible={isFocusVisible}
css={applyVariants(props)}
/>
);
Expand Down Expand Up @@ -181,15 +195,17 @@ export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>((pr
const _disabled = isDisabled || props.isDisabled;
const _required = isRequired || props.isRequired;
const _hasError = hasError || props.hasError;

const { isFocusVisible, focusProps } = useFocusRing({ isTextInput: true });
return (
<textarea
{...rest}
{...focusProps}
ref={ref}
disabled={_disabled}
required={_required}
aria-invalid={_hasError}
aria-required={_required}
data-focus-visible={isFocusVisible}
css={applyVariants(props)}
/>
);
Expand Down
7 changes: 5 additions & 2 deletions packages/clerk-js/src/ui/primitives/Link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,12 @@ const { applyVariants, filterProps } = createVariants(theme => ({
},
focusRing: {
true: {
'&:focus': {
'&:focus-visible': {
outline: 'none',
...common.focusRing(theme),
outlineWidth: '2px',
outlineStyle: 'solid',
outlineColor: theme.colors.$primary500,
outlineOffset: '3px',
},
},
},
Expand Down
Loading