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

feat(console): app type description

This commit is contained in:
Gao Sun 2022-03-08 12:39:58 +08:00
parent c4dace9a51
commit 3bbfe905bb
No known key found for this signature in database
GPG key ID: 13EBE123E4773688
12 changed files with 113 additions and 50 deletions

View file

@ -34,6 +34,8 @@
--color-component-text: #191c1d;
--color-outline: #78767f;
--color-table-row-selected: rgba(0, 0, 0, 2%);
--color-text-default: #202223;
--color-foggy: #888;
}
$font-family: 'SF UI Text', 'SF Pro Display', sans-serif;

View file

@ -1,5 +1,5 @@
import classNames from 'classnames';
import React, { forwardRef, ReactNode } from 'react';
import React, { ReactNode } from 'react';
import * as styles from './index.module.scss';
@ -15,8 +15,6 @@ const Check = () => (
</svg>
);
// https://github.com/yannickcr/eslint-plugin-react/issues/2856
/* eslint-disable react/require-default-props */
export type Props = {
value: string;
title: string;
@ -25,28 +23,18 @@ export type Props = {
isChecked?: boolean;
onClick?: () => void;
};
/* eslint-enable react/require-default-props */
const Radio = forwardRef<HTMLInputElement, Props>(
({ value, title, name, children, isChecked, onClick }, reference) => {
return (
<div className={classNames(styles.radio, isChecked && styles.checked)} onClick={onClick}>
<input
ref={reference}
readOnly
type="radio"
name={name}
value={value}
checked={isChecked}
/>
<div className={classNames(styles.headline, !children && styles.center)}>
<div className={styles.title}>{title}</div>
<Check />
</div>
{children}
const Radio = ({ value, title, name, children, isChecked, onClick }: Props) => {
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.headline, !children && styles.center)}>
<div className={styles.title}>{title}</div>
<Check />
</div>
);
}
);
{children}
</div>
);
};
export default Radio;

View file

@ -15,13 +15,14 @@
> .radio {
position: relative;
flex: 1;
min-width: 220px;
max-width: 220px;
padding: _.unit(5);
display: flex;
flex-direction: column;
border-radius: _.unit(4);
border: 1px solid var(--color-neutral-90);
outline: none;
user-select: none;
cursor: pointer;
&:not(:first-child) {

View file

@ -0,0 +1,12 @@
import React, { SVGProps } from 'react';
const Close = (props: SVGProps<SVGSVGElement>) => (
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M7.05727 7.05703C7.57797 6.53633 8.42219 6.53633 8.94289 7.05703L16.0001 14.1142L23.0573 7.05703C23.578 6.53633 24.4222 6.53633 24.9429 7.05703C25.4636 7.57773 25.4636 8.42195 24.9429 8.94265L17.8857 15.9998L24.9429 23.057C25.4636 23.5777 25.4636 24.4219 24.9429 24.9426C24.4222 25.4633 23.578 25.4633 23.0573 24.9426L16.0001 17.8855L8.94289 24.9426C8.42219 25.4633 7.57797 25.4633 7.05727 24.9426C6.53657 24.4219 6.53657 23.5777 7.05727 23.057L14.1145 15.9998L7.05727 8.94265C6.53657 8.42195 6.53657 7.57773 7.05727 7.05703Z"
fill="#333333"
/>
</svg>
);
export default Close;

View file

@ -1,23 +1,18 @@
import { ApplicationType } from '@logto/schemas';
import React, { SVGProps } from 'react';
import React from 'react';
import { useController, useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import Card from '@/components/Card';
import CardTitle from '@/components/CardTitle';
import FormField from '@/components/FormField';
import RadioGroup, { Radio } from '@/components/RadioGroup';
import Close from '@/icons/Close';
import { applicationTypeI18nKey } from '@/types/applications';
import TypeDescription from '../TypeDescription';
import * as styles from './index.module.scss';
const Close = (props: SVGProps<SVGSVGElement>) => (
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M7.05727 7.05703C7.57797 6.53633 8.42219 6.53633 8.94289 7.05703L16.0001 14.1142L23.0573 7.05703C23.578 6.53633 24.4222 6.53633 24.9429 7.05703C25.4636 7.57773 25.4636 8.42195 24.9429 8.94265L17.8857 15.9998L24.9429 23.057C25.4636 23.5777 25.4636 24.4219 24.9429 24.9426C24.4222 25.4633 23.578 25.4633 23.0573 24.9426L16.0001 17.8855L8.94289 24.9426C8.42219 25.4633 7.57797 25.4633 7.05727 24.9426C6.53657 24.4219 6.53657 23.5777 7.05727 23.057L14.1145 15.9998L7.05727 8.94265C6.53657 8.42195 6.53657 7.57773 7.05727 7.05703Z"
fill="#333333"
/>
</svg>
);
type FormData = {
type: ApplicationType;
name: string;
@ -33,6 +28,7 @@ const CreateForm = ({ onClose }: Props) => {
const {
field: { onChange, value },
} = useController({ name: 'type', control });
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const onSubmit = handleSubmit((data) => {
console.log(data);
@ -47,9 +43,14 @@ const CreateForm = ({ onClose }: Props) => {
<form className={styles.form} onSubmit={onSubmit}>
<FormField title="admin_console.applications.select_application_type">
<RadioGroup name="application_type" value={value} onChange={onChange}>
<Radio title="Native" value={ApplicationType.Native} />
<Radio title="Single Page Application" value={ApplicationType.SPA} />
<Radio title="Tranditional Web" value={ApplicationType.Traditional} />
{Object.values(ApplicationType).map((value) => (
<Radio key={value} title={t(`${applicationTypeI18nKey[value]}.title`)} value={value}>
<TypeDescription
subtitle={t(`${applicationTypeI18nKey[value]}.subtitle`)}
description={t(`${applicationTypeI18nKey[value]}.description`)}
/>
</Radio>
))}
</RadioGroup>
</FormField>
<button type="submit">Submit</button>

View file

@ -0,0 +1,17 @@
@use '@/scss/underscore' as _;
.subtitle,
.description {
margin-top: _.unit(3);
flex: 2;
}
.subtitle {
font: var(--font-body);
color: var(--color-text-default);
}
.description {
font: var(--font-caption);
color: var(--color-foggy);
}

View file

@ -0,0 +1,19 @@
import React from 'react';
import * as styles from './index.module.scss';
type Props = {
subtitle: string;
description: string;
};
const TypeDescription = ({ subtitle, description }: Props) => {
return (
<>
<div className={styles.subtitle}>{subtitle}</div>
<div className={styles.description}>{description}</div>
</>
);
};
export default TypeDescription;

View file

@ -68,7 +68,7 @@ const Applications = () => {
<td>
<ItemPreview
title={name}
subtitle={String(t(applicationTypeI18nKey[type]))}
subtitle={t(`${applicationTypeI18nKey[type]}.title`)}
icon={<ImagePlaceholder />}
/>
</td>

View file

@ -2,7 +2,7 @@
position: absolute;
left: 50%;
top: 50%;
transform: translateX(-50%) translateY(-50%);
transform: translateX(-50%, -50%);
outline: none;
}

View file

@ -1,8 +1,7 @@
import { AdminConsoleKey } from '@logto/phrases';
import { ApplicationType } from '@logto/schemas';
export const applicationTypeI18nKey: Record<ApplicationType, AdminConsoleKey> = {
export const applicationTypeI18nKey = Object.freeze({
[ApplicationType.Native]: 'applications.type.native',
[ApplicationType.SPA]: 'applications.type.spa',
[ApplicationType.Traditional]: 'applications.type.tranditional',
};
[ApplicationType.Traditional]: 'applications.type.traditional',
} as const);

View file

@ -50,9 +50,21 @@ const translation = {
select_application_type: 'Select an application type',
client_id: 'Client ID',
type: {
native: 'Native App',
spa: 'Single Page App',
tranditional: 'Tranditional Web App',
native: {
title: 'Native',
subtitle: 'Mobile, desktop, CLI and smart device apps running natively.',
description: 'E.g.: iOS, Electron, Apple TV apps',
},
spa: {
title: 'Single Page App',
subtitle: 'A JavaScript front-end app that uses an API.',
description: 'E.g.: Angular, React, Vue',
},
traditional: {
title: 'Tranditional Web',
subtitle: 'Traditional web app using redirects.',
description: 'E.g.: Node.js, Express, ASP.NET, Java, PHP',
},
},
},
api_resources: {

View file

@ -52,9 +52,21 @@ const translation = {
select_application_type: 'Select an application type',
client_id: 'Client ID',
type: {
native: 'Native App',
spa: 'Single Page App',
tranditional: 'Tranditional Web App',
native: {
title: 'Native',
subtitle: 'Mobile, desktop, CLI and smart device apps running natively.',
description: 'E.g.: iOS, Electron, Apple TV apps',
},
spa: {
title: 'Single Page App',
subtitle: 'A JavaScript front-end app that uses an API.',
description: 'E.g.: Angular, React, Vue',
},
traditional: {
title: 'Tranditional Web',
subtitle: 'Traditional web app using redirects.',
description: 'E.g.: Node.js, Express, ASP.NET, Java, PHP',
},
},
},
api_resources: {