@ankhorage/zora 2.5.4 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +5 -3
  3. package/dist/components/gradient/Gradient.d.ts +10 -0
  4. package/dist/components/gradient/Gradient.d.ts.map +1 -0
  5. package/dist/components/gradient/Gradient.js +21 -0
  6. package/dist/components/gradient/Gradient.js.map +1 -0
  7. package/dist/components/gradient/index.d.ts +3 -0
  8. package/dist/components/gradient/index.d.ts.map +1 -0
  9. package/dist/components/gradient/index.js +2 -0
  10. package/dist/components/gradient/index.js.map +1 -0
  11. package/dist/components/gradient/meta.d.ts +9 -0
  12. package/dist/components/gradient/meta.d.ts.map +1 -0
  13. package/dist/components/gradient/meta.js +9 -0
  14. package/dist/components/gradient/meta.js.map +1 -0
  15. package/dist/components/gradient/types.d.ts +23 -0
  16. package/dist/components/gradient/types.d.ts.map +1 -0
  17. package/dist/components/gradient/types.js +2 -0
  18. package/dist/components/gradient/types.js.map +1 -0
  19. package/dist/components/splash-screen/SplashScreen.d.ts +11 -0
  20. package/dist/components/splash-screen/SplashScreen.d.ts.map +1 -0
  21. package/dist/components/splash-screen/SplashScreen.js +54 -0
  22. package/dist/components/splash-screen/SplashScreen.js.map +1 -0
  23. package/dist/components/splash-screen/index.d.ts +3 -0
  24. package/dist/components/splash-screen/index.d.ts.map +1 -0
  25. package/dist/components/splash-screen/index.js +2 -0
  26. package/dist/components/splash-screen/index.js.map +1 -0
  27. package/dist/components/splash-screen/meta.d.ts +9 -0
  28. package/dist/components/splash-screen/meta.d.ts.map +1 -0
  29. package/dist/components/splash-screen/meta.js +9 -0
  30. package/dist/components/splash-screen/meta.js.map +1 -0
  31. package/dist/components/splash-screen/types.d.ts +16 -0
  32. package/dist/components/splash-screen/types.d.ts.map +1 -0
  33. package/dist/components/splash-screen/types.js +2 -0
  34. package/dist/components/splash-screen/types.js.map +1 -0
  35. package/dist/index.d.ts +6 -2
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +3 -1
  38. package/dist/index.js.map +1 -1
  39. package/dist/metadata/componentMeta.d.ts.map +1 -1
  40. package/dist/metadata/componentMeta.js +7 -1
  41. package/dist/metadata/componentMeta.js.map +1 -1
  42. package/dist/patterns/auth/OAuthProviderButton.d.ts +10 -0
  43. package/dist/patterns/auth/OAuthProviderButton.d.ts.map +1 -0
  44. package/dist/patterns/auth/OAuthProviderButton.js +21 -0
  45. package/dist/patterns/auth/OAuthProviderButton.js.map +1 -0
  46. package/dist/patterns/auth/OAuthProviderList.d.ts +7 -0
  47. package/dist/patterns/auth/OAuthProviderList.d.ts.map +1 -0
  48. package/dist/patterns/auth/OAuthProviderList.js +16 -0
  49. package/dist/patterns/auth/OAuthProviderList.js.map +1 -0
  50. package/dist/patterns/auth/index.d.ts +4 -1
  51. package/dist/patterns/auth/index.d.ts.map +1 -1
  52. package/dist/patterns/auth/index.js +3 -0
  53. package/dist/patterns/auth/index.js.map +1 -1
  54. package/dist/patterns/auth/meta.d.ts +16 -0
  55. package/dist/patterns/auth/meta.d.ts.map +1 -1
  56. package/dist/patterns/auth/meta.js +16 -0
  57. package/dist/patterns/auth/meta.js.map +1 -1
  58. package/dist/patterns/auth/oauthProviders.d.ts +74 -0
  59. package/dist/patterns/auth/oauthProviders.d.ts.map +1 -0
  60. package/dist/patterns/auth/oauthProviders.js +66 -0
  61. package/dist/patterns/auth/oauthProviders.js.map +1 -0
  62. package/dist/patterns/auth/types.d.ts +38 -0
  63. package/dist/patterns/auth/types.d.ts.map +1 -1
  64. package/dist/patterns/auth/types.js.map +1 -1
  65. package/package.json +7 -2
  66. package/src/components/gradient/Gradient.tsx +53 -0
  67. package/src/components/gradient/index.ts +8 -0
  68. package/src/components/gradient/meta.ts +10 -0
  69. package/src/components/gradient/types.ts +26 -0
  70. package/src/components/splash-screen/SplashScreen.tsx +98 -0
  71. package/src/components/splash-screen/index.ts +2 -0
  72. package/src/components/splash-screen/meta.ts +10 -0
  73. package/src/components/splash-screen/types.ts +18 -0
  74. package/src/index.ts +26 -1
  75. package/src/metadata/componentMeta.ts +8 -0
  76. package/src/patterns/auth/OAuthProviderButton.tsx +51 -0
  77. package/src/patterns/auth/OAuthProviderList.tsx +54 -0
  78. package/src/patterns/auth/index.ts +12 -0
  79. package/src/patterns/auth/meta.ts +18 -0
  80. package/src/patterns/auth/oauthProviders.test.ts +31 -0
  81. package/src/patterns/auth/oauthProviders.ts +79 -0
  82. package/src/patterns/auth/types.ts +43 -0
@@ -0,0 +1,26 @@
1
+ import type React from 'react';
2
+
3
+ import type { BoxProps } from '../../foundation';
4
+ import type { ZoraBaseProps } from '../../theme/ZoraBaseProps';
5
+
6
+ export type GradientColor = string;
7
+ export type GradientColors = readonly [GradientColor, GradientColor, ...GradientColor[]];
8
+ export type GradientLocations = readonly [number, number, ...number[]];
9
+
10
+ export interface GradientPoint {
11
+ readonly x: number;
12
+ readonly y: number;
13
+ }
14
+
15
+ export interface GradientProps extends ZoraBaseProps {
16
+ children?: React.ReactNode;
17
+ colors: GradientColors;
18
+ locations?: GradientLocations;
19
+ start?: GradientPoint;
20
+ end?: GradientPoint;
21
+ width?: BoxProps['width'];
22
+ height?: BoxProps['height'];
23
+ minHeight?: BoxProps['minHeight'];
24
+ radius?: BoxProps['radius'];
25
+ p?: BoxProps['p'];
26
+ }
@@ -0,0 +1,98 @@
1
+ import React from 'react';
2
+
3
+ import { Box, Center, Stack } from '../../foundation';
4
+ import { useZoraTheme } from '../../theme/useZoraTheme';
5
+ import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
6
+ import { Heading } from '../heading';
7
+ import { Text } from '../text';
8
+ import type { SplashScreenLogoShape, SplashScreenProps } from './types';
9
+
10
+ interface PlaceholderLogoProps {
11
+ label: string;
12
+ shape: SplashScreenLogoShape;
13
+ size: number;
14
+ }
15
+
16
+ function resolveLogoRadius(shape: SplashScreenLogoShape): number {
17
+ switch (shape) {
18
+ case 'circle':
19
+ return 9999;
20
+ case 'square':
21
+ return 0;
22
+ case 'rounded':
23
+ default:
24
+ return 28;
25
+ }
26
+ }
27
+
28
+ function PlaceholderLogo({ label, shape, size }: PlaceholderLogoProps) {
29
+ const { theme } = useZoraTheme();
30
+ const radius = resolveLogoRadius(shape);
31
+
32
+ return (
33
+ <Center
34
+ accessibilityLabel={label}
35
+ bg={theme.semantics.action.primary.base}
36
+ height={size}
37
+ width={size}
38
+ style={{ borderRadius: radius }}
39
+ >
40
+ <Text emphasis="inverse" variant="eyebrow" weight="bold">
41
+ {label.slice(0, 2).toUpperCase()}
42
+ </Text>
43
+ </Center>
44
+ );
45
+ }
46
+
47
+ function SplashScreenInner({
48
+ themeId: _themeId,
49
+ mode: _mode,
50
+ logo,
51
+ title,
52
+ subtitle,
53
+ children,
54
+ minHeight = '100%',
55
+ backgroundColor,
56
+ logoSize = 96,
57
+ logoLabel = 'ZORA',
58
+ logoShape = 'rounded',
59
+ testID,
60
+ }: SplashScreenProps) {
61
+ const { theme } = useZoraTheme();
62
+ const background = backgroundColor ?? theme.semantics.neutral.surface;
63
+ const renderedLogo = logo ?? (
64
+ <PlaceholderLogo label={logoLabel} shape={logoShape} size={logoSize} />
65
+ );
66
+
67
+ return (
68
+ <Center bg={background} minHeight={minHeight} p="xl" testID={testID} width="100%">
69
+ <Stack align="center" gap="l" style={{ maxWidth: 360 }}>
70
+ {renderedLogo}
71
+ {title || subtitle ? (
72
+ <Stack align="center" gap="xs">
73
+ {title ? (
74
+ <Heading align="center" level={1} size="h1">
75
+ {title}
76
+ </Heading>
77
+ ) : null}
78
+ {subtitle ? (
79
+ <Text align="center" emphasis="muted" variant="bodySmall">
80
+ {subtitle}
81
+ </Text>
82
+ ) : null}
83
+ </Stack>
84
+ ) : null}
85
+ {children ? <Box width="100%">{children}</Box> : null}
86
+ </Stack>
87
+ </Center>
88
+ );
89
+ }
90
+
91
+ /***
92
+ * Branded launch/loading surface for previews, web fallbacks, and app bootstrap states.
93
+ *
94
+ * `SplashScreen` is a React-rendered component. It intentionally does not manage
95
+ * Expo's native splash screen lifecycle or replace generated `expo-splash-screen`
96
+ * configuration.
97
+ */
98
+ export const SplashScreen = withZoraThemeScope(SplashScreenInner);
@@ -0,0 +1,2 @@
1
+ export { SplashScreen } from './SplashScreen';
2
+ export type { SplashScreenLogoShape, SplashScreenProps } from './types';
@@ -0,0 +1,10 @@
1
+ import type { ZoraComponentMeta } from '../../metadata';
2
+
3
+ export const splashScreenMeta = {
4
+ name: 'SplashScreen',
5
+ category: 'component',
6
+ directManifestNode: false,
7
+ allowedChildren: [],
8
+ note: 'React-rendered branded loading and preview surface; native splash configuration is generated outside ZORA.',
9
+ props: {},
10
+ } as const satisfies ZoraComponentMeta;
@@ -0,0 +1,18 @@
1
+ import type React from 'react';
2
+
3
+ import type { BoxProps } from '../../foundation';
4
+ import type { ZoraBaseProps } from '../../theme/ZoraBaseProps';
5
+
6
+ export type SplashScreenLogoShape = 'circle' | 'square' | 'rounded';
7
+
8
+ export interface SplashScreenProps extends ZoraBaseProps {
9
+ logo?: React.ReactNode;
10
+ title?: React.ReactNode;
11
+ subtitle?: React.ReactNode;
12
+ children?: React.ReactNode;
13
+ minHeight?: BoxProps['minHeight'];
14
+ backgroundColor?: BoxProps['bg'];
15
+ logoSize?: number;
16
+ logoLabel?: string;
17
+ logoShape?: SplashScreenLogoShape;
18
+ }
package/src/index.ts CHANGED
@@ -68,6 +68,14 @@ export {
68
68
  validateFields,
69
69
  validateValue,
70
70
  } from './components/form';
71
+ export type {
72
+ GradientColor,
73
+ GradientColors,
74
+ GradientLocations,
75
+ GradientPoint,
76
+ GradientProps,
77
+ } from './components/gradient';
78
+ export { Gradient } from './components/gradient';
71
79
  export type {
72
80
  HeadingAlign,
73
81
  HeadingColor,
@@ -124,6 +132,8 @@ export type {
124
132
  SkeletonTextProps,
125
133
  } from './components/skeleton';
126
134
  export { Skeleton, SkeletonCard, SkeletonList, SkeletonText } from './components/skeleton';
135
+ export type { SplashScreenLogoShape, SplashScreenProps } from './components/splash-screen';
136
+ export { SplashScreen } from './components/splash-screen';
127
137
  export type { TabItem, TabsProps, TabsVariant } from './components/tabs';
128
138
  export { Tabs } from './components/tabs';
129
139
  export type {
@@ -215,6 +225,11 @@ export type {
215
225
  AuthIdentifierKind,
216
226
  ForgotPasswordFormProps,
217
227
  ForgotPasswordFormValues,
228
+ OAuthProviderButtonProps,
229
+ OAuthProviderIconSpec,
230
+ OAuthProviderItem,
231
+ OAuthProviderListLayout,
232
+ OAuthProviderListProps,
218
233
  OtpFormProps,
219
234
  OtpFormValues,
220
235
  SignInFormProps,
@@ -223,7 +238,17 @@ export type {
223
238
  SignUpFormProps,
224
239
  SignUpFormValues,
225
240
  } from './patterns/auth';
226
- export { ForgotPasswordForm, OtpForm, SignInForm, SignUpForm } from './patterns/auth';
241
+ export {
242
+ DEFAULT_OAUTH_PROVIDER_ICONS,
243
+ ForgotPasswordForm,
244
+ OAuthProviderButton,
245
+ OAuthProviderList,
246
+ OtpForm,
247
+ resolveOAuthProviderIcon,
248
+ resolveOAuthProviderLabel,
249
+ SignInForm,
250
+ SignUpForm,
251
+ } from './patterns/auth';
227
252
  export type { ChatListAvatar, ChatListItemProps } from './patterns/chat-list-item';
228
253
  export { ChatListItem } from './patterns/chat-list-item';
229
254
  export type {
@@ -14,6 +14,7 @@ import { dataTableMeta } from '../components/data-table/meta';
14
14
  import { datePickerMeta } from '../components/date-picker/meta';
15
15
  import { drawerMeta } from '../components/drawer/meta';
16
16
  import { formActionsMeta, formErrorMeta, formFieldMeta, formMeta } from '../components/form/meta';
17
+ import { gradientMeta } from '../components/gradient/meta';
17
18
  import { headingMeta } from '../components/heading/meta';
18
19
  import { iconMeta } from '../components/icon/meta';
19
20
  import { iconButtonMeta } from '../components/icon-button/meta';
@@ -37,6 +38,7 @@ import {
37
38
  skeletonMeta,
38
39
  skeletonTextMeta,
39
40
  } from '../components/skeleton/meta';
41
+ import { splashScreenMeta } from '../components/splash-screen/meta';
40
42
  import { tabsMeta } from '../components/tabs/meta';
41
43
  import { textMeta } from '../components/text/meta';
42
44
  import { textareaMeta } from '../components/textarea/meta';
@@ -52,6 +54,8 @@ import { sidebarLayoutMeta } from '../layout/sidebar-layout/meta';
52
54
  import { topbarLayoutMeta } from '../layout/topbar-layout/meta';
53
55
  import {
54
56
  forgotPasswordFormMeta,
57
+ oauthProviderButtonMeta,
58
+ oauthProviderListMeta,
55
59
  otpFormMeta,
56
60
  signInFormMeta,
57
61
  signUpFormMeta,
@@ -108,6 +112,7 @@ export const ZORA_COMPONENT_META: ZoraComponentMetaRegistry = {
108
112
  FormActions: formActionsMeta,
109
113
  FormError: formErrorMeta,
110
114
  FormField: formFieldMeta,
115
+ Gradient: gradientMeta,
111
116
  Heading: headingMeta,
112
117
  Icon: iconMeta,
113
118
  IconButton: iconButtonMeta,
@@ -130,6 +135,7 @@ export const ZORA_COMPONENT_META: ZoraComponentMetaRegistry = {
130
135
  SkeletonCard: skeletonCardMeta,
131
136
  SkeletonList: skeletonListMeta,
132
137
  SkeletonText: skeletonTextMeta,
138
+ SplashScreen: splashScreenMeta,
133
139
  Tabs: tabsMeta,
134
140
  Text: textMeta,
135
141
  Textarea: textareaMeta,
@@ -145,6 +151,8 @@ export const ZORA_COMPONENT_META: ZoraComponentMetaRegistry = {
145
151
  SidebarLayout: sidebarLayoutMeta,
146
152
  TopbarLayout: topbarLayoutMeta,
147
153
  ForgotPasswordForm: forgotPasswordFormMeta,
154
+ OAuthProviderButton: oauthProviderButtonMeta,
155
+ OAuthProviderList: oauthProviderListMeta,
148
156
  OtpForm: otpFormMeta,
149
157
  SignInForm: signInFormMeta,
150
158
  SignUpForm: signUpFormMeta,
@@ -0,0 +1,51 @@
1
+ import React from 'react';
2
+
3
+ import { Button } from '../../components/button';
4
+ import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
5
+ import { resolveOAuthProviderIcon, resolveOAuthProviderLabel } from './oauthProviders';
6
+ import type { OAuthProviderButtonProps } from './types';
7
+
8
+ function OAuthProviderButtonInner({
9
+ themeId: _themeId,
10
+ mode: _mode,
11
+ providerId,
12
+ label,
13
+ icon,
14
+ disabled = false,
15
+ loading = false,
16
+ fullWidth = true,
17
+ size = 'l',
18
+ variant = 'outline',
19
+ color = 'neutral',
20
+ onPress,
21
+ testID,
22
+ }: OAuthProviderButtonProps) {
23
+ const resolvedIcon = icon ?? resolveOAuthProviderIcon(providerId);
24
+ const resolvedLabel = label ?? `Continue with ${resolveOAuthProviderLabel(providerId)}`;
25
+
26
+ return (
27
+ <Button
28
+ color={color}
29
+ disabled={disabled || loading || onPress === undefined}
30
+ fullWidth={fullWidth}
31
+ leadingIcon={resolvedIcon}
32
+ loading={loading}
33
+ onPress={() => {
34
+ void onPress?.(providerId);
35
+ }}
36
+ size={size}
37
+ testID={testID}
38
+ variant={variant}
39
+ >
40
+ {resolvedLabel}
41
+ </Button>
42
+ );
43
+ }
44
+
45
+ /***
46
+ * Renders a provider-branded OAuth action button without owning auth behavior.
47
+ *
48
+ * The component only renders UI and forwards the provider id through `onPress`.
49
+ * Adapters, redirects, and callback handling belong to app/runtime layers.
50
+ */
51
+ export const OAuthProviderButton = withZoraThemeScope(OAuthProviderButtonInner);
@@ -0,0 +1,54 @@
1
+ import React from 'react';
2
+
3
+ import { Stack } from '../../foundation';
4
+ import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
5
+ import { OAuthProviderButton } from './OAuthProviderButton';
6
+ import type { OAuthProviderListProps } from './types';
7
+
8
+ function OAuthProviderListInner({
9
+ themeId: _themeId,
10
+ mode: _mode,
11
+ providers,
12
+ disabled = false,
13
+ loading = false,
14
+ fullWidth,
15
+ layout = 'stack',
16
+ size,
17
+ variant,
18
+ color,
19
+ onProviderPress,
20
+ testID,
21
+ }: OAuthProviderListProps) {
22
+ const direction = layout === 'inline' ? 'row' : 'column';
23
+ const resolvedFullWidth = fullWidth ?? layout === 'stack';
24
+
25
+ return (
26
+ <Stack
27
+ direction={direction}
28
+ gap="s"
29
+ testID={testID}
30
+ wrap={layout === 'inline' ? 'wrap' : undefined}
31
+ >
32
+ {providers.map((provider) => (
33
+ <OAuthProviderButton
34
+ color={color}
35
+ disabled={disabled || provider.disabled}
36
+ fullWidth={resolvedFullWidth}
37
+ icon={provider.icon}
38
+ key={provider.id}
39
+ label={provider.label}
40
+ loading={loading || provider.loading}
41
+ onPress={onProviderPress}
42
+ providerId={provider.id}
43
+ size={size}
44
+ variant={variant}
45
+ />
46
+ ))}
47
+ </Stack>
48
+ );
49
+ }
50
+
51
+ /***
52
+ * Renders a group of OAuth provider buttons for sign-in and auth settings flows.
53
+ */
54
+ export const OAuthProviderList = withZoraThemeScope(OAuthProviderListInner);
@@ -1,4 +1,11 @@
1
1
  export { ForgotPasswordForm } from './ForgotPasswordForm';
2
+ export { OAuthProviderButton } from './OAuthProviderButton';
3
+ export { OAuthProviderList } from './OAuthProviderList';
4
+ export {
5
+ DEFAULT_OAUTH_PROVIDER_ICONS,
6
+ resolveOAuthProviderIcon,
7
+ resolveOAuthProviderLabel,
8
+ } from './oauthProviders';
2
9
  export { OtpForm } from './OtpForm';
3
10
  export { SignInForm } from './SignInForm';
4
11
  export { SignUpForm } from './SignUpForm';
@@ -7,6 +14,11 @@ export type {
7
14
  AuthIdentifierKind,
8
15
  ForgotPasswordFormProps,
9
16
  ForgotPasswordFormValues,
17
+ OAuthProviderButtonProps,
18
+ OAuthProviderIconSpec,
19
+ OAuthProviderItem,
20
+ OAuthProviderListLayout,
21
+ OAuthProviderListProps,
10
22
  OtpFormProps,
11
23
  OtpFormValues,
12
24
  SignInFormProps,
@@ -11,6 +11,24 @@ export const forgotPasswordFormMeta = {
11
11
  props: {},
12
12
  } as const satisfies ZoraComponentMeta;
13
13
 
14
+ export const oauthProviderButtonMeta = {
15
+ name: 'OAuthProviderButton',
16
+ category: 'pattern',
17
+ directManifestNode: false,
18
+ allowedChildren: [],
19
+ note: AUTH_NOTE,
20
+ props: {},
21
+ } as const satisfies ZoraComponentMeta;
22
+
23
+ export const oauthProviderListMeta = {
24
+ name: 'OAuthProviderList',
25
+ category: 'pattern',
26
+ directManifestNode: false,
27
+ allowedChildren: [],
28
+ note: AUTH_NOTE,
29
+ props: {},
30
+ } as const satisfies ZoraComponentMeta;
31
+
14
32
  export const otpFormMeta = {
15
33
  name: 'OtpForm',
16
34
  category: 'pattern',
@@ -0,0 +1,31 @@
1
+ import { describe, expect, it } from 'bun:test';
2
+
3
+ import {
4
+ DEFAULT_OAUTH_PROVIDER_ICONS,
5
+ resolveOAuthProviderIcon,
6
+ resolveOAuthProviderLabel,
7
+ } from './oauthProviders';
8
+
9
+ describe('OAuth provider UI helpers', () => {
10
+ it('resolves known provider labels', () => {
11
+ expect(resolveOAuthProviderLabel('google')).toBe('Google');
12
+ expect(resolveOAuthProviderLabel('github')).toBe('GitHub');
13
+ expect(resolveOAuthProviderLabel('linkedin')).toBe('LinkedIn');
14
+ });
15
+
16
+ it('formats custom provider labels', () => {
17
+ expect(resolveOAuthProviderLabel('custom-sso')).toBe('Custom Sso');
18
+ expect(resolveOAuthProviderLabel(' enterprise_idp ')).toBe('Enterprise Idp');
19
+ expect(resolveOAuthProviderLabel('')).toBe('Provider');
20
+ });
21
+
22
+ it('resolves known provider icons', () => {
23
+ expect(resolveOAuthProviderIcon('google')).toEqual({ provider: 'FontAwesome', name: 'google' });
24
+ expect(resolveOAuthProviderIcon('github')).toEqual({ provider: 'FontAwesome', name: 'github' });
25
+ expect(DEFAULT_OAUTH_PROVIDER_ICONS.apple).toEqual({ provider: 'FontAwesome', name: 'apple' });
26
+ });
27
+
28
+ it('returns undefined for custom provider icons', () => {
29
+ expect(resolveOAuthProviderIcon('custom-sso')).toBeUndefined();
30
+ });
31
+ });
@@ -0,0 +1,79 @@
1
+ import type { OAuthProviderIconSpec } from './types';
2
+
3
+ export const DEFAULT_OAUTH_PROVIDER_ICONS = {
4
+ apple: { provider: 'FontAwesome', name: 'apple' },
5
+ azure: { provider: 'FontAwesome5', name: 'microsoft' },
6
+ bitbucket: { provider: 'FontAwesome', name: 'bitbucket' },
7
+ discord: { provider: 'FontAwesome5', name: 'discord' },
8
+ facebook: { provider: 'FontAwesome', name: 'facebook' },
9
+ figma: { provider: 'FontAwesome5', name: 'figma' },
10
+ github: { provider: 'FontAwesome', name: 'github' },
11
+ gitlab: { provider: 'FontAwesome5', name: 'gitlab' },
12
+ google: { provider: 'FontAwesome', name: 'google' },
13
+ linkedin: { provider: 'FontAwesome', name: 'linkedin' },
14
+ microsoft: { provider: 'FontAwesome5', name: 'microsoft' },
15
+ slack: { provider: 'FontAwesome5', name: 'slack' },
16
+ spotify: { provider: 'FontAwesome', name: 'spotify' },
17
+ twitch: { provider: 'FontAwesome5', name: 'twitch' },
18
+ twitter: { provider: 'FontAwesome', name: 'twitter' },
19
+ x: { provider: 'FontAwesome6', name: 'x-twitter' },
20
+ zoom: { provider: 'FontAwesome5', name: 'video' },
21
+ } as const satisfies Record<string, OAuthProviderIconSpec>;
22
+
23
+ const DEFAULT_OAUTH_PROVIDER_LABELS = {
24
+ apple: 'Apple',
25
+ azure: 'Microsoft',
26
+ bitbucket: 'Bitbucket',
27
+ discord: 'Discord',
28
+ facebook: 'Facebook',
29
+ figma: 'Figma',
30
+ github: 'GitHub',
31
+ gitlab: 'GitLab',
32
+ google: 'Google',
33
+ kakao: 'Kakao',
34
+ keycloak: 'Keycloak',
35
+ linkedin: 'LinkedIn',
36
+ microsoft: 'Microsoft',
37
+ notion: 'Notion',
38
+ slack: 'Slack',
39
+ spotify: 'Spotify',
40
+ twitch: 'Twitch',
41
+ twitter: 'Twitter',
42
+ workos: 'WorkOS',
43
+ x: 'X',
44
+ zoom: 'Zoom',
45
+ } as const satisfies Record<string, string>;
46
+
47
+ const oauthProviderLabels: Partial<Record<string, string>> = DEFAULT_OAUTH_PROVIDER_LABELS;
48
+
49
+ export function resolveOAuthProviderIcon(providerId: string): OAuthProviderIconSpec | undefined {
50
+ const normalizedProviderId = normalizeOAuthProviderId(providerId);
51
+
52
+ return DEFAULT_OAUTH_PROVIDER_ICONS[
53
+ normalizedProviderId as keyof typeof DEFAULT_OAUTH_PROVIDER_ICONS
54
+ ];
55
+ }
56
+
57
+ export function resolveOAuthProviderLabel(providerId: string): string {
58
+ const normalizedProviderId = normalizeOAuthProviderId(providerId);
59
+ const knownLabel = oauthProviderLabels[normalizedProviderId];
60
+
61
+ return knownLabel ?? titleCaseProviderId(normalizedProviderId);
62
+ }
63
+
64
+ function normalizeOAuthProviderId(providerId: string): string {
65
+ return providerId.trim().toLowerCase();
66
+ }
67
+
68
+ function titleCaseProviderId(providerId: string): string {
69
+ const words = providerId
70
+ .split(/[-_\s]+/u)
71
+ .map((word) => word.trim())
72
+ .filter((word) => word.length > 0);
73
+
74
+ if (words.length === 0) {
75
+ return 'Provider';
76
+ }
77
+
78
+ return words.map((word) => `${word.charAt(0).toUpperCase()}${word.slice(1)}`).join(' ');
79
+ }
@@ -1,5 +1,6 @@
1
1
  import type React from 'react';
2
2
 
3
+ import type { ButtonProps } from '../../components/button';
3
4
  import type { FormFieldConfig, FormValues } from '../../components/form';
4
5
  import type { ZoraBaseProps } from '../../theme/ZoraBaseProps';
5
6
 
@@ -65,3 +66,45 @@ export interface OtpFormProps extends AuthFormBaseProps {
65
66
  onSubmit: (values: OtpFormValues) => void | Promise<void>;
66
67
  onResend?: () => void | Promise<void>;
67
68
  }
69
+
70
+ export interface OAuthProviderIconSpec {
71
+ name: string;
72
+ provider?: string;
73
+ size?: number | string;
74
+ color?: string;
75
+ }
76
+
77
+ export interface OAuthProviderItem {
78
+ id: string;
79
+ label?: React.ReactNode;
80
+ icon?: OAuthProviderIconSpec;
81
+ disabled?: boolean;
82
+ loading?: boolean;
83
+ }
84
+
85
+ export interface OAuthProviderButtonProps extends ZoraBaseProps {
86
+ providerId: string;
87
+ label?: React.ReactNode;
88
+ icon?: OAuthProviderIconSpec;
89
+ disabled?: boolean;
90
+ loading?: boolean;
91
+ fullWidth?: boolean;
92
+ size?: ButtonProps['size'];
93
+ variant?: ButtonProps['variant'];
94
+ color?: ButtonProps['color'];
95
+ onPress?: (providerId: string) => void | Promise<void>;
96
+ }
97
+
98
+ export type OAuthProviderListLayout = 'stack' | 'inline';
99
+
100
+ export interface OAuthProviderListProps extends ZoraBaseProps {
101
+ providers: readonly OAuthProviderItem[];
102
+ disabled?: boolean;
103
+ loading?: boolean;
104
+ fullWidth?: boolean;
105
+ layout?: OAuthProviderListLayout;
106
+ size?: ButtonProps['size'];
107
+ variant?: ButtonProps['variant'];
108
+ color?: ButtonProps['color'];
109
+ onProviderPress?: (providerId: string) => void | Promise<void>;
110
+ }