mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
fix(console): responsive modal items layout (#2160)
This commit is contained in:
parent
4ce2073692
commit
ac38a7f3ac
9 changed files with 178 additions and 108 deletions
|
@ -6,13 +6,10 @@ import { useTranslation } from 'react-i18next';
|
|||
import * as styles from './index.module.scss';
|
||||
|
||||
const Check = () => (
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="1" y="1" width="18" height="18" rx="9" fill="#4F37F9" />
|
||||
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M8.31476 13.858L5.13295 10.441C4.95568 10.253 4.95568 9.947 5.13295 9.757L5.77568 9.074C5.95295 8.886 6.24113 8.886 6.4184 9.074L8.63657 11.466L13.5811 6.141C13.7584 5.953 14.0465 5.953 14.2238 6.141L14.8665 6.825C15.0438 7.013 15.0438 7.32 14.8665 7.507L8.95748 13.858C8.78021 14.046 8.49203 14.046 8.31476 13.858Z"
|
||||
fill="white"
|
||||
d="M8.66666 1.33334C4.99999 1.33334 1.99999 4.33334 1.99999 8.00001C1.99999 11.6667 4.99999 14.6667 8.66666 14.6667C12.3333 14.6667 15.3333 11.6667 15.3333 8.00001C15.3333 4.33334 12.3333 1.33334 8.66666 1.33334ZM11.4667 6.86668L8.26666 10.0667C7.99999 10.3333 7.59999 10.3333 7.33333 10.0667L5.86666 8.60001C5.59999 8.33334 5.59999 7.93334 5.86666 7.66668C6.13333 7.40001 6.53333 7.40001 6.79999 7.66668L7.79999 8.66668L10.5333 5.93334C10.8 5.66668 11.2 5.66668 11.4667 5.93334C11.7333 6.20001 11.7333 6.60001 11.4667 6.86668Z"
|
||||
fill="#5D34F2"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
@ -29,7 +26,6 @@ export type Props = {
|
|||
type?: 'card' | 'plain';
|
||||
isDisabled?: boolean;
|
||||
disabledLabel?: AdminConsoleKey;
|
||||
size?: 'normal' | 'small';
|
||||
};
|
||||
|
||||
const Radio = ({
|
||||
|
@ -44,7 +40,6 @@ const Radio = ({
|
|||
type,
|
||||
isDisabled,
|
||||
disabledLabel,
|
||||
size = 'normal',
|
||||
}: Props) => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
||||
|
@ -68,7 +63,6 @@ const Radio = ({
|
|||
styles.radio,
|
||||
isChecked && styles.checked,
|
||||
isDisabled && styles.disabled,
|
||||
styles[size],
|
||||
className
|
||||
)}
|
||||
// eslint-disable-next-line jsx-a11y/role-has-required-aria-props
|
||||
|
@ -77,15 +71,23 @@ const Radio = ({
|
|||
onClick={isDisabled ? undefined : onClick}
|
||||
onKeyPress={handleKeyPress}
|
||||
>
|
||||
<div className={styles.content}>
|
||||
<input readOnly disabled type="radio" name={name} value={value} checked={isChecked} />
|
||||
{type === 'card' && <Check />}
|
||||
{type === 'card' && (
|
||||
<div className={styles.indicator}>
|
||||
<Check />
|
||||
</div>
|
||||
)}
|
||||
{children}
|
||||
{type === 'plain' && <div className={styles.indicator} />}
|
||||
{title && t(title)}
|
||||
{isDisabled && disabledLabel && (
|
||||
<div className={styles.disabledLabel}>{t(disabledLabel)}</div>
|
||||
<div className={classNames(styles.indicator, styles.disabledLabel)}>
|
||||
{t(disabledLabel)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -11,18 +11,8 @@
|
|||
}
|
||||
|
||||
.card {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: _.unit(3);
|
||||
|
||||
> .radio {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
min-width: 170px;
|
||||
max-width: 230px;
|
||||
padding: _.unit(5);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: _.unit(3);
|
||||
border-radius: _.unit(4);
|
||||
border: 1px solid transparent;
|
||||
outline: 1px solid var(--color-neutral-90);
|
||||
|
@ -45,51 +35,46 @@
|
|||
box-shadow: var(--shadow-2);
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
|
||||
.indicator {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
|
||||
svg {
|
||||
opacity: 0%;
|
||||
position: absolute;
|
||||
right: _.unit(5);
|
||||
top: _.unit(5);
|
||||
}
|
||||
|
||||
&.small {
|
||||
padding: _.unit(3);
|
||||
|
||||
svg {
|
||||
right: _.unit(3);
|
||||
top: _.unit(3);
|
||||
}
|
||||
}
|
||||
|
||||
.disabledLabel {
|
||||
position: absolute;
|
||||
right: _.unit(3);
|
||||
top: _.unit(3);
|
||||
background: var(--color-neutral-90);
|
||||
padding: _.unit(0.5) _.unit(2);
|
||||
border-radius: 10px;
|
||||
font: var(--font-label-medium);
|
||||
color: var(--color-text);
|
||||
}
|
||||
}
|
||||
|
||||
&.checked {
|
||||
border-color: var(--color-primary);
|
||||
outline: 1px solid var(--color-primary);
|
||||
|
||||
.indicator {
|
||||
svg {
|
||||
opacity: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.plain {
|
||||
font: var(--font-body-medium);
|
||||
|
||||
/* stylelint-disable-next-line no-descending-specificity */
|
||||
> .radio {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
|
||||
/* stylelint-disable-next-line no-descending-specificity */
|
||||
|
@ -97,6 +82,10 @@
|
|||
margin-bottom: _.unit(2);
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.indicator {
|
||||
border-radius: 50%;
|
||||
border: 2px solid var(--color-neutral-60);
|
||||
|
@ -113,8 +102,10 @@
|
|||
border: 2px solid var(--color-layer-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.checked {
|
||||
.content {
|
||||
.indicator {
|
||||
border-color: var(--color-primary);
|
||||
|
||||
|
@ -123,11 +114,13 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
color: var(--color-disabled);
|
||||
|
||||
.content {
|
||||
.indicator {
|
||||
border-color: var(--color-disabled);
|
||||
|
||||
|
@ -138,3 +131,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,19 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
@use '@/scss/dimensions' as dim;
|
||||
|
||||
.radioGroup {
|
||||
margin-top: _.unit(2);
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: _.unit(4);
|
||||
|
||||
@media screen and (max-width: dim.$modal-layout-grid-medium) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
@media screen and (max-width: dim.$modal-layout-grid-small) {
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.error {
|
||||
|
|
|
@ -90,6 +90,7 @@ const CreateForm = ({ onClose }: Props) => {
|
|||
{Object.values(ApplicationType).map((value) => (
|
||||
<Radio key={value} value={value}>
|
||||
<TypeDescription
|
||||
type={value}
|
||||
title={t(`${applicationTypeI18nKey[value]}.title`)}
|
||||
subtitle={t(`${applicationTypeI18nKey[value]}.subtitle`)}
|
||||
description={t(`${applicationTypeI18nKey[value]}.description`)}
|
||||
|
|
|
@ -31,10 +31,14 @@
|
|||
width: 100%;
|
||||
margin-top: _.unit(6);
|
||||
margin-right: 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: _.unit(5);
|
||||
}
|
||||
|
||||
.radio {
|
||||
border-radius: _.unit(2);
|
||||
padding: _.unit(5);
|
||||
width: 240px;
|
||||
max-width: unset;
|
||||
font: var(--font-subhead-2);
|
||||
|
|
|
@ -1,14 +1,20 @@
|
|||
import { ApplicationType } from '@logto/schemas';
|
||||
|
||||
import ApplicationIcon from '@/components/ApplicationIcon';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
description: string;
|
||||
type: ApplicationType;
|
||||
};
|
||||
|
||||
const TypeDescription = ({ title, subtitle, description }: Props) => {
|
||||
const TypeDescription = ({ title, subtitle, description, type }: Props) => {
|
||||
return (
|
||||
<>
|
||||
<ApplicationIcon type={type} />
|
||||
<div className={styles.title}>{title}</div>
|
||||
<div className={styles.subtitle}>{subtitle}</div>
|
||||
<div className={styles.description}>{description}</div>
|
||||
|
|
|
@ -1,15 +1,42 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
@use '@/scss/dimensions' as dim;
|
||||
|
||||
.body {
|
||||
.connectorGroup {
|
||||
gap: _.unit(4);
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
|
||||
.connectorRadio {
|
||||
// Override radio style
|
||||
min-width: 276px;
|
||||
max-width: 276px;
|
||||
width: 276px;
|
||||
height: 96px;
|
||||
@media screen and (max-width: dim.$modal-layout-grid-large) {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
@media screen and (max-width: dim.$modal-layout-grid-medium) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
@media screen and (max-width: dim.$modal-layout-grid-small) {
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
}
|
||||
|
||||
&.medium {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
|
||||
@media screen and (max-width: dim.$modal-layout-grid-small) {
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
&.large {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
|
||||
@media screen and (max-width: dim.$modal-layout-grid-medium) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
@media screen and (max-width: dim.$modal-layout-grid-small) {
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.connector {
|
||||
|
@ -17,14 +44,15 @@
|
|||
display: flex;
|
||||
|
||||
.logo {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: _.unit(3);
|
||||
|
||||
img {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,6 +61,11 @@
|
|||
|
||||
.name {
|
||||
font: var(--font-subhead-2);
|
||||
@include _.multi-line-ellipsis(1);
|
||||
|
||||
&.nameWithRightPadding {
|
||||
padding-right: _.unit(12); /* For check mark and added label */
|
||||
}
|
||||
}
|
||||
|
||||
.connectorId {
|
||||
|
@ -45,7 +78,7 @@
|
|||
font: var(--font-body-small);
|
||||
color: var(--color-text-secondary);
|
||||
margin-top: _.unit(1);
|
||||
@include _.multi-line-ellipsis(3);
|
||||
@include _.multi-line-ellipsis(4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { ConnectorType } from '@logto/schemas';
|
||||
import classNames from 'classnames';
|
||||
import { useMemo, useState } from 'react';
|
||||
import Modal from 'react-modal';
|
||||
|
||||
|
@ -78,6 +79,18 @@ const CreateForm = ({ onClose, isOpen: isFormOpen, type }: Props) => {
|
|||
setActiveConnectorId(undefined);
|
||||
};
|
||||
|
||||
const modalSize = useMemo(() => {
|
||||
if (!groups || groups.length <= 2) {
|
||||
return 'medium';
|
||||
}
|
||||
|
||||
if (groups.length === 3) {
|
||||
return 'large';
|
||||
}
|
||||
|
||||
return 'xlarge';
|
||||
}, [groups]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isFormOpen}
|
||||
|
@ -97,7 +110,7 @@ const CreateForm = ({ onClose, isOpen: isFormOpen, type }: Props) => {
|
|||
/>
|
||||
}
|
||||
className={styles.body}
|
||||
size="xlarge"
|
||||
size={modalSize}
|
||||
onClose={onClose}
|
||||
>
|
||||
{isLoading && 'Loading...'}
|
||||
|
@ -107,24 +120,25 @@ const CreateForm = ({ onClose, isOpen: isFormOpen, type }: Props) => {
|
|||
name="group"
|
||||
value={activeGroupId}
|
||||
type="card"
|
||||
className={styles.connectorGroup}
|
||||
className={classNames(styles.connectorGroup, styles[modalSize])}
|
||||
onChange={handleGroupChange}
|
||||
>
|
||||
{groups.map(({ id, name, logo, description, connectors }) => (
|
||||
<Radio
|
||||
key={id}
|
||||
value={id}
|
||||
className={styles.connectorRadio}
|
||||
isDisabled={connectors.every(({ enabled }) => enabled)}
|
||||
disabledLabel="general.added"
|
||||
size="small"
|
||||
>
|
||||
{groups.map(({ id, name, logo, description, connectors }) => {
|
||||
const isDisabled = connectors.every(({ enabled }) => enabled);
|
||||
|
||||
return (
|
||||
<Radio key={id} value={id} isDisabled={isDisabled} disabledLabel="general.added">
|
||||
<div className={styles.connector}>
|
||||
<div className={styles.logo}>
|
||||
<img src={logo} alt="logo" />
|
||||
</div>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.name}>
|
||||
<div
|
||||
className={classNames(
|
||||
styles.name,
|
||||
isDisabled && styles.nameWithRightPadding
|
||||
)}
|
||||
>
|
||||
<UnnamedTrans resource={name} />
|
||||
</div>
|
||||
<div className={styles.description}>
|
||||
|
@ -133,7 +147,8 @@ const CreateForm = ({ onClose, isOpen: isFormOpen, type }: Props) => {
|
|||
</div>
|
||||
</div>
|
||||
</Radio>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</RadioGroup>
|
||||
)}
|
||||
{activeGroup && (
|
||||
|
|
|
@ -2,4 +2,7 @@ $modal-layout-width-xlarge: 1224px;
|
|||
$modal-layout-width-large: 784px;
|
||||
$modal-layout-width-medium: 600px;
|
||||
$modal-layout-width-small: 352px;
|
||||
$modal-layout-grid-large: 850px;
|
||||
$modal-layout-grid-medium: 668px;
|
||||
$modal-layout-grid-small: 500px;
|
||||
$form-text-field-width: 556px;
|
||||
|
|
Loading…
Reference in a new issue