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

feat(console): plain radio (#548)

* feat(console): plain radio

* refactor: title and children
This commit is contained in:
Wang Sijie 2022-04-15 12:18:42 +08:00 committed by GitHub
parent 14cdddc18e
commit dc32f1dc9f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 108 additions and 67 deletions

View file

@ -1,5 +1,7 @@
import { AdminConsoleKey } from '@logto/phrases';
import classNames from 'classnames';
import React, { KeyboardEventHandler, ReactNode, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import * as styles from './index.module.scss';
@ -18,12 +20,13 @@ const Check = () => (
export type Props = {
className?: string;
value: string;
title?: string;
title?: AdminConsoleKey;
name?: string;
children?: ReactNode;
isChecked?: boolean;
onClick?: () => void;
tabIndex?: number;
type?: 'card' | 'plain';
};
const Radio = ({
@ -35,7 +38,10 @@ const Radio = ({
isChecked,
onClick,
tabIndex,
type,
}: Props) => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const handleKeyPress: KeyboardEventHandler<HTMLDivElement> = useCallback(
(event) => {
if ([' ', 'Enter'].includes(event.key)) {
@ -54,13 +60,10 @@ const Radio = ({
onKeyPress={handleKeyPress}
>
<input readOnly disabled type="radio" name={name} value={value} checked={isChecked} />
{title && (
<div className={classNames(styles.headline, !children && styles.center)}>
<div className={styles.title}>{title}</div>
</div>
)}
<Check />
{type === 'card' && <Check />}
{children}
{type === 'plain' && <div className={styles.indicator} />}
{title && t(title)}
</div>
);
};

View file

@ -1,72 +1,96 @@
@use '@/scss/underscore' as _;
.radioGroup {
display: flex;
flex-wrap: wrap;
margin: 0 _.unit(-8) _.unit(-8) 0;
input[type='radio'] {
appearance: none;
position: absolute;
margin: 0;
width: 0;
height: 0;
}
> .radio {
position: relative;
flex: 1;
min-width: 180px;
max-width: 220px;
padding: _.unit(5);
&.card {
display: flex;
flex-direction: column;
border-radius: _.unit(4);
border: 1px solid var(--color-neutral-90);
outline: none;
user-select: none;
cursor: pointer;
transition: border 0.2s ease-in-out;
margin: 0 _.unit(8) _.unit(8) 0;
flex-wrap: wrap;
margin: 0 _.unit(-8) _.unit(-8) 0;
&:focus,
&:hover {
border-color: var(--color-primary);
}
> .radio {
position: relative;
flex: 1;
min-width: 180px;
max-width: 220px;
padding: _.unit(5);
display: flex;
flex-direction: column;
border-radius: _.unit(4);
border: 1px solid transparent;
outline: 1px solid var(--color-neutral-90);
user-select: none;
cursor: pointer;
margin: 0 _.unit(8) _.unit(8) 0;
svg {
opacity: 0%;
position: absolute;
right: _.unit(5);
top: _.unit(5);
}
&.checked {
border-color: var(--color-primary);
outline: 1px solid var(--color-primary);
&:focus,
&:hover {
outline: 1px solid var(--color-primary);
box-shadow: var(--shadow-light-s2);
}
svg {
opacity: 100%;
opacity: 0%;
position: absolute;
right: _.unit(5);
top: _.unit(5);
}
&.checked {
border-color: var(--color-primary);
outline: 1px solid var(--color-primary);
svg {
opacity: 100%;
}
}
}
}
.headline {
flex: 1;
&.plain {
font: var(--font-body-medium);
> .radio {
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
&.center {
align-items: center;
&:not(:last-child) {
margin-bottom: _.unit(2);
}
> *:not(:first-child) {
margin-left: _.unit(3);
.indicator {
border-radius: 50%;
border: 2px solid var(--color-neutral-60);
display: inline-block;
margin-right: _.unit(2);
&::before {
content: '';
background: var(--color-layer-1);
width: 10px;
height: 10px;
display: block;
border-radius: 50%;
border: 2px solid var(--color-layer-1);
}
}
}
.title {
font: var(--font-subhead-2);
color: var(--color-text);
}
&.checked {
.indicator {
border-color: var(--color-primary);
input[type='radio'] {
appearance: none;
position: absolute;
margin: 0;
width: 0;
height: 0;
&::before {
background: var(--color-primary);
}
}
}
}
}
}

View file

@ -15,16 +15,17 @@ type Props = {
name: string;
children: ReactNode;
value?: string;
type?: 'card' | 'plain';
className?: string;
onChange?: (value: string) => void;
};
const RadioGroup = (
{ name, children, value, className, onChange }: Props,
{ name, children, value, className, onChange, type = 'plain' }: Props,
reference?: LegacyRef<HTMLDivElement>
) => {
return (
<div ref={reference} className={classNames(styles.radioGroup, className)}>
<div ref={reference} className={classNames(styles.radioGroup, styles[type], className)}>
{Children.map(children, (child) => {
if (!isValidElement(child) || child.type !== Radio) {
return child;
@ -37,6 +38,7 @@ const RadioGroup = (
onChange?.(child.props.value);
},
tabIndex: 0,
type,
});
})}
</div>

View file

@ -95,10 +95,11 @@ const CreateForm = ({ onClose }: Props) => {
>
<form>
<FormField title="admin_console.applications.select_application_type">
<RadioGroup ref={ref} name={name} value={value} onChange={onChange}>
<RadioGroup ref={ref} name={name} value={value} type="card" onChange={onChange}>
{Object.values(ApplicationType).map((value) => (
<Radio key={value} title={t(`${applicationTypeI18nKey[value]}.title`)} value={value}>
<Radio key={value} value={value}>
<TypeDescription
title={t(`${applicationTypeI18nKey[value]}.title`)}
subtitle={t(`${applicationTypeI18nKey[value]}.subtitle`)}
description={t(`${applicationTypeI18nKey[value]}.description`)}
/>

View file

@ -39,10 +39,13 @@ const LibrarySelector = ({
className={styles.radioGroup}
name="libraryName"
value={libraryName}
type="card"
onChange={onChange}
>
{Object.values(SupportedJavascriptLibraries).map((library) => (
<Radio key={library} className={styles.radio} title={library} value={library} />
<Radio key={library} className={styles.radio} value={library}>
{library}
</Radio>
))}
</RadioGroup>
<div className={styles.buttonWrapper}>

View file

@ -1,5 +1,10 @@
@use '@/scss/underscore' as _;
.title {
font: var(--font-subhead-2);
color: var(--color-text);
}
.subtitle,
.description {
margin-top: _.unit(3);

View file

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

View file

@ -46,10 +46,11 @@ const BrandingForm = () => {
defaultValue={BrandingStyle.Logo_Slogan}
render={({ field: { onChange, value, name } }) => (
<RadioGroup value={value} name={name} onChange={onChange}>
<Radio value={BrandingStyle.Logo_Slogan}>
{t('sign_in_exp.branding.styles.logo_slogan')}
</Radio>
<Radio value={BrandingStyle.Logo}>{t('sign_in_exp.branding.styles.logo')}</Radio>
<Radio
value={BrandingStyle.Logo_Slogan}
title="sign_in_exp.branding.styles.logo_slogan"
/>
<Radio value={BrandingStyle.Logo} title="sign_in_exp.branding.styles.logo" />
</RadioGroup>
)}
/>