0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-03-31 22:51:25 -05:00

style(console): fix multi-text input margin (#2546)

This commit is contained in:
Xiao Yijun 2022-11-29 18:44:50 +08:00 committed by GitHub
parent cb152a2c07
commit 671a27d147
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 212 additions and 147 deletions

View file

@ -11,21 +11,29 @@ import Spacer from '../Spacer';
import Tooltip from '../Tooltip';
import * as styles from './index.module.scss';
type Props = {
export type Props = {
title: AdminConsoleKey | ReactElement<typeof DangerousRaw>;
children: ReactNode;
isRequired?: boolean;
className?: string;
headlineClassName?: string;
tooltip?: AdminConsoleKey;
};
const FormField = ({ title, children, isRequired, className, tooltip }: Props) => {
const FormField = ({
title,
children,
isRequired,
className,
tooltip,
headlineClassName,
}: Props) => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const tipRef = useRef<HTMLDivElement>(null);
return (
<div className={classNames(styles.field, className)}>
<div className={styles.headline}>
<div className={classNames(styles.headline, headlineClassName)}>
<div className={styles.title}>{typeof title === 'string' ? t(title) : title}</div>
{tooltip && (
<div ref={tipRef} className={styles.icon}>

View file

@ -5,14 +5,20 @@
margin-top: _.unit(2);
}
.firstFieldWithMultiInputs {
padding-right: _.unit(9);
}
.deletableInput {
display: flex;
align-items: center;
> :first-child {
@include _.form-text-field;
margin-right: _.unit(2);
flex-shrink: 0;
flex: 1;
}
> :not(:first-child) {
margin-left: _.unit(2);
}
}

View file

@ -1,4 +1,6 @@
import type { AdminConsoleKey } from '@logto/phrases';
import { conditional } from '@silverhand/essentials';
import classNames from 'classnames';
import type { KeyboardEvent } from 'react';
import { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
@ -12,16 +14,25 @@ import TextInput from '../TextInput';
import * as styles from './index.module.scss';
import type { MultiTextInputError } from './types';
type Props = {
export type Props = {
title: AdminConsoleKey;
value?: string[];
onChange: (value: string[]) => void;
onKeyPress?: (event: KeyboardEvent<HTMLInputElement>) => void;
error?: MultiTextInputError;
placeholder?: string;
className?: string;
};
const MultiTextInput = ({ title, value, onChange, onKeyPress, error, placeholder }: Props) => {
const MultiTextInput = ({
title,
value,
onChange,
onKeyPress,
error,
placeholder,
className,
}: Props) => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const [deleteFieldIndex, setDeleteFieldIndex] = useState<number>();
@ -47,10 +58,15 @@ const MultiTextInput = ({ title, value, onChange, onKeyPress, error, placeholder
};
return (
<div className={styles.multilineInput}>
<div className={classNames(styles.multilineInput, className)}>
{fields.map((fieldValue, fieldIndex) => (
// eslint-disable-next-line react/no-array-index-key
<div key={fieldIndex}>
<div
// eslint-disable-next-line react/no-array-index-key
key={fieldIndex}
className={conditional(
fields.length > 1 && fieldIndex === 0 && styles.firstFieldWithMultiInputs
)}
>
<div className={styles.deletableInput}>
<TextInput
hasError={Boolean(

View file

@ -0,0 +1,5 @@
@use '@/scss/underscore' as _;
.headlineWithMultiInputs {
padding-right: _.unit(9);
}

View file

@ -0,0 +1,34 @@
import { conditional } from '@silverhand/essentials';
import type { Props as FormFieldProps } from '@/components/FormField';
import FormField from '@/components/FormField';
import type { Props as MultiTextInputProps } from '@/components/MultiTextInput';
import MultiTextInput from '../MultiTextInput';
import * as styles from './index.module.scss';
type Props = MultiTextInputProps &
Pick<FormFieldProps, 'isRequired' | 'tooltip'> & {
formFieldClassName?: FormFieldProps['className'];
};
const MultiTextInputField = ({
title,
isRequired,
tooltip,
formFieldClassName,
value,
...rest
}: Props) => (
<FormField
title={title}
isRequired={isRequired}
tooltip={tooltip}
className={formFieldClassName}
headlineClassName={conditional(value && value.length > 1 && styles.headlineWithMultiInputs)}
>
<MultiTextInput title={title} value={value} {...rest} />
</FormField>
);
export default MultiTextInputField;

View file

@ -5,13 +5,16 @@
align-items: flex-start;
position: relative;
.field {
flex: 1;
.multiTextInput {
flex: 1;
}
}
.saveButton {
position: absolute;
left: calc(556px + _.unit(3));
top: 0;
flex-shrink: 0;
margin: _.unit(6) 0 0 _.unit(2);
}
}
.field {
width: 556px;
}

View file

@ -9,8 +9,8 @@ import useSWR from 'swr';
import Button from '@/components/Button';
import FormField from '@/components/FormField';
import MultiTextInput from '@/components/MultiTextInput';
import { convertRhfErrorMessage, createValidatorForRhf } from '@/components/MultiTextInput/utils';
import MultiTextInputField from '@/components/MultiTextInputField';
import TextInput from '@/components/TextInput';
import type { RequestError } from '@/hooks/use-api';
import useApi from '@/hooks/use-api';
@ -69,31 +69,33 @@ const UriInputField = ({ appId, name, title, isSingle = false }: Props) => {
return (
<FormProvider {...methods}>
<form>
<FormField isRequired={name === 'redirectUris'} className={styles.field} title={title}>
<Controller
name={name}
control={control}
defaultValue={data?.oidcClientMetadata[name]}
rules={{
validate: createValidatorForRhf({
required: t(
isSingle
? 'errors.required_field_missing'
: 'errors.required_field_missing_plural',
{ field: title }
),
pattern: {
verify: (value) => !value || uriValidator(value),
message: t('errors.invalid_uri_format'),
},
}),
}}
render={({ field: { onChange, value = [] }, fieldState: { error, isDirty } }) => {
const errorObject = convertRhfErrorMessage(error?.message);
<Controller
name={name}
control={control}
defaultValue={data?.oidcClientMetadata[name]}
rules={{
validate: createValidatorForRhf({
required: t(
isSingle ? 'errors.required_field_missing' : 'errors.required_field_missing_plural',
{ field: title }
),
pattern: {
verify: (value) => !value || uriValidator(value),
message: t('errors.invalid_uri_format'),
},
}),
}}
render={({ field: { onChange, value = [] }, fieldState: { error, isDirty } }) => {
const errorObject = convertRhfErrorMessage(error?.message);
return (
<div ref={ref} className={styles.wrapper}>
{isSingle && (
return (
<div ref={ref} className={styles.wrapper}>
{isSingle && (
<FormField
isRequired={name === 'redirectUris'}
className={styles.field}
title={title}
>
<TextInput
className={styles.field}
value={value[0]}
@ -105,31 +107,34 @@ const UriInputField = ({ appId, name, title, isSingle = false }: Props) => {
onKeyPress(event, value);
}}
/>
)}
{!isSingle && (
<MultiTextInput
title={title}
value={value}
error={errorObject}
onChange={onChange}
onKeyPress={(event) => {
onKeyPress(event, value);
}}
/>
)}
<Button
className={styles.saveButton}
disabled={!isDirty}
isLoading={isSubmitting}
title="general.save"
type="primary"
onClick={handleSubmit(async () => onSubmit(value))}
</FormField>
)}
{!isSingle && (
<MultiTextInputField
isRequired={name === 'redirectUris'}
formFieldClassName={styles.field}
title={title}
value={value}
error={errorObject}
className={styles.multiTextInput}
onChange={onChange}
onKeyPress={(event) => {
onKeyPress(event, value);
}}
/>
</div>
);
}}
/>
</FormField>
)}
<Button
className={styles.saveButton}
disabled={!isDirty}
isLoading={isSubmitting}
title="general.save"
type="primary"
onClick={handleSubmit(async () => onSubmit(value))}
/>
</div>
);
}}
/>
</form>
</FormProvider>
);

View file

@ -6,9 +6,9 @@ import { useTranslation } from 'react-i18next';
import CopyToClipboard from '@/components/CopyToClipboard';
import FormCard from '@/components/FormCard';
import FormField from '@/components/FormField';
import MultiTextInput from '@/components/MultiTextInput';
import type { MultiTextInputRule } from '@/components/MultiTextInput/types';
import { createValidatorForRhf, convertRhfErrorMessage } from '@/components/MultiTextInput/utils';
import MultiTextInputField from '@/components/MultiTextInputField';
import TextInput from '@/components/TextInput';
import { uriOriginValidator } from '@/utilities/validator';
@ -71,89 +71,77 @@ const Settings = ({ data }: Props) => {
</FormField>
)}
{applicationType !== ApplicationType.MachineToMachine && (
<FormField
isRequired
title="application_details.redirect_uris"
tooltip="application_details.redirect_uri_tip"
>
<Controller
name="oidcClientMetadata.redirectUris"
control={control}
defaultValue={[]}
rules={{
validate: createValidatorForRhf({
...uriPatternRules,
required: t('application_details.redirect_uri_required'),
}),
}}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<MultiTextInput
title="application_details.redirect_uris"
value={value}
error={convertRhfErrorMessage(error?.message)}
placeholder={
applicationType === ApplicationType.Native
? t('application_details.redirect_uri_placeholder_native')
: t('application_details.redirect_uri_placeholder')
}
onChange={onChange}
/>
)}
/>
</FormField>
<Controller
name="oidcClientMetadata.redirectUris"
control={control}
defaultValue={[]}
rules={{
validate: createValidatorForRhf({
...uriPatternRules,
required: t('application_details.redirect_uri_required'),
}),
}}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<MultiTextInputField
isRequired
title="application_details.redirect_uris"
tooltip="application_details.redirect_uri_tip"
value={value}
error={convertRhfErrorMessage(error?.message)}
placeholder={
applicationType === ApplicationType.Native
? t('application_details.redirect_uri_placeholder_native')
: t('application_details.redirect_uri_placeholder')
}
onChange={onChange}
/>
)}
/>
)}
{applicationType !== ApplicationType.MachineToMachine && (
<FormField
title="application_details.post_sign_out_redirect_uris"
tooltip="application_details.post_sign_out_redirect_uri_tip"
>
<Controller
name="oidcClientMetadata.postLogoutRedirectUris"
control={control}
defaultValue={[]}
rules={{
validate: createValidatorForRhf(uriPatternRules),
}}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<MultiTextInput
title="application_details.post_sign_out_redirect_uris"
value={value}
error={convertRhfErrorMessage(error?.message)}
placeholder={t('application_details.post_sign_out_redirect_uri_placeholder')}
onChange={onChange}
/>
)}
/>
</FormField>
<Controller
name="oidcClientMetadata.postLogoutRedirectUris"
control={control}
defaultValue={[]}
rules={{
validate: createValidatorForRhf(uriPatternRules),
}}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<MultiTextInputField
title="application_details.post_sign_out_redirect_uris"
tooltip="application_details.post_sign_out_redirect_uri_tip"
value={value}
error={convertRhfErrorMessage(error?.message)}
placeholder={t('application_details.post_sign_out_redirect_uri_placeholder')}
onChange={onChange}
/>
)}
/>
)}
{applicationType !== ApplicationType.MachineToMachine && (
<FormField
title="application_details.cors_allowed_origins"
tooltip="application_details.cors_allowed_origins_tip"
>
<Controller
name="customClientMetadata.corsAllowedOrigins"
control={control}
defaultValue={[]}
rules={{
validate: createValidatorForRhf({
pattern: {
verify: (value) => !value || uriOriginValidator(value),
message: t('errors.invalid_origin_format'),
},
}),
}}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<MultiTextInput
title="application_details.cors_allowed_origins"
value={value}
error={convertRhfErrorMessage(error?.message)}
placeholder={t('application_details.cors_allowed_origins_placeholder')}
onChange={onChange}
/>
)}
/>
</FormField>
<Controller
name="customClientMetadata.corsAllowedOrigins"
control={control}
defaultValue={[]}
rules={{
validate: createValidatorForRhf({
pattern: {
verify: (value) => !value || uriOriginValidator(value),
message: t('errors.invalid_origin_format'),
},
}),
}}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<MultiTextInputField
title="application_details.cors_allowed_origins"
tooltip="application_details.cors_allowed_origins_tip"
value={value}
error={convertRhfErrorMessage(error?.message)}
placeholder={t('application_details.cors_allowed_origins_placeholder')}
onChange={onChange}
/>
)}
/>
)}
</FormCard>
);