0
Fork 0
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:
simeng-li 2023-01-16 17:24:14 +08:00 committed by GitHub
parent d559937e8b
commit 089b138c77
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 35 additions and 96 deletions

View file

@ -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;
} }
} }

View file

@ -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>
); );
}; };

View file

@ -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
}); });
}); });

View file

@ -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>
); );
}; };

View file

@ -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;

View file

@ -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 () => {

View file

@ -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}
/>
</> </>
) )
} }

View file

@ -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;

View file

@ -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 () => {

View file

@ -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}
/>
</> </>
) )
} }