0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-03-31 22:51:25 -05:00

refactor(ui): social container refactor (#955)

* refactor(ui): social container refactor

social container refactor

* refactor(ui): extract socialSignInIconList

extract socialSignInIconList
This commit is contained in:
simeng-li 2022-05-26 13:30:09 +08:00 committed by GitHub
parent f01d113445
commit 70d34c49c8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 198 additions and 129 deletions

View file

@ -0,0 +1,15 @@
import React from 'react';
import SocialSignInList from '../SocialSignInList';
export const defaultSize = 3;
type Props = {
className?: string;
};
const PrimarySocialSignIn = ({ className }: Props) => {
return <SocialSignInList className={className} />;
};
export default PrimarySocialSignIn;

View file

@ -1,82 +0,0 @@
import classNames from 'classnames';
import React, { useMemo, useState, useRef } from 'react';
import MoreSocialIcon from '@/assets/icons/more-social-icon.svg';
import IconButton from '@/components/Button/IconButton';
import SocialIconButton from '@/components/Button/SocialIconButton';
import usePlatform from '@/hooks/use-platform';
import useSocial from '@/hooks/use-social';
import * as styles from './SecondarySocialSignIn.module.scss';
import SocialSignInDropdown from './SocialSignInDropdown';
import SocialSignInPopUp from './SocialSignInPopUp';
export const defaultSize = 4;
type Props = {
className?: string;
};
const SecondarySocialSignIn = ({ className }: Props) => {
const { socialConnectors, invokeSocialSignIn } = useSocial();
const isOverSize = socialConnectors.length > defaultSize;
const [showModal, setShowModal] = useState(false);
const moreButtonRef = useRef<HTMLButtonElement>(null);
const { isMobile } = usePlatform();
const displayConnectors = useMemo(() => {
if (isOverSize) {
return socialConnectors.slice(0, defaultSize - 1);
}
return socialConnectors;
}, [socialConnectors, isOverSize]);
return (
<>
<div className={classNames(styles.socialIconList, className)}>
{displayConnectors.map((connector) => (
<SocialIconButton
key={connector.id}
className={styles.socialButton}
connector={connector}
onClick={() => {
void invokeSocialSignIn(connector.id);
}}
/>
))}
{isOverSize && (
<IconButton
ref={moreButtonRef}
className={styles.moreButton}
onClick={() => {
setShowModal(true);
}}
>
<MoreSocialIcon />
</IconButton>
)}
</div>
{isOverSize && isMobile && (
<SocialSignInPopUp
isOpen={showModal}
onClose={() => {
setShowModal(false);
}}
/>
)}
{isOverSize && !isMobile && (
<SocialSignInDropdown
anchorRef={moreButtonRef}
isOpen={showModal}
connectors={socialConnectors.slice(defaultSize - 1)}
onClose={() => {
setShowModal(false);
}}
/>
)}
</>
);
};
export default SecondarySocialSignIn;

View file

@ -8,7 +8,7 @@ import { socialConnectors, mockSignInExperienceSettings } from '@/__mocks__/logt
import * as socialSignInApi from '@/apis/social';
import { generateState, storeState } from '@/hooks/utils';
import SecondarySocialSignIn, { defaultSize } from './SecondarySocialSignIn';
import SecondarySocialSignIn, { defaultSize } from '.';
describe('SecondarySocialSignIn', () => {
const mockOrigin = 'https://logto.dev';

View file

@ -0,0 +1,65 @@
import React, { useMemo, useState, useRef } from 'react';
import usePlatform from '@/hooks/use-platform';
import useSocial from '@/hooks/use-social';
import SocialSignInDropdown from '../SocialSignInDropdown';
import SocialSignInIconList from '../SocialSignInIconList';
import SocialSignInPopUp from '../SocialSignInPopUp';
export const defaultSize = 4;
type Props = {
className?: string;
};
const SecondarySocialSignIn = ({ className }: Props) => {
const { socialConnectors } = useSocial();
const [showModal, setShowModal] = useState(false);
const moreButtonRef = useRef<HTMLButtonElement>(null);
const { isMobile } = usePlatform();
const isCollapsed = socialConnectors.length > defaultSize;
const displayConnectors = useMemo(() => {
if (isCollapsed) {
return socialConnectors.slice(0, defaultSize - 1);
}
return socialConnectors;
}, [socialConnectors, isCollapsed]);
return (
<>
<SocialSignInIconList
className={className}
connectors={displayConnectors}
hasMore={isCollapsed}
moreButtonRef={moreButtonRef}
onMoreButtonClick={() => {
setShowModal(true);
}}
/>
{isCollapsed && isMobile && (
<SocialSignInPopUp
isOpen={showModal}
onClose={() => {
setShowModal(false);
}}
/>
)}
{isCollapsed && !isMobile && (
<SocialSignInDropdown
anchorRef={moreButtonRef}
isOpen={showModal}
connectors={socialConnectors.slice(defaultSize - 1)}
onClose={() => {
setShowModal(false);
}}
/>
)}
</>
);
};
export default SecondarySocialSignIn;

View file

@ -0,0 +1,24 @@
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
import SettingsProvider from '@/__mocks__/RenderWithPageContext/SettingsProvider';
import { socialConnectors } from '@/__mocks__/logto';
import SocialSignInDropdown from '.';
describe('SocialSignInDropdown', () => {
it('render properly', () => {
const { queryByText } = renderWithPageContext(
<SettingsProvider>
<MemoryRouter>
<SocialSignInDropdown isOpen connectors={socialConnectors} onClose={jest.fn} />
</MemoryRouter>
</SettingsProvider>
);
for (const { name } of socialConnectors) {
expect(queryByText(name.en)).not.toBeNull();
}
});
});

View file

@ -1,12 +1,12 @@
import { Language } from '@logto/phrases';
import React, { useMemo, useState, useCallback } from 'react';
import React, { useState, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import Dropdown, { DropdownItem } from '@/components/Dropdown';
import useSocial from '@/hooks/use-social';
import { ConnectorData } from '@/types';
import * as styles from './SocialSignInDropdown.module.scss';
import * as styles from './index.module.scss';
type Props = {
anchorRef?: React.RefObject<HTMLElement>;
@ -19,31 +19,8 @@ const SocialSignInDropdown = ({ isOpen, onClose, connectors, anchorRef }: Props)
const {
i18n: { language },
} = useTranslation();
const { invokeSocialSignIn } = useSocial();
const [contentStyle, setContentStyle] = useState<{ top?: number; left?: number }>();
const items = useMemo(
() =>
connectors.map(({ id, name, logo }) => {
const languageKey = Object.keys(name).find((key) => key === language) ?? 'en';
const localName = name[languageKey as Language];
return (
<DropdownItem
key={id}
onClick={() => {
void invokeSocialSignIn(id, onClose);
}}
>
<img src={logo} alt={id} className={styles.socialLogo} />
<span>{localName}</span>
</DropdownItem>
);
}),
[connectors, language, invokeSocialSignIn, onClose]
);
const { invokeSocialSignIn } = useSocial();
const adjustPosition = useCallback(() => {
if (anchorRef?.current) {
@ -68,7 +45,22 @@ const SocialSignInDropdown = ({ isOpen, onClose, connectors, anchorRef }: Props)
setContentStyle(undefined);
}}
>
{items}
{connectors.map(({ id, name, logo }) => {
const languageKey = Object.keys(name).find((key) => key === language) ?? 'en';
const localName = name[languageKey as Language];
return (
<DropdownItem
key={id}
onClick={() => {
void invokeSocialSignIn(id, onClose);
}}
>
<img src={logo} alt={id} className={styles.socialLogo} />
<span>{localName}</span>
</DropdownItem>
);
})}
</Dropdown>
);
};

View file

@ -0,0 +1,50 @@
import classNames from 'classnames';
import React from 'react';
import MoreSocialIcon from '@/assets/icons/more-social-icon.svg';
import IconButton from '@/components/Button/IconButton';
import SocialIconButton from '@/components/Button/SocialIconButton';
import useSocial from '@/hooks/use-social';
import { ConnectorData } from '@/types';
import * as styles from './index.module.scss';
type Props = {
className?: string;
connectors?: ConnectorData[];
hasMore?: boolean;
moreButtonRef: React.RefObject<HTMLButtonElement>;
onMoreButtonClick?: () => void;
};
const SocialSignInIconList = ({
className,
connectors = [],
hasMore = false,
moreButtonRef,
onMoreButtonClick,
}: Props) => {
const { invokeSocialSignIn } = useSocial();
return (
<div className={classNames(styles.socialIconList, className)}>
{connectors.map((connector) => (
<SocialIconButton
key={connector.id}
className={styles.socialButton}
connector={connector}
onClick={() => {
void invokeSocialSignIn(connector.id);
}}
/>
))}
{hasMore && (
<IconButton ref={moreButtonRef} className={styles.moreButton} onClick={onMoreButtonClick}>
<MoreSocialIcon />
</IconButton>
)}
</div>
);
};
export default SocialSignInIconList;

View file

@ -6,9 +6,9 @@ import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
import SettingsProvider from '@/__mocks__/RenderWithPageContext/SettingsProvider';
import { socialConnectors, mockSignInExperienceSettings } from '@/__mocks__/logto';
import PrimarySocialSignIn, { defaultSize } from './PrimarySocialSignIn';
import SocialSignInList, { defaultSize } from '.';
describe('SecondarySocialSignIn', () => {
describe('SocialSignInList', () => {
it('less than three connectors', () => {
const { container } = renderWithPageContext(
<SettingsProvider
@ -18,7 +18,7 @@ describe('SecondarySocialSignIn', () => {
}}
>
<MemoryRouter>
<PrimarySocialSignIn />
<SocialSignInList />
</MemoryRouter>
</SettingsProvider>
);
@ -29,7 +29,7 @@ describe('SecondarySocialSignIn', () => {
const { container } = renderWithPageContext(
<SettingsProvider>
<MemoryRouter>
<PrimarySocialSignIn />
<SocialSignInList />
</MemoryRouter>
</SettingsProvider>
);

View file

@ -6,29 +6,33 @@ import IconButton from '@/components/Button/IconButton';
import SocialLinkButton from '@/components/Button/SocialLinkButton';
import useSocial from '@/hooks/use-social';
import * as styles from './PrimarySocialSignIn.module.scss';
import * as styles from './index.module.scss';
export const defaultSize = 3;
export const defaultSize = 4;
type Props = {
className?: string;
isPopup?: boolean;
isCollapseEnabled?: boolean;
onSocialSignInCallback?: () => void;
};
const PrimarySocialSignIn = ({ className, isPopup = false, onSocialSignInCallback }: Props) => {
const [showAll, setShowAll] = useState(false);
const SocialSignInList = ({
className,
isCollapseEnabled = true,
onSocialSignInCallback,
}: Props) => {
const [expand, setExpand] = useState(false);
const { invokeSocialSignIn, socialConnectors } = useSocial();
const isOverSize = socialConnectors.length > defaultSize;
const fullDisplay = isPopup || !isOverSize;
const displayAll = !isOverSize || !isCollapseEnabled;
const displayConnectors = useMemo(() => {
if (fullDisplay || showAll) {
if (displayAll || expand) {
return socialConnectors;
}
return socialConnectors.slice(0, defaultSize);
}, [fullDisplay, showAll, socialConnectors]);
}, [displayAll, expand, socialConnectors]);
return (
<div className={classNames(styles.socialLinkList, className)}>
@ -42,11 +46,11 @@ const PrimarySocialSignIn = ({ className, isPopup = false, onSocialSignInCallbac
}}
/>
))}
{!fullDisplay && (
{!displayAll && (
<IconButton
className={classNames(styles.expandIcon, showAll && styles.expanded)}
className={classNames(styles.expandIcon, expand && styles.expanded)}
onClick={() => {
setShowAll(!showAll);
setExpand(!expand);
}}
>
<ExpandIcon />
@ -56,4 +60,4 @@ const PrimarySocialSignIn = ({ className, isPopup = false, onSocialSignInCallbac
);
};
export default PrimarySocialSignIn;
export default SocialSignInList;

View file

@ -2,7 +2,7 @@ import React from 'react';
import Drawer from '@/components/Drawer';
import PrimarySocialSignIn from './PrimarySocialSignIn';
import SocialSignInList from '../SocialSignInList';
type Props = {
isOpen?: boolean;
@ -12,7 +12,7 @@ type Props = {
const SocialSignInPopUp = ({ isOpen = false, onClose, className }: Props) => (
<Drawer className={className} isOpen={isOpen} onClose={onClose}>
<PrimarySocialSignIn isPopup onSocialSignInCallback={onClose} />
<SocialSignInList isCollapseEnabled={false} onSocialSignInCallback={onClose} />
</Drawer>
);

View file

@ -4,6 +4,7 @@ import { MemoryRouter } from 'react-router-dom';
import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
import SettingsProvider from '@/__mocks__/RenderWithPageContext/SettingsProvider';
import { mockSignInExperienceSettings } from '@/__mocks__/logto';
import { defaultSize } from '@/containers/SocialSignIn/SocialSignInList';
import SignIn from '@/pages/SignIn';
describe('<SignIn />', () => {
@ -56,6 +57,6 @@ describe('<SignIn />', () => {
</SettingsProvider>
);
expect(container.querySelectorAll('button')).toHaveLength(4); // Plus Expand Button
expect(container.querySelectorAll('button')).toHaveLength(defaultSize + 1); // Plus Expand Button
});
});