@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.
- package/CHANGELOG.md +12 -0
- package/README.md +5 -3
- package/dist/components/gradient/Gradient.d.ts +10 -0
- package/dist/components/gradient/Gradient.d.ts.map +1 -0
- package/dist/components/gradient/Gradient.js +21 -0
- package/dist/components/gradient/Gradient.js.map +1 -0
- package/dist/components/gradient/index.d.ts +3 -0
- package/dist/components/gradient/index.d.ts.map +1 -0
- package/dist/components/gradient/index.js +2 -0
- package/dist/components/gradient/index.js.map +1 -0
- package/dist/components/gradient/meta.d.ts +9 -0
- package/dist/components/gradient/meta.d.ts.map +1 -0
- package/dist/components/gradient/meta.js +9 -0
- package/dist/components/gradient/meta.js.map +1 -0
- package/dist/components/gradient/types.d.ts +23 -0
- package/dist/components/gradient/types.d.ts.map +1 -0
- package/dist/components/gradient/types.js +2 -0
- package/dist/components/gradient/types.js.map +1 -0
- package/dist/components/splash-screen/SplashScreen.d.ts +11 -0
- package/dist/components/splash-screen/SplashScreen.d.ts.map +1 -0
- package/dist/components/splash-screen/SplashScreen.js +54 -0
- package/dist/components/splash-screen/SplashScreen.js.map +1 -0
- package/dist/components/splash-screen/index.d.ts +3 -0
- package/dist/components/splash-screen/index.d.ts.map +1 -0
- package/dist/components/splash-screen/index.js +2 -0
- package/dist/components/splash-screen/index.js.map +1 -0
- package/dist/components/splash-screen/meta.d.ts +9 -0
- package/dist/components/splash-screen/meta.d.ts.map +1 -0
- package/dist/components/splash-screen/meta.js +9 -0
- package/dist/components/splash-screen/meta.js.map +1 -0
- package/dist/components/splash-screen/types.d.ts +16 -0
- package/dist/components/splash-screen/types.d.ts.map +1 -0
- package/dist/components/splash-screen/types.js +2 -0
- package/dist/components/splash-screen/types.js.map +1 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/metadata/componentMeta.d.ts.map +1 -1
- package/dist/metadata/componentMeta.js +7 -1
- package/dist/metadata/componentMeta.js.map +1 -1
- package/dist/patterns/auth/OAuthProviderButton.d.ts +10 -0
- package/dist/patterns/auth/OAuthProviderButton.d.ts.map +1 -0
- package/dist/patterns/auth/OAuthProviderButton.js +21 -0
- package/dist/patterns/auth/OAuthProviderButton.js.map +1 -0
- package/dist/patterns/auth/OAuthProviderList.d.ts +7 -0
- package/dist/patterns/auth/OAuthProviderList.d.ts.map +1 -0
- package/dist/patterns/auth/OAuthProviderList.js +16 -0
- package/dist/patterns/auth/OAuthProviderList.js.map +1 -0
- package/dist/patterns/auth/index.d.ts +4 -1
- package/dist/patterns/auth/index.d.ts.map +1 -1
- package/dist/patterns/auth/index.js +3 -0
- package/dist/patterns/auth/index.js.map +1 -1
- package/dist/patterns/auth/meta.d.ts +16 -0
- package/dist/patterns/auth/meta.d.ts.map +1 -1
- package/dist/patterns/auth/meta.js +16 -0
- package/dist/patterns/auth/meta.js.map +1 -1
- package/dist/patterns/auth/oauthProviders.d.ts +74 -0
- package/dist/patterns/auth/oauthProviders.d.ts.map +1 -0
- package/dist/patterns/auth/oauthProviders.js +66 -0
- package/dist/patterns/auth/oauthProviders.js.map +1 -0
- package/dist/patterns/auth/types.d.ts +38 -0
- package/dist/patterns/auth/types.d.ts.map +1 -1
- package/dist/patterns/auth/types.js.map +1 -1
- package/package.json +7 -2
- package/src/components/gradient/Gradient.tsx +53 -0
- package/src/components/gradient/index.ts +8 -0
- package/src/components/gradient/meta.ts +10 -0
- package/src/components/gradient/types.ts +26 -0
- package/src/components/splash-screen/SplashScreen.tsx +98 -0
- package/src/components/splash-screen/index.ts +2 -0
- package/src/components/splash-screen/meta.ts +10 -0
- package/src/components/splash-screen/types.ts +18 -0
- package/src/index.ts +26 -1
- package/src/metadata/componentMeta.ts +8 -0
- package/src/patterns/auth/OAuthProviderButton.tsx +51 -0
- package/src/patterns/auth/OAuthProviderList.tsx +54 -0
- package/src/patterns/auth/index.ts +12 -0
- package/src/patterns/auth/meta.ts +18 -0
- package/src/patterns/auth/oauthProviders.test.ts +31 -0
- package/src/patterns/auth/oauthProviders.ts +79 -0
- 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,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 {
|
|
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
|
+
}
|