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:
parent
c46fb704bf
commit
1df1aee010
7 changed files with 70 additions and 26 deletions
|
@ -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 />
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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: {
|
||||
|
|
Loading…
Add table
Reference in a new issue