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:
parent
14cdddc18e
commit
dc32f1dc9f
8 changed files with 108 additions and 67 deletions
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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`)}
|
||||
/>
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.title {
|
||||
font: var(--font-subhead-2);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.subtitle,
|
||||
.description {
|
||||
margin-top: _.unit(3);
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
|
|
|
@ -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>
|
||||
)}
|
||||
/>
|
||||
|
|
Loading…
Add table
Reference in a new issue