diff --git a/packages/clerk-js/src/ui/customizables/elementDescriptors.ts b/packages/clerk-js/src/ui/customizables/elementDescriptors.ts index 3ebc8557552..a6907c7cf71 100644 --- a/packages/clerk-js/src/ui/customizables/elementDescriptors.ts +++ b/packages/clerk-js/src/ui/customizables/elementDescriptors.ts @@ -49,6 +49,7 @@ export const APPEARANCE_KEYS = containsAllElementsConfigKeys([ 'socialButtonsRoot', 'socialButtons', + 'socialButtonsColumn', 'socialButtonsIconButton', 'socialButtonsBlockButton', 'socialButtonsBlockButtonText', diff --git a/packages/clerk-js/src/ui/elements/SocialButtons.tsx b/packages/clerk-js/src/ui/elements/SocialButtons.tsx index 584d2ba8738..99fb4215128 100644 --- a/packages/clerk-js/src/ui/elements/SocialButtons.tsx +++ b/packages/clerk-js/src/ui/elements/SocialButtons.tsx @@ -5,6 +5,7 @@ import React, { forwardRef, isValidElement } from 'react'; import { ProviderInitialIcon } from '../common'; import type { LocalizationKey } from '../customizables'; import { + Box, Button, descriptors, Flex, @@ -18,10 +19,10 @@ import { useAppearance, } from '../customizables'; import { useEnabledThirdPartyProviders, useResizeObserver } from '../hooks'; -import { mqu, type PropsOfComponent } from '../styledSystem'; +import { mq, mqu, type PropsOfComponent } from '../styledSystem'; import { sleep } from '../utils'; import { useCardState } from './contexts'; -import { distributeStrategiesIntoRows } from './utils'; +import { distributeStrategiesIntoRows, getColumnCount } from './utils'; const SOCIAL_BUTTON_BLOCK_THRESHOLD = 2; const SOCIAL_BUTTON_PRE_TEXT_THRESHOLD = 1; @@ -57,6 +58,7 @@ export const SocialButtons = React.memo((props: SocialButtonsRootProps) => { return null; } + const strategyColumns = getColumnCount(strategies.length, MAX_STRATEGIES_PER_ROW); const strategyRows = distributeStrategiesIntoRows([...strategies], MAX_STRATEGIES_PER_ROW); const preferBlockButtons = @@ -90,6 +92,80 @@ export const SocialButtons = React.memo((props: SocialButtonsRootProps) => { gap={2} elementDescriptor={descriptors.socialButtonsRoot} > + ({ + rowGap: t.sizes.$2, + [mq.sm]: { + marginLeft: `calc(${t.sizes.$2}*-1)`, + }, + })} + > + {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 ref = null; + + const imageOrInitial = strategyToDisplayData[strategy].iconUrl ? ( + {`Sign ({ width: theme.sizes.$4, height: theme.sizes.$4, maxWidth: '100%' })} + /> + ) : ( + + ); + + return ( + ({ + [mq.sm]: { + paddingLeft: t.sizes.$2, + width: `calc(100%/${strategyColumns})`, + }, + width: '100%', + })} + > + + + ); + })} + + ({ height: t.sizes.$4 })} /> {strategyRows.map((row, rowIndex) => ( (strategies: T[], maxStrategiesPe return rows; } + +/** + * Calculates the number of columns given the total number of items and the maximum columns allowed per row. + * + * @param {Object} options + * @param {number} options.length - The total number of items. + * @param {number} options.max - The maximum number of columns allowed per row. + * @returns The calculated number of columns. + * + * Example output for item counts from 1 to 24 with `columns: 6`: + * + * 1: [ 1 ] + * 2: [ 1, 2 ] + * 3: [ 1, 2, 3 ] + * 4: [ 1, 2, 3, 4 ] + * 5: [ 1, 2, 3, 4, 5 ] + * 6: [ 1, 2, 3, 4, 5, 6 ] + * 7: [ [1, 2, 3, 4], [5, 6, 7] ] + * 8: [ [1, 2, 3, 4], [5, 6, 7, 8] ] + * 9: [ [1, 2, 3, 4, 5], [6, 7, 8, 9] ] + * 10: [ [1, 2, 3, 4, 5], [6, 7, 8, 9, 10] ] + * 11: [ [1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11] ] + * 12: [ [1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11, 12] ] + * 13: [ [1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13] ] + * 14: [ [1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14] ] + * 15: [ [1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11], [12, 13, 14, 15] ] + * 16: [ [1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11, 12], [13, 14, 15, 16] ] + * 17: [ [1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11, 12], [13, 14, 15, 16, 17] ] + * 18: [ [1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15], [16, 17, 18] ] + * 19: [ [1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15], [16, 17, 18, 19] ] + * 20: [ [1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15], [16, 17, 18, 19, 20] ] + * 21: [ [1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11], [12, 13, 14, 15, 16], [17, 18, 19, 20, 21] ] + * 22: [ [1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11], [12, 13, 14, 15, 16], [17, 18, 19, 20, 21, 22] ] + * 23: [ [1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11, 12], [13, 14, 15, 16, 17], [18, 19, 20, 21, 22, 23] ] + * 24: [ [1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11, 12], [13, 14, 15, 16, 17, 18], [19, 20, 21, 22, 23, 24] ] + * + * Examples: + * ``` + * getColumnCount(1); // 1 + * getColumnCount(7); // 4 + * getColumnCount(15); // 6 + * ``` + */ +export function getColumnCount(length: number, max: number): number { + const numRows = Math.ceil(length / max); + return Math.ceil(length / numRows); +} diff --git a/packages/clerk-js/src/ui/styledSystem/breakpoints.tsx b/packages/clerk-js/src/ui/styledSystem/breakpoints.tsx index 23b536d202c..b70892b727e 100644 --- a/packages/clerk-js/src/ui/styledSystem/breakpoints.tsx +++ b/packages/clerk-js/src/ui/styledSystem/breakpoints.tsx @@ -13,9 +13,9 @@ const deviceQueries = { ios: '@supports (-webkit-touch-callout: none)', } as const; -// export const mq = Object.fromEntries( -// Object.entries(breakpoints).map(([k, v]) => [k, `@media (min-width: ${v})`]), -// ) as Record; +export const mq = Object.fromEntries( + Object.entries(breakpoints).map(([k, v]) => [k, `@media (min-width: ${v})`]), +) as Record; export const mqu = { ...deviceQueries, diff --git a/packages/types/src/appearance.ts b/packages/types/src/appearance.ts index 73d2e73b487..32ae911396f 100644 --- a/packages/types/src/appearance.ts +++ b/packages/types/src/appearance.ts @@ -168,6 +168,7 @@ export type ElementsConfig = { socialButtonsRoot: WithOptions; socialButtons: WithOptions; + socialButtonsColumn: WithOptions; socialButtonsIconButton: WithOptions; socialButtonsBlockButton: WithOptions; socialButtonsBlockButtonText: WithOptions;