diff --git a/.changeset/salty-jokes-hide.md b/.changeset/salty-jokes-hide.md new file mode 100644 index 00000000000..13bb5dcc473 --- /dev/null +++ b/.changeset/salty-jokes-hide.md @@ -0,0 +1,5 @@ +--- +'@clerk/clerk-js': patch +--- + +Update SocialButtons layout logic to live within a single flex container to enable moving SocialButtons between rows using CSS order property. diff --git a/packages/clerk-js/src/ui/elements/SocialButtons.tsx b/packages/clerk-js/src/ui/elements/SocialButtons.tsx index a7960c383f7..f51ad4616aa 100644 --- a/packages/clerk-js/src/ui/elements/SocialButtons.tsx +++ b/packages/clerk-js/src/ui/elements/SocialButtons.tsx @@ -9,7 +9,6 @@ import { Button, descriptors, Flex, - Grid, Icon, Image, localizationKeys, @@ -22,11 +21,9 @@ import { useEnabledThirdPartyProviders } from '../hooks'; import { mqu, type PropsOfComponent } from '../styledSystem'; import { sleep } from '../utils/sleep'; import { useCardState } from './contexts'; -import { distributeStrategiesIntoRows } from './utils'; const SOCIAL_BUTTON_BLOCK_THRESHOLD = 2; const SOCIAL_BUTTON_PRE_TEXT_THRESHOLD = 1; -const MAX_STRATEGIES_PER_ROW = 6; export type SocialButtonsProps = React.PropsWithChildren<{ enableOAuthProviders: boolean; @@ -49,6 +46,11 @@ const isPhoneCodeChannel = (val: string): val is PhoneCodeChannel => { return !!getAlternativePhoneCodeProviderData(val); }; +function getColumnCount(itemsLength: number, maxCols: number = 6): number { + const numRows = Math.ceil(itemsLength / maxCols); + return Math.ceil(itemsLength / numRows); +} + export const SocialButtons = React.memo((props: SocialButtonsRootProps) => { const { oauthCallback, @@ -70,13 +72,12 @@ export const SocialButtons = React.memo((props: SocialButtonsRootProps) => { ...(enableAlternativePhoneCodeProviders ? alternativePhoneCodeChannels : []), ]; + const columns = React.useMemo(() => getColumnCount(strategies.length), [strategies.length]); + if (!strategies.length) { return null; } - const strategyRows = distributeStrategiesIntoRows([...strategies], MAX_STRATEGIES_PER_ROW); - const strategyRowOneLength = strategyRows.at(0)?.length ?? 0; - const preferBlockButtons = socialButtonsVariant === 'blockButton' ? true @@ -118,77 +119,72 @@ export const SocialButtons = React.memo((props: SocialButtonsRootProps) => { gap={2} elementDescriptor={descriptors.socialButtonsRoot} > - {strategyRows.map((row, rowIndex) => ( - ({ - justifyContent: 'center', + ({ + '--columns': columns, + '--gap': t.space.$2, + '--item-width': `calc(100% / var(--columns) - var(--gap) / var(--columns) * (var(--columns) - 1))`, + flexDirection: 'row', + width: '100%', + justifyContent: 'center', + flexWrap: 'wrap', + gap: 'var(--gap)', + '& > *': { + flex: '0 0 var(--item-width)', [mqu.sm]: { - gridTemplateColumns: 'repeat(1, 1fr)', + flex: '0 0 100%', }, - gridTemplateColumns: - strategies.length < 1 - ? `repeat(1, 1fr)` - : `repeat(${row.length}, ${ - rowIndex === 0 - ? `1fr` - : // Calculate the width of each button based on the width of the buttons within the first row. - // t.sizes.$2 is used here to represent the gap defined on the Grid component. - `calc((100% - (${strategyRowOneLength} - 1) * ${t.sizes.$2}) / ${strategyRowOneLength})` - })`, - })} - > - {row.map(strategy => { - const label = - strategies.length === SOCIAL_BUTTON_PRE_TEXT_THRESHOLD - ? `Continue with ${strategyToDisplayData[strategy].name}` - : strategyToDisplayData[strategy].name; + }, + })} + > + {strategies.map(strategy => { + const label = + strategies.length === SOCIAL_BUTTON_PRE_TEXT_THRESHOLD + ? `Continue with ${strategyToDisplayData[strategy].name}` + : strategyToDisplayData[strategy].name; - const localizedText = - strategies.length === SOCIAL_BUTTON_PRE_TEXT_THRESHOLD - ? localizationKeys('socialButtonsBlockButton', { - provider: strategyToDisplayData[strategy].name, - }) - : localizationKeys('socialButtonsBlockButtonManyInView', { - provider: strategyToDisplayData[strategy].name, - }); + const localizedText = + strategies.length === SOCIAL_BUTTON_PRE_TEXT_THRESHOLD + ? localizationKeys('socialButtonsBlockButton', { + provider: strategyToDisplayData[strategy].name, + }) + : localizationKeys('socialButtonsBlockButtonManyInView', { + provider: strategyToDisplayData[strategy].name, + }); - const imageOrInitial = strategyToDisplayData[strategy].iconUrl ? ( - {`Sign ({ width: theme.sizes.$4, height: 'auto', maxWidth: '100%' })} - /> - ) : ( - - ); + const imageOrInitial = strategyToDisplayData[strategy].iconUrl ? ( + {`Sign ({ width: theme.sizes.$4, height: 'auto', maxWidth: '100%' })} + /> + ) : ( + + ); - return ( - - ); - })} - - ))} + return ( + + ); + })} + ); }); diff --git a/packages/clerk-js/src/ui/elements/utils.ts b/packages/clerk-js/src/ui/elements/utils.ts deleted file mode 100644 index ae98a8174f5..00000000000 --- a/packages/clerk-js/src/ui/elements/utils.ts +++ /dev/null @@ -1,24 +0,0 @@ -// This function evenly distributes the strategies into rows (the inner arrays). -// This is done by calculating the number of necessary rows, then the amount of strategies in each row, and then distributing the strategies into the rows. -// Example of 5 strategies with max 3 per row: [ [1, 2, 3], [4, 5] ] -export function distributeStrategiesIntoRows(strategies: T[], maxStrategiesPerRow: number): T[][] { - if (strategies.length <= maxStrategiesPerRow) { - return [strategies]; - } - - const numRows = Math.ceil(strategies.length / maxStrategiesPerRow); - const strategiesPerRow = Math.ceil(strategies.length / numRows); - const rows: T[][] = Array.from({ length: numRows }, () => []); - - let currentArrayIndex = 0; - - for (const strategy of strategies) { - rows[currentArrayIndex].push(strategy); - - if (rows[currentArrayIndex].length === strategiesPerRow) { - currentArrayIndex++; - } - } - - return rows; -}