Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/slow-wombats-battle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/clerk-js': patch
---

Refactor of internal radio input in forms.
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import type { OrganizationDomainResource, OrganizationEnrollmentMode } from '@clerk/types';
import type {
OrganizationDomainResource,
OrganizationEnrollmentMode,
OrganizationSettingsResource,
} from '@clerk/types';

import { CalloutWithAction, useGate } from '../../common';
import { useCoreOrganization, useEnvironment } from '../../contexts';
Expand Down Expand Up @@ -51,6 +55,46 @@ const useCalloutLabel = (
];
};

const buildEnrollmentOptions = (settings: OrganizationSettingsResource) => {
const _options = [];
if (settings.domains.enrollmentModes.includes('manual_invitation')) {
_options.push({
value: 'manual_invitation',
label: localizationKeys('organizationProfile.verifiedDomainPage.enrollmentTab.manualInvitationOption__label'),
description: localizationKeys(
'organizationProfile.verifiedDomainPage.enrollmentTab.manualInvitationOption__description',
),
});
}

if (settings.domains.enrollmentModes.includes('automatic_invitation')) {
_options.push({
value: 'automatic_invitation',
label: localizationKeys('organizationProfile.verifiedDomainPage.enrollmentTab.automaticInvitationOption__label'),
description: localizationKeys(
'organizationProfile.verifiedDomainPage.enrollmentTab.automaticInvitationOption__description',
),
});
}

if (settings.domains.enrollmentModes.includes('automatic_suggestion')) {
_options.push({
value: 'automatic_suggestion',
label: localizationKeys('organizationProfile.verifiedDomainPage.enrollmentTab.automaticSuggestionOption__label'),
description: localizationKeys(
'organizationProfile.verifiedDomainPage.enrollmentTab.automaticSuggestionOption__description',
),
});
}

return _options;
};

const useEnrollmentOptions = () => {
const { organizationSettings } = useEnvironment();
return buildEnrollmentOptions(organizationSettings);
};

export const VerifiedDomainPage = withCardStateProvider(() => {
const card = useCardState();
const { organizationSettings } = useEnvironment();
Expand All @@ -71,49 +115,11 @@ export const VerifiedDomainPage = withCardStateProvider(() => {
const breadcrumbTitle = localizationKeys('organizationProfile.profilePage.domainSection.title');
const allowsEdit = mode === 'edit';

const enrollmentOptions = useEnrollmentOptions();
const enrollmentMode = useFormControl('enrollmentMode', '', {
type: 'radio',
radioOptions: [
...(organizationSettings.domains.enrollmentModes.includes('manual_invitation')
? [
{
value: 'manual_invitation',
label: localizationKeys(
'organizationProfile.verifiedDomainPage.enrollmentTab.manualInvitationOption__label',
),
description: localizationKeys(
'organizationProfile.verifiedDomainPage.enrollmentTab.manualInvitationOption__description',
),
},
]
: []),
...(organizationSettings.domains.enrollmentModes.includes('automatic_invitation')
? [
{
value: 'automatic_invitation',
label: localizationKeys(
'organizationProfile.verifiedDomainPage.enrollmentTab.automaticInvitationOption__label',
),
description: localizationKeys(
'organizationProfile.verifiedDomainPage.enrollmentTab.automaticInvitationOption__description',
),
},
]
: []),
...(organizationSettings.domains.enrollmentModes.includes('automatic_suggestion')
? [
{
value: 'automatic_suggestion',
label: localizationKeys(
'organizationProfile.verifiedDomainPage.enrollmentTab.automaticSuggestionOption__label',
),
description: localizationKeys(
'organizationProfile.verifiedDomainPage.enrollmentTab.automaticSuggestionOption__description',
),
},
]
: []),
],
radioOptions: enrollmentOptions,
isRequired: true,
});

const deletePending = useFormControl('deleteExistingInvitationsSuggestions', '', {
Expand Down Expand Up @@ -252,7 +258,7 @@ export const VerifiedDomainPage = withCardStateProvider(() => {
gap={6}
>
<Form.ControlRow elementId={enrollmentMode.id}>
<Form.Control {...enrollmentMode.props} />
<Form.RadioGroup {...enrollmentMode.props} />
</Form.ControlRow>

{allowsEdit && (
Expand Down
2 changes: 2 additions & 0 deletions packages/clerk-js/src/ui/elements/FieldControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { useFormControlFeedback } from '../utils';
import { useCardState } from './contexts';
import type { FormFeedbackProps } from './FormControl';
import { FormFeedback } from './FormControl';
import { RadioItem } from './RadioGroup';

type FormControlProps = Omit<PropsOfComponent<typeof Input>, 'label' | 'placeholder' | 'disabled' | 'required'> &
ReturnType<typeof useFormControlUtil<FieldId>>['props'];
Expand Down Expand Up @@ -218,6 +219,7 @@ export const Field = {
Label: FieldLabel,
LabelRow: FieldLabelRow,
Input: InputElement,
RadioItem: RadioItem,
Action: FieldAction,
AsOptional: FieldOptionalLabel,
LabelIcon: FieldLabelIcon,
Expand Down
70 changes: 46 additions & 24 deletions packages/clerk-js/src/ui/elements/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { PropsWithChildren } from 'react';
import React, { useState } from 'react';

import type { LocalizationKey } from '../customizables';
import { Button, descriptors, Flex, Form as FormPrim, localizationKeys } from '../customizables';
import { Button, Col, descriptors, Flex, Form as FormPrim, localizationKeys } from '../customizables';
import { useLoadingStatus } from '../hooks';
import type { PropsOfComponent } from '../styledSystem';
import { useCardState } from './contexts';
Expand Down Expand Up @@ -76,35 +76,31 @@ const FormRoot = (props: FormProps): JSX.Element => {
const FormSubmit = (props: PropsOfComponent<typeof Button>) => {
const { isLoading, isDisabled } = useFormState();
return (
<>
<Button
elementDescriptor={descriptors.formButtonPrimary}
block
textVariant='buttonExtraSmallBold'
isLoading={isLoading}
isDisabled={isDisabled}
type='submit'
{...props}
localizationKey={props.localizationKey || localizationKeys('formButtonPrimary')}
/>
</>
<Button
elementDescriptor={descriptors.formButtonPrimary}
block
textVariant='buttonExtraSmallBold'
isLoading={isLoading}
isDisabled={isDisabled}
type='submit'
{...props}
localizationKey={props.localizationKey || localizationKeys('formButtonPrimary')}
/>
);
};

const FormReset = (props: PropsOfComponent<typeof Button>) => {
const { isLoading, isDisabled } = useFormState();
return (
<>
<Button
elementDescriptor={descriptors.formButtonReset}
block
variant='ghost'
textVariant='buttonExtraSmallBold'
type='reset'
isDisabled={isLoading || isDisabled}
{...props}
/>
</>
<Button
elementDescriptor={descriptors.formButtonReset}
block
variant='ghost'
textVariant='buttonExtraSmallBold'
type='reset'
isDisabled={isLoading || isDisabled}
{...props}
/>
);
};

Expand Down Expand Up @@ -160,6 +156,31 @@ const PlainInput = (props: CommonInputProps) => {
);
};

const RadioGroup = (
props: Omit<PropsOfComponent<typeof Field.Root>, 'infoText' | 'type' | 'validatePassword' | 'label' | 'placeholder'>,
) => {
const { radioOptions, ...fieldProps } = props;
return (
<Field.Root {...fieldProps}>
<Col
elementDescriptor={descriptors.formFieldRadioGroup}
gap={2}
>
{radioOptions?.map(({ value, description, label }) => (
<Field.RadioItem
key={value}
value={value}
label={label}
description={description}
/>
))}
</Col>

<Field.Feedback />
</Field.Root>
);
};

export const Form = {
Root: FormRoot,
ControlRow: FormControlRow,
Expand All @@ -168,6 +189,7 @@ export const Form = {
*/
Control: FormControl,
PlainInput,
RadioGroup,
SubmitButton: FormSubmit,
ResetButton: FormReset,
};
Expand Down
93 changes: 92 additions & 1 deletion packages/clerk-js/src/ui/elements/RadioGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { useId } from 'react';
import { forwardRef, useId } from 'react';

import type { LocalizationKey } from '../customizables';
import { Col, descriptors, Flex, FormLabel, Input, Text } from '../customizables';
import { sanitizeInputProps, useFormField } from '../primitives/hooks';
import type { PropsOfComponent } from '../styledSystem';

/**
* @deprecated
*/
export const RadioGroup = (
props: PropsOfComponent<typeof Input> & {
radioOptions?: {
Expand All @@ -30,6 +34,9 @@ export const RadioGroup = (
);
};

/**
* @deprecated
*/
const RadioGroupItem = (props: {
inputProps: PropsOfComponent<typeof Input>;
value: string;
Expand Down Expand Up @@ -86,3 +93,87 @@ const RadioGroupItem = (props: {
</Flex>
);
};

const RadioIndicator = forwardRef<HTMLInputElement, { value: string; id: string }>((props, ref) => {
const formField = useFormField();
const { value, placeholder, ...inputProps } = sanitizeInputProps(formField);

return (
<Input
ref={ref}
{...inputProps}
elementDescriptor={descriptors.formFieldRadioInput}
id={props.id}
focusRing={false}
sx={t => ({
width: 'fit-content',
marginTop: t.space.$0x5,
})}
type='radio'
value={props.value}
checked={props.value === value}
/>
);
});

export const RadioLabel = (props: {
label: string | LocalizationKey;
description?: string | LocalizationKey;
id?: string;
}) => {
return (
<FormLabel
elementDescriptor={descriptors.formFieldRadioLabel}
htmlFor={props.id}
sx={t => ({
padding: `${t.space.$none} ${t.space.$2}`,
display: 'flex',
flexDirection: 'column',
})}
>
<Text
elementDescriptor={descriptors.formFieldRadioLabelTitle}
variant='regularMedium'
localizationKey={props.label}
/>

{props.description && (
<Text
elementDescriptor={descriptors.formFieldRadioLabelDescription}
colorScheme='neutral'
variant='smallRegular'
localizationKey={props.description}
/>
)}
</FormLabel>
);
};

export const RadioItem = forwardRef<
HTMLInputElement,
{
value: string;
label: string | LocalizationKey;
description?: string | LocalizationKey;
}
>((props, ref) => {
const randomId = useId();
return (
<Flex
elementDescriptor={descriptors.formFieldRadioGroupItem}
align='start'
>
<RadioIndicator
id={randomId}
ref={ref}
value={props.value}
/>

<RadioLabel
id={randomId}
label={props.label}
description={props.description}
/>
</Flex>
);
});
Loading