0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-03-24 22:41:28 -05:00

refactor(console): improve create app form ux (#340)

* refactor(console): improve create app form ux

* refactor(console): update i18n
This commit is contained in:
Gao Sun 2022-03-09 12:24:33 +08:00 committed by GitHub
parent c46fb704bf
commit 1df1aee010
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 70 additions and 26 deletions

View file

@ -1,5 +1,5 @@
import classNames from 'classnames';
import React, { ReactNode } from 'react';
import React, { KeyboardEventHandler, ReactNode, useCallback } from 'react';
import * as styles from './index.module.scss';
@ -22,12 +22,28 @@ export type Props = {
children?: ReactNode;
isChecked?: boolean;
onClick?: () => void;
tabIndex?: number;
};
const Radio = ({ value, title, name, children, isChecked, onClick }: Props) => {
const Radio = ({ value, title, name, children, isChecked, onClick, tabIndex }: Props) => {
const handleKeyPress: KeyboardEventHandler<HTMLDivElement> = useCallback(
(event) => {
if ([' ', 'Enter'].includes(event.key)) {
onClick?.();
event.preventDefault();
}
},
[onClick]
);
return (
<div className={classNames(styles.radio, isChecked && styles.checked)} onClick={onClick}>
<input readOnly type="radio" name={name} value={value} checked={isChecked} />
<div
className={classNames(styles.radio, isChecked && styles.checked)}
tabIndex={tabIndex}
onClick={onClick}
onKeyPress={handleKeyPress}
>
<input readOnly disabled type="radio" name={name} value={value} checked={isChecked} />
<div className={classNames(styles.headline, !children && styles.center)}>
<div className={styles.title}>{title}</div>
<Check />

View file

@ -15,6 +15,7 @@
> .radio {
position: relative;
flex: 1;
min-width: 180px;
max-width: 220px;
padding: _.unit(5);
display: flex;
@ -24,6 +25,12 @@
outline: none;
user-select: none;
cursor: pointer;
transition: border 0.2s ease-in-out;
&:focus,
&:hover {
border-color: var(--color-primary);
}
&:not(:first-child) {
margin-left: _.unit(8);

View file

@ -1,4 +1,4 @@
import React, { Children, cloneElement, isValidElement, ReactNode } from 'react';
import React, { Children, cloneElement, forwardRef, isValidElement, ReactNode } from 'react';
import Radio, { Props as RadioProps } from './Radio';
import * as styles from './index.module.scss';
@ -7,28 +7,33 @@ type Props = {
name: string;
children: ReactNode;
value: string;
// https://github.com/yannickcr/eslint-plugin-react/issues/2856
// eslint-disable-next-line react/require-default-props
onChange?: (value: string) => void;
};
const RadioGroup = ({ name, children, value, onChange }: Props) => {
return (
<div className={styles.radioGroup} tabIndex={0}>
{Children.map(children, (child) => {
if (!isValidElement(child) || child.type !== Radio) {
return child;
}
const RadioGroup = forwardRef<HTMLDivElement, Props>(
({ name, children, value, onChange }: Props, reference) => {
return (
<div ref={reference} className={styles.radioGroup}>
{Children.map(children, (child) => {
if (!isValidElement(child) || child.type !== Radio) {
return child;
}
return cloneElement<RadioProps>(child, {
name,
isChecked: value === child.props.value,
onClick: () => {
onChange?.(child.props.value);
},
});
})}
</div>
);
};
return cloneElement<RadioProps>(child, {
name,
isChecked: value === child.props.value,
onClick: () => {
onChange?.(child.props.value);
},
tabIndex: 0,
});
})}
</div>
);
}
);
export default RadioGroup;
export { default as Radio } from './Radio';

View file

@ -29,3 +29,9 @@
margin-top: _.unit(8);
text-align: right;
}
.error {
font: var(--font-body-2);
color: var(--color-error);
margin-top: _.unit(2);
}

View file

@ -27,9 +27,14 @@ type Props = {
};
const CreateForm = ({ onClose }: Props) => {
const { handleSubmit, control, register } = useForm<FormData>();
const {
field: { onChange, value, name },
handleSubmit,
control,
register,
formState: { errors },
} = useForm<FormData>();
const {
field: { onChange, value, name, ref },
} = useController({ name: 'type', control, rules: { required: true } });
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
@ -51,7 +56,7 @@ const CreateForm = ({ onClose }: Props) => {
</div>
<form className={styles.form} onSubmit={onSubmit}>
<FormField title="admin_console.applications.select_application_type">
<RadioGroup name={name} value={value} onChange={onChange}>
<RadioGroup ref={ref} name={name} value={value} onChange={onChange}>
{Object.values(ApplicationType).map((value) => (
<Radio key={value} title={t(`${applicationTypeI18nKey[value]}.title`)} value={value}>
<TypeDescription
@ -61,6 +66,9 @@ const CreateForm = ({ onClose }: Props) => {
</Radio>
))}
</RadioGroup>
{errors.type?.type === 'required' && (
<div className={styles.error}>{t('applications.no_application_type_selected')}</div>
)}
</FormField>
<FormField
isRequired

View file

@ -52,6 +52,7 @@ const translation = {
application_name: 'Application Name',
application_description: 'Application Description',
select_application_type: 'Select an Application Type',
no_application_type_selected: 'You have to select an application type to proceed.',
client_id: 'Client ID',
type: {
native: {

View file

@ -54,6 +54,7 @@ const translation = {
application_name: 'Application Name',
application_description: 'Application Description',
select_application_type: 'Select an application type',
no_application_type_selected: 'You have to select an application type to proceed.',
client_id: 'Client ID',
type: {
native: {