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 ? (
- ({ width: theme.sizes.$4, height: 'auto', maxWidth: '100%' })}
- />
- ) : (
-
- );
+ const imageOrInitial = strategyToDisplayData[strategy].iconUrl ? (
+ ({ 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;
-}