mirror of
https://github.com/logto-io/logto.git
synced 2025-03-10 22:22:45 -05:00
style(ui): main page layout update (#2915)
This commit is contained in:
parent
d559937e8b
commit
089b138c77
10 changed files with 35 additions and 96 deletions
|
@ -12,10 +12,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Main Layout */
|
/* Main Layout */
|
||||||
.container {
|
.viewBox {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
min-height: 100%;
|
||||||
@include _.flex_column(center, normal);
|
@include _.flex_column(center, normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,6 +33,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(body.mobile) {
|
:global(body.mobile) {
|
||||||
|
.container {
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
|
}
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
|
@ -36,6 +44,10 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
background: var(--color-bg-body);
|
background: var(--color-bg-body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.placeHolder {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(body.desktop) {
|
:global(body.desktop) {
|
||||||
|
@ -47,6 +59,5 @@
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
background: var(--color-bg-float);
|
background: var(--color-bg-float);
|
||||||
box-shadow: var(--color-shadow-2);
|
box-shadow: var(--color-shadow-2);
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,17 @@
|
||||||
import { useContext } from 'react';
|
|
||||||
import { Outlet } from 'react-router-dom';
|
import { Outlet } from 'react-router-dom';
|
||||||
|
|
||||||
import { PageContext } from '@/hooks/use-page-context';
|
|
||||||
|
|
||||||
import * as styles from './index.module.scss';
|
import * as styles from './index.module.scss';
|
||||||
|
|
||||||
const AppContent = () => {
|
const AppContent = () => {
|
||||||
const { platform } = useContext(PageContext);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.viewBox}>
|
||||||
{platform === 'web' && <div className={styles.placeHolder} />}
|
<div className={styles.container}>
|
||||||
<main className={styles.main}>
|
<div className={styles.placeHolder} />
|
||||||
<Outlet />
|
<main className={styles.main}>
|
||||||
</main>
|
<Outlet />
|
||||||
{platform === 'web' && <div className={styles.placeHolder} />}
|
</main>
|
||||||
|
<div className={styles.placeHolder} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,43 +1,20 @@
|
||||||
import { fireEvent } from '@testing-library/react';
|
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
|
|
||||||
import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
|
import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
|
||||||
import SettingsProvider from '@/__mocks__/RenderWithPageContext/SettingsProvider';
|
import SettingsProvider from '@/__mocks__/RenderWithPageContext/SettingsProvider';
|
||||||
import { socialConnectors } from '@/__mocks__/logto';
|
import { socialConnectors } from '@/__mocks__/logto';
|
||||||
|
|
||||||
import SocialSignInList, { defaultSize } from '.';
|
import SocialSignInList from '.';
|
||||||
|
|
||||||
describe('SocialSignInList', () => {
|
describe('SocialSignInList', () => {
|
||||||
it('less than three connectors', () => {
|
it('Display connectors', () => {
|
||||||
const { container } = renderWithPageContext(
|
const { container } = renderWithPageContext(
|
||||||
<SettingsProvider>
|
<SettingsProvider>
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<SocialSignInList socialConnectors={socialConnectors.slice(0, defaultSize)} />
|
<SocialSignInList socialConnectors={socialConnectors} />
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
</SettingsProvider>
|
</SettingsProvider>
|
||||||
);
|
);
|
||||||
expect(container.querySelectorAll('button')).toHaveLength(
|
expect(container.querySelectorAll('button')).toHaveLength(socialConnectors.length);
|
||||||
socialConnectors.slice(0, defaultSize).length
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('more than three connectors', () => {
|
|
||||||
const { container } = renderWithPageContext(
|
|
||||||
<SettingsProvider>
|
|
||||||
<MemoryRouter>
|
|
||||||
<SocialSignInList isCollapseEnabled socialConnectors={socialConnectors} />
|
|
||||||
</MemoryRouter>
|
|
||||||
</SettingsProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(container.querySelectorAll('button')).toHaveLength(defaultSize + 1); // Expand button
|
|
||||||
|
|
||||||
const expandButton = container.querySelector('svg');
|
|
||||||
|
|
||||||
if (expandButton) {
|
|
||||||
fireEvent.click(expandButton);
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(container.querySelectorAll('button')).toHaveLength(socialConnectors.length + 1); // Expand button
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,40 +1,23 @@
|
||||||
import type { ConnectorMetadata } from '@logto/schemas';
|
import type { ConnectorMetadata } from '@logto/schemas';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useState, useMemo } from 'react';
|
|
||||||
|
|
||||||
import ExpandIcon from '@/assets/icons/expand-icon.svg';
|
|
||||||
import IconButton from '@/components/Button/IconButton';
|
|
||||||
import SocialLinkButton from '@/components/Button/SocialLinkButton';
|
import SocialLinkButton from '@/components/Button/SocialLinkButton';
|
||||||
import useSocial from '@/hooks/use-social';
|
import useSocial from '@/hooks/use-social';
|
||||||
import { getLogoUrl } from '@/utils/logo';
|
import { getLogoUrl } from '@/utils/logo';
|
||||||
|
|
||||||
import * as styles from './index.module.scss';
|
import * as styles from './index.module.scss';
|
||||||
|
|
||||||
export const defaultSize = 4;
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
className?: string;
|
className?: string;
|
||||||
socialConnectors?: ConnectorMetadata[];
|
socialConnectors?: ConnectorMetadata[];
|
||||||
isCollapseEnabled?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const SocialSignInList = ({ className, socialConnectors = [], isCollapseEnabled }: Props) => {
|
const SocialSignInList = ({ className, socialConnectors = [] }: Props) => {
|
||||||
const [expand, setExpand] = useState(false);
|
|
||||||
const { invokeSocialSignIn, theme } = useSocial();
|
const { invokeSocialSignIn, theme } = useSocial();
|
||||||
const isOverSize = socialConnectors.length > defaultSize;
|
|
||||||
const displayAll = !isOverSize || !isCollapseEnabled;
|
|
||||||
|
|
||||||
const displayConnectors = useMemo(() => {
|
|
||||||
if (displayAll || expand) {
|
|
||||||
return socialConnectors;
|
|
||||||
}
|
|
||||||
|
|
||||||
return socialConnectors.slice(0, defaultSize);
|
|
||||||
}, [displayAll, expand, socialConnectors]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames(styles.socialLinkList, className)}>
|
<div className={classNames(styles.socialLinkList, className)}>
|
||||||
{displayConnectors.map((connector) => {
|
{socialConnectors.map((connector) => {
|
||||||
const { id, name, logo: logoUrl, logoDark: darkLogoUrl, target } = connector;
|
const { id, name, logo: logoUrl, logoDark: darkLogoUrl, target } = connector;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -50,16 +33,6 @@ const SocialSignInList = ({ className, socialConnectors = [], isCollapseEnabled
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{!displayAll && (
|
|
||||||
<IconButton
|
|
||||||
className={classNames(styles.expandIcon, expand && styles.expanded)}
|
|
||||||
onClick={() => {
|
|
||||||
setExpand(!expand);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ExpandIcon />
|
|
||||||
</IconButton>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -15,13 +15,6 @@
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
:global(body.mobile) {
|
|
||||||
.createAccount {
|
|
||||||
padding-bottom: env(safe-area-inset-bottom);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(body.desktop) {
|
:global(body.desktop) {
|
||||||
.placeHolder {
|
.placeHolder {
|
||||||
flex: 0;
|
flex: 0;
|
||||||
|
|
|
@ -4,7 +4,6 @@ import { MemoryRouter } from 'react-router-dom';
|
||||||
import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
|
import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
|
||||||
import SettingsProvider from '@/__mocks__/RenderWithPageContext/SettingsProvider';
|
import SettingsProvider from '@/__mocks__/RenderWithPageContext/SettingsProvider';
|
||||||
import { mockSignInExperienceSettings } from '@/__mocks__/logto';
|
import { mockSignInExperienceSettings } from '@/__mocks__/logto';
|
||||||
import { defaultSize } from '@/containers/SocialSignIn/SocialSignInList';
|
|
||||||
import Register from '@/pages/Register';
|
import Register from '@/pages/Register';
|
||||||
|
|
||||||
jest.mock('i18next', () => ({
|
jest.mock('i18next', () => ({
|
||||||
|
@ -24,7 +23,9 @@ describe('<Register />', () => {
|
||||||
expect(container.querySelector('input[name="new-username"]')).not.toBeNull();
|
expect(container.querySelector('input[name="new-username"]')).not.toBeNull();
|
||||||
|
|
||||||
// Social
|
// Social
|
||||||
expect(queryAllByText('action.sign_in_with')).toHaveLength(defaultSize);
|
expect(queryAllByText('action.sign_in_with')).toHaveLength(
|
||||||
|
mockSignInExperienceSettings.socialConnectors.length
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('renders with email passwordless as primary', async () => {
|
test('renders with email passwordless as primary', async () => {
|
||||||
|
|
|
@ -41,11 +41,7 @@ const Register = () => {
|
||||||
signUpMethods.length > 0 && socialConnectors.length > 0 && (
|
signUpMethods.length > 0 && socialConnectors.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<Divider label="description.or" className={styles.divider} />
|
<Divider label="description.or" className={styles.divider} />
|
||||||
<SocialSignInList
|
<SocialSignInList socialConnectors={socialConnectors} className={styles.main} />
|
||||||
isCollapseEnabled
|
|
||||||
socialConnectors={socialConnectors}
|
|
||||||
className={styles.main}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,12 +17,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
:global(body.mobile) {
|
|
||||||
.createAccount {
|
|
||||||
padding-bottom: env(safe-area-inset-bottom);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(body.desktop) {
|
:global(body.desktop) {
|
||||||
.placeHolder {
|
.placeHolder {
|
||||||
flex: 0;
|
flex: 0;
|
||||||
|
|
|
@ -8,7 +8,6 @@ import {
|
||||||
emailSignInMethod,
|
emailSignInMethod,
|
||||||
phoneSignInMethod,
|
phoneSignInMethod,
|
||||||
} from '@/__mocks__/logto';
|
} from '@/__mocks__/logto';
|
||||||
import { defaultSize } from '@/containers/SocialSignIn/SocialSignInList';
|
|
||||||
import SignIn from '@/pages/SignIn';
|
import SignIn from '@/pages/SignIn';
|
||||||
|
|
||||||
jest.mock('i18next', () => ({
|
jest.mock('i18next', () => ({
|
||||||
|
@ -32,7 +31,9 @@ describe('<SignIn />', () => {
|
||||||
expect(queryByText('secondary.sign_in_with')).not.toBeNull();
|
expect(queryByText('secondary.sign_in_with')).not.toBeNull();
|
||||||
|
|
||||||
// Social
|
// Social
|
||||||
expect(queryAllByText('action.sign_in_with')).toHaveLength(defaultSize);
|
expect(queryAllByText('action.sign_in_with')).toHaveLength(
|
||||||
|
mockSignInExperienceSettings.socialConnectors.length
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('renders with email passwordless as primary', async () => {
|
test('renders with email passwordless as primary', async () => {
|
||||||
|
|
|
@ -42,11 +42,7 @@ const SignIn = () => {
|
||||||
signInMethods.length > 0 && socialConnectors.length > 0 && (
|
signInMethods.length > 0 && socialConnectors.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<Divider label="description.or" className={styles.divider} />
|
<Divider label="description.or" className={styles.divider} />
|
||||||
<SocialSignInList
|
<SocialSignInList socialConnectors={socialConnectors} className={styles.main} />
|
||||||
isCollapseEnabled
|
|
||||||
socialConnectors={socialConnectors}
|
|
||||||
className={styles.main}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue