diff --git a/packages/clerk-js/src/ui/components/devPrompts/EnableOrganizationsPrompt/index.tsx b/packages/clerk-js/src/ui/components/devPrompts/EnableOrganizationsPrompt/index.tsx
index 0b53555f857..dda7cd40a0d 100644
--- a/packages/clerk-js/src/ui/components/devPrompts/EnableOrganizationsPrompt/index.tsx
+++ b/packages/clerk-js/src/ui/components/devPrompts/EnableOrganizationsPrompt/index.tsx
@@ -1,15 +1,15 @@
import { useClerk } from '@clerk/shared/react';
import type { __internal_EnableOrganizationsPromptProps } from '@clerk/shared/types';
// eslint-disable-next-line no-restricted-imports
-import { css } from '@emotion/react';
-import { forwardRef, useMemo, useRef, useState } from 'react';
+import { css, type Theme } from '@emotion/react';
+import { forwardRef, useId, useMemo, useRef, useState } from 'react';
import { Modal } from '@/ui/elements/Modal';
import { common, InternalThemeProvider } from '@/ui/styledSystem';
import { DevTools } from '../../../../core/resources/DevTools';
import type { Environment } from '../../../../core/resources/Environment';
-import { Flex } from '../../../customizables';
+import { Flex, Span } from '../../../customizables';
import { Portal } from '../../../elements/Portal';
import { basePromptElementStyles, handleDashboardUrlParsing, PromptContainer, PromptSuccessIcon } from '../shared';
@@ -88,7 +88,8 @@ const EnableOrganizationsPromptInternal = ({
sx={() => ({
display: 'flex',
flexDirection: 'column',
- maxWidth: '30rem',
+ width: '30rem',
+ maxWidth: 'calc(100vw - 2rem)',
})}
>
({ marginTop: t.sizes.$3 })}>
- setAllowPersonalAccount(!allowPersonalAccount)}
- isDisabled={false}
+ onChange={() => setAllowPersonalAccount(prev => !prev)}
/>
)}
@@ -314,8 +316,10 @@ const EnableOrganizationsPromptInternal = ({
@@ -461,118 +465,134 @@ const PromptButton = forwardRef(({ variant
);
});
-type AllowPersonalAccountSwitchProps = {
- checked: boolean;
- isDisabled: boolean;
- onChange: (checked: boolean) => void;
+type SwitchProps = React.ComponentProps<'input'> & {
+ label: string;
+ description?: string;
};
-const AllowPersonalAccountSwitch = forwardRef(
- ({ checked, onChange, isDisabled = false }, ref) => {
+const TRACK_PADDING = '2px';
+const TRACK_INNER_WIDTH = (t: Theme) => t.sizes.$6;
+const TRACK_HEIGHT = (t: Theme) => t.sizes.$4;
+const THUMB_WIDTH = (t: Theme) => t.sizes.$3;
+
+const Switch = forwardRef(
+ ({ label, description, checked: controlledChecked, defaultChecked, onChange, ...props }, ref) => {
+ const descriptionId = useId();
+
+ const isControlled = controlledChecked !== undefined;
+ const [internalChecked, setInternalChecked] = useState(!!defaultChecked);
+ const checked = isControlled ? controlledChecked : internalChecked;
+
const handleChange = (e: React.ChangeEvent) => {
- if (isDisabled) {
- return;
+ if (!isControlled) {
+ setInternalChecked(e.target.checked);
}
-
- onChange?.(e.target.checked);
+ onChange?.(e);
};
return (
({
- isolation: 'isolate',
- width: 'fit-content',
- '&:has(input:focus-visible) > input + span': {
- ...common.focusRingStyles(t),
- },
- })}
+ direction='col'
+ gap={1}
>
- {/* The order of the elements is important here for the focus ring to work. The input is visually hidden, so the focus ring is applied to the span. */}
-
({
- minWidth: t.sizes.$7,
- alignSelf: 'flex-start',
- height: t.sizes.$4,
- alignItems: 'center',
- position: 'relative',
- borderColor: '#DBDBE0',
- backgroundColor: checked ? '#DBDBE0' : t.colors.$primary500,
- borderRadius: 999,
- transition: 'background-color 0.2s',
- opacity: isDisabled ? 0.6 : 1,
- cursor: isDisabled ? 'not-allowed' : 'pointer',
- outline: 'none',
- boxSizing: 'border-box',
- boxShadow:
- '0px 0px 6px 0px rgba(255, 255, 255, 0.04) inset, 0px 0px 0px 1px rgba(255, 255, 255, 0.04) inset, 0px 1px 0px 0px rgba(255, 255, 255, 0.04) inset, 0px 0px 0px 1px rgba(0, 0, 0, 0.1)',
- })}
+ as='label'
+ gap={2}
+ align='center'
+ sx={{
+ isolation: 'isolate',
+ userSelect: 'none',
+ '&:has(input:focus-visible) > input + span': {
+ outline: '2px solid white',
+ outlineOffset: '2px',
+ },
+ '&:has(input:disabled) > input + span': {
+ opacity: 0.6,
+ cursor: 'not-allowed',
+ pointerEvents: 'none',
+ },
+ }}
>
- ({
- position: 'absolute',
- left: t.sizes.$0x5,
- width: t.sizes.$3,
- height: t.sizes.$3,
- borderRadius: '50%',
- backgroundColor: 'white',
- boxShadow: t.shadows.$switchControl,
- transform: `translateX(${checked ? t.sizes.$3 : 0})`,
- transition: 'transform 0.2s',
- zIndex: 1,
- })}
+
-
-
-
+ {
+ const trackWidth = `calc(${TRACK_INNER_WIDTH(t)} + ${TRACK_PADDING} + ${TRACK_PADDING})`;
+ const trackHeight = `calc(${TRACK_HEIGHT(t)} + ${TRACK_PADDING})`;
+ return {
+ display: 'flex',
+ alignItems: 'center',
+ paddingInline: TRACK_PADDING,
+ width: trackWidth,
+ height: trackHeight,
+ border: `1px solid rgba(118, 118, 132, 0.25)`,
+ backgroundColor: checked ? 'rgba(255, 255, 255, 0.75)' : `rgba(255, 255, 255, 0.1)`,
+ borderRadius: 999,
+ transition: 'background-color 0.2s ease-in-out',
+ '&:hover': {
+ borderColor: `rgba(118, 118, 132, 0.5)`,
+ },
+ };
+ }}
+ >
+ {
+ const size = THUMB_WIDTH(t);
+ const maxTranslateX = `calc(${TRACK_INNER_WIDTH(t)} - ${size} - ${TRACK_PADDING})`;
+ return {
+ width: size,
+ height: size,
+ borderRadius: 9999,
+ backgroundColor: 'white',
+ transform: `translateX(${checked ? maxTranslateX : '0'})`,
+ transition: 'transform 0.2s ease-in-out',
+ '@media (prefers-reduced-motion: reduce)': {
+ transition: 'none',
+ },
+ };
+ }}
+ />
+
- Allow personal account
+ {label}
-
-
+ {description ? (
+ [
basePromptElementStyles,
- css`
- color: #b4b4b4;
- font-size: 0.8125rem;
- font-weight: 400;
- line-height: 1.23;
- `,
+ {
+ display: 'block',
+ paddingInlineStart: `calc(${TRACK_INNER_WIDTH(t)} + ${TRACK_PADDING} + ${TRACK_PADDING} + ${t.sizes.$2})`,
+ fontSize: '0.75rem',
+ lineHeight: '1.3333333333',
+ color: '#c3c3c6',
+ textWrap: 'pretty',
+ },
]}
>
- This is an uncommon setting, meant for applications that sell to both organizations and individual users.
- Most B2B applications require users to be part of an organization, and should keep this setting disabled.
-
-
+ {description}
+
+ ) : null}
);
},
diff --git a/packages/clerk-js/src/ui/components/devPrompts/KeylessPrompt/index.tsx b/packages/clerk-js/src/ui/components/devPrompts/KeylessPrompt/index.tsx
index 97c4734609f..1eccd1b856b 100644
--- a/packages/clerk-js/src/ui/components/devPrompts/KeylessPrompt/index.tsx
+++ b/packages/clerk-js/src/ui/components/devPrompts/KeylessPrompt/index.tsx
@@ -118,6 +118,7 @@ const KeylessPromptInternal = (_props: KeylessPromptProps) => {
minWidth: '13.4rem',
paddingLeft: `${t.space.$3}`,
borderRadius: '1.25rem',
+ transition: 'all 195ms cubic-bezier(0.2, 0.61, 0.1, 1)',
'&[data-expanded="false"]:hover': {
background: 'linear-gradient(180deg, rgba(255, 255, 255, 0.20) 0%, rgba(255, 255, 255, 0) 100%), #1f1f1f',
diff --git a/packages/clerk-js/src/ui/components/devPrompts/shared.tsx b/packages/clerk-js/src/ui/components/devPrompts/shared.tsx
index 42618e60646..c959d76ef9c 100644
--- a/packages/clerk-js/src/ui/components/devPrompts/shared.tsx
+++ b/packages/clerk-js/src/ui/components/devPrompts/shared.tsx
@@ -17,7 +17,6 @@ export function PromptContainer({ children, sx, ...props }: React.ComponentProps
background: 'linear-gradient(180deg, rgba(255, 255, 255, 0.16) 0%, rgba(255, 255, 255, 0) 100%), #1f1f1f',
boxShadow:
'0px 0px 0px 0.5px #2F3037 inset, 0px 1px 0px 0px rgba(255, 255, 255, 0.08) inset, 0px 0px 0.8px 0.8px rgba(255, 255, 255, 0.20) inset, 0px 0px 0px 0px rgba(255, 255, 255, 0.72), 0px 16px 36px -6px rgba(0, 0, 0, 0.36), 0px 6px 16px -2px rgba(0, 0, 0, 0.20);',
- transition: 'all 195ms cubic-bezier(0.2, 0.61, 0.1, 1)',
},
sx,
]}