@@ -3,14 +3,14 @@ import type { __internal_EnableOrganizationsPromptProps, EnableEnvironmentSettin
33// eslint-disable-next-line no-restricted-imports
44import type { SerializedStyles } from '@emotion/react' ;
55// eslint-disable-next-line no-restricted-imports
6- import { css , type Theme } from '@emotion/react' ;
7- import { forwardRef , useId , useLayoutEffect , useRef , useState } from 'react' ;
6+ import { css } from '@emotion/react' ;
7+ import React , { forwardRef , useId , useLayoutEffect , useRef , useState } from 'react' ;
88
99import { useEnvironment } from '@/ui/contexts' ;
1010import { Modal } from '@/ui/elements/Modal' ;
11- import { common , InternalThemeProvider } from '@/ui/styledSystem' ;
11+ import { InternalThemeProvider } from '@/ui/styledSystem' ;
1212
13- import { Box , Flex , Span } from '../../../customizables' ;
13+ import { Box , Flex } from '../../../customizables' ;
1414import { Portal } from '../../../elements/Portal' ;
1515import { basePromptElementStyles , ClerkLogoIcon , PromptContainer , PromptSuccessIcon } from '../shared' ;
1616
@@ -197,12 +197,21 @@ const EnableOrganizationsPromptInternal = ({
197197 } ) }
198198 >
199199 < Flex sx = { t => ( { marginTop : t . sizes . $2 } ) } >
200- < Switch
201- label = 'Allow personal account'
202- description = 'Allow users to work outside of an organization by providing a personal account. We do not recommend for B2B SaaS apps.'
203- checked = { allowPersonalAccount }
204- onChange = { ( ) => setAllowPersonalAccount ( prev => ! prev ) }
205- />
200+ < RadioGroup
201+ value = { allowPersonalAccount ? 'allow' : 'require' }
202+ onChange = { value => setAllowPersonalAccount ( value === 'allow' ) }
203+ >
204+ < RadioGroupItem
205+ value = 'require'
206+ label = 'Require organization membership'
207+ description = 'Users will be required to create or join an organization to access the application. Common for most B2B SaaS applications.'
208+ />
209+ < RadioGroupItem
210+ value = 'allow'
211+ label = 'Allow personal accounts'
212+ description = 'Users will be able to work outside of an organization by providing a personal account.'
213+ />
214+ </ RadioGroup >
206215 </ Flex >
207216 </ Flex >
208217 </ Box >
@@ -368,100 +377,103 @@ const PromptButton = forwardRef<HTMLButtonElement, PromptButtonProps>(({ variant
368377 ) ;
369378} ) ;
370379
371- type SwitchProps = React . ComponentProps < 'input' > & {
372- label : string ;
373- description ?: string ;
380+ type RadioGroupProps = {
381+ value : string ;
382+ onChange : ( value : string ) => void ;
383+ children : React . ReactNode ;
374384} ;
375385
376- const TRACK_PADDING = '2px' ;
377- const TRACK_INNER_WIDTH = ( t : Theme ) => t . sizes . $6 ;
378- const TRACK_HEIGHT = ( t : Theme ) => t . sizes . $4 ;
379- const THUMB_WIDTH = ( t : Theme ) => t . sizes . $3 ;
386+ const RadioGroup = ( { value, onChange, children } : RadioGroupProps ) => {
387+ const name = useId ( ) ;
380388
381- const Switch = forwardRef < HTMLInputElement , SwitchProps > (
382- ( { label, description, checked : controlledChecked , defaultChecked, onChange, ...props } , ref ) => {
383- const descriptionId = useId ( ) ;
389+ return (
390+ < Flex
391+ role = 'radiogroup'
392+ direction = 'col'
393+ gap = { 3 }
394+ >
395+ { React . Children . map ( children , child => {
396+ if ( React . isValidElement < RadioGroupItemProps > ( child ) ) {
397+ return React . cloneElement ( child , {
398+ name,
399+ checked : child . props . value === value ,
400+ onChange : ( ) => onChange ( child . props . value ) ,
401+ } ) ;
402+ }
403+ return child ;
404+ } ) }
405+ </ Flex >
406+ ) ;
407+ } ;
384408
385- const isControlled = controlledChecked !== undefined ;
386- const [ internalChecked , setInternalChecked ] = useState ( ! ! defaultChecked ) ;
387- const checked = isControlled ? controlledChecked : internalChecked ;
409+ type RadioGroupItemProps = {
410+ value : string ;
411+ label : string ;
412+ description ?: string ;
413+ name ?: string ;
414+ checked ?: boolean ;
415+ onChange ?: ( ) => void ;
416+ } ;
388417
389- const handleChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
390- if ( ! isControlled ) {
391- setInternalChecked ( e . target . checked ) ;
392- }
393- onChange ?.( e ) ;
394- } ;
418+ const RadioGroupItem = forwardRef < HTMLInputElement , RadioGroupItemProps > (
419+ ( { value, label, description, name, checked, onChange } , ref ) => {
420+ const id = useId ( ) ;
421+ const descriptionId = useId ( ) ;
395422
396423 return (
397424 < Flex
398- direction = 'col'
399- gap = { 1 }
425+ as = 'label'
426+ gap = { 2 }
427+ align = 'start'
428+ sx = { {
429+ cursor : 'pointer' ,
430+ userSelect : 'none' ,
431+ } }
400432 >
433+ < input
434+ ref = { ref }
435+ type = 'radio'
436+ id = { id }
437+ name = { name }
438+ value = { value }
439+ checked = { checked }
440+ onChange = { onChange }
441+ aria-describedby = { description ? descriptionId : undefined }
442+ css = { css `
443+ ${ basePromptElementStyles } ;
444+ appearance: none;
445+ width: 1rem;
446+ height: 1rem;
447+ margin: 0;
448+ margin- to p: 0.125rem;
449+ bor der: 1px solid rgba(255, 255, 255, 0.3);
450+ bor der- radius: 50%;
451+ background- color : transparent;
452+ cursor : pointer;
453+ flex- shrink: 0;
454+ transition: 120ms ease- in- out;
455+ transition- property: bor der- color , background- color , box- shadow;
456+
457+ & : checked {
458+ border-color : # fff ;
459+ background-color : # fff ;
460+ box-shadow : inset 0 0 0 3px # 1f1f1f ;
461+ }
462+
463+ & : focus-visible {
464+ outline : 2px solid white;
465+ outline-offset : 2px ;
466+ }
467+
468+ & : hover : not (: checked ) {
469+ border-color : rgba (255 , 255 , 255 , 0.5 );
470+ }
471+ ` }
472+ />
401473 < Flex
402- as = 'label'
403- gap = { 2 }
404- align = 'center'
405- sx = { {
406- isolation : 'isolate' ,
407- userSelect : 'none' ,
408- '&:has(input:focus-visible) > input + span' : {
409- outline : '2px solid white' ,
410- outlineOffset : '2px' ,
411- } ,
412- '&:has(input:disabled) > input + span' : {
413- opacity : 0.6 ,
414- cursor : 'not-allowed' ,
415- pointerEvents : 'none' ,
416- } ,
417- } }
474+ direction = 'col'
475+ gap = { 1 }
418476 >
419- < input
420- type = 'checkbox'
421- { ...props }
422- ref = { ref }
423- role = 'switch'
424- { ...( isControlled ? { checked } : { defaultChecked } ) }
425- onChange = { handleChange }
426- css = { { ...common . visuallyHidden ( ) } }
427- aria-describedby = { description ? descriptionId : undefined }
428- />
429- < Span
430- sx = { t => {
431- const trackWidth = `calc(${ TRACK_INNER_WIDTH ( t ) } + ${ TRACK_PADDING } + ${ TRACK_PADDING } )` ;
432- const trackHeight = `calc(${ TRACK_HEIGHT ( t ) } + ${ TRACK_PADDING } )` ;
433- return {
434- display : 'flex' ,
435- alignItems : 'center' ,
436- paddingInline : TRACK_PADDING ,
437- width : trackWidth ,
438- height : trackHeight ,
439- border : '1px solid rgba(255, 255, 255, 0.2)' ,
440- backgroundColor : checked ? '#6C47FF' : 'rgba(0, 0, 0, 0.2)' ,
441- borderRadius : 999 ,
442- transition : 'background-color 0.2s ease-in-out' ,
443- } ;
444- } }
445- >
446- < Span
447- sx = { t => {
448- const size = THUMB_WIDTH ( t ) ;
449- const maxTranslateX = `calc(${ TRACK_INNER_WIDTH ( t ) } - ${ size } - ${ TRACK_PADDING } )` ;
450- return {
451- width : size ,
452- height : size ,
453- borderRadius : 9999 ,
454- backgroundColor : 'white' ,
455- boxShadow : '0px 0px 0px 1px rgba(0, 0, 0, 0.1)' ,
456- transform : `translateX(${ checked ? maxTranslateX : '0' } )` ,
457- transition : 'transform 0.2s ease-in-out' ,
458- '@media (prefers-reduced-motion: reduce)' : {
459- transition : 'none' ,
460- } ,
461- } ;
462- } }
463- />
464- </ Span >
465477 < span
466478 css = { [
467479 basePromptElementStyles ,
@@ -475,25 +487,23 @@ const Switch = forwardRef<HTMLInputElement, SwitchProps>(
475487 >
476488 { label }
477489 </ span >
490+ { description && (
491+ < span
492+ id = { descriptionId }
493+ css = { [
494+ basePromptElementStyles ,
495+ css `
496+ font-size : 0.75rem ;
497+ line-height : 1.3333333333 ;
498+ color : # c3c3c6 ;
499+ text-wrap : pretty;
500+ ` ,
501+ ] }
502+ >
503+ { description }
504+ </ span >
505+ ) }
478506 </ Flex >
479- { description ? (
480- < Span
481- id = { descriptionId }
482- sx = { t => [
483- basePromptElementStyles ,
484- {
485- display : 'block' ,
486- paddingInlineStart : `calc(${ TRACK_INNER_WIDTH ( t ) } + ${ TRACK_PADDING } + ${ TRACK_PADDING } + ${ t . sizes . $2 } )` ,
487- fontSize : '0.75rem' ,
488- lineHeight : '1.3333333333' ,
489- color : '#c3c3c6' ,
490- textWrap : 'pretty' ,
491- } ,
492- ] }
493- >
494- { description }
495- </ Span >
496- ) : null }
497507 </ Flex >
498508 ) ;
499509 } ,
0 commit comments