@slyxup/ui 0.1.0 → 0.2.1
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/.turbo/turbo-build.log +4 -0
- package/CHANGELOG.md +27 -0
- package/LICENSE +21 -0
- package/README.md +105 -0
- package/dist/components/BillingPortal/BillingPortal.d.ts +22 -0
- package/dist/components/BillingPortal/BillingPortal.d.ts.map +1 -0
- package/dist/components/BillingPortal/BillingPortal.js +59 -0
- package/dist/components/BillingPortal/BillingPortal.js.map +1 -0
- package/dist/components/EmailVerification/EmailVerification.d.ts +9 -0
- package/dist/components/EmailVerification/EmailVerification.d.ts.map +1 -0
- package/dist/components/EmailVerification/EmailVerification.js +72 -0
- package/dist/components/EmailVerification/EmailVerification.js.map +1 -0
- package/dist/components/ForgotPassword/ForgotPassword.d.ts +8 -0
- package/dist/components/ForgotPassword/ForgotPassword.d.ts.map +1 -0
- package/dist/components/ForgotPassword/ForgotPassword.js +43 -0
- package/dist/components/ForgotPassword/ForgotPassword.js.map +1 -0
- package/dist/components/PricingTable/PricingTable.d.ts +19 -0
- package/dist/components/PricingTable/PricingTable.d.ts.map +1 -0
- package/dist/components/PricingTable/PricingTable.js +52 -0
- package/dist/components/PricingTable/PricingTable.js.map +1 -0
- package/dist/components/ResetPassword/ResetPassword.d.ts +9 -0
- package/dist/components/ResetPassword/ResetPassword.d.ts.map +1 -0
- package/dist/components/ResetPassword/ResetPassword.js +50 -0
- package/dist/components/ResetPassword/ResetPassword.js.map +1 -0
- package/dist/components/SignIn/SignIn.d.ts +11 -0
- package/dist/components/SignIn/SignIn.d.ts.map +1 -0
- package/dist/components/SignIn/SignIn.js +53 -0
- package/dist/components/SignIn/SignIn.js.map +1 -0
- package/dist/components/SignUp/SignUp.d.ts +8 -0
- package/dist/components/SignUp/SignUp.d.ts.map +1 -0
- package/dist/components/SignUp/SignUp.js +54 -0
- package/dist/components/SignUp/SignUp.js.map +1 -0
- package/dist/components/SocialButtons/SocialButtons.d.ts +9 -0
- package/dist/components/SocialButtons/SocialButtons.d.ts.map +1 -0
- package/dist/components/SocialButtons/SocialButtons.js +16 -0
- package/dist/components/SocialButtons/SocialButtons.js.map +1 -0
- package/dist/components/UserButton/UserButton.d.ts +3 -0
- package/dist/components/UserButton/UserButton.d.ts.map +1 -0
- package/dist/components/UserButton/UserButton.js +43 -0
- package/dist/components/UserButton/UserButton.js.map +1 -0
- package/dist/components/UserProfile/UserProfile.d.ts +3 -0
- package/dist/components/UserProfile/UserProfile.d.ts.map +1 -0
- package/dist/components/UserProfile/UserProfile.js +38 -0
- package/dist/components/UserProfile/UserProfile.js.map +1 -0
- package/dist/icons.d.ts +6 -0
- package/dist/icons.d.ts.map +1 -0
- package/dist/icons.js +15 -0
- package/dist/icons.js.map +1 -0
- package/dist/index.d.ts +15 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -1
- package/dist/index.js.map +1 -1
- package/dist/styles.d.ts +9 -0
- package/dist/styles.d.ts.map +1 -0
- package/dist/styles.js +219 -0
- package/dist/styles.js.map +1 -0
- package/package.json +35 -3
- package/src/components/BillingPortal/BillingPortal.tsx +182 -0
- package/src/components/EmailVerification/EmailVerification.tsx +180 -0
- package/src/components/ForgotPassword/ForgotPassword.tsx +122 -0
- package/src/components/PricingTable/PricingTable.tsx +155 -0
- package/src/components/ResetPassword/ResetPassword.tsx +120 -0
- package/src/components/SignIn/SignIn.tsx +168 -0
- package/src/components/SignUp/SignUp.tsx +181 -0
- package/src/components/SocialButtons/SocialButtons.tsx +39 -0
- package/src/components/UserButton/UserButton.tsx +89 -0
- package/src/components/UserProfile/UserProfile.tsx +93 -0
- package/src/icons.tsx +71 -0
- package/src/index.ts +37 -1
- package/src/styles.ts +221 -0
- package/test/components.test.tsx +135 -0
- package/test/icons.test.tsx +32 -0
- package/test/styles.test.ts +47 -0
- package/vitest.config.ts +2 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { SlyxupError } from '@slyxup/core';
|
|
2
|
+
import { useAuth } from '@slyxup/react';
|
|
3
|
+
import { type FormEvent, useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { GitHubIcon, GoogleIcon, KeyholeMark } from '../../icons';
|
|
5
|
+
|
|
6
|
+
export interface SignUpProps {
|
|
7
|
+
social?: boolean;
|
|
8
|
+
onSuccess?: () => void;
|
|
9
|
+
onSignInClick?: () => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Email/password + OAuth sign-up card. */
|
|
13
|
+
export function SignUp({
|
|
14
|
+
social = true,
|
|
15
|
+
onSuccess,
|
|
16
|
+
onSignInClick,
|
|
17
|
+
}: SignUpProps) {
|
|
18
|
+
const { signUp, client } = useAuth() as unknown as {
|
|
19
|
+
signUp: ReturnType<typeof useAuth>['signUp'];
|
|
20
|
+
client: { publishableKey?: string };
|
|
21
|
+
};
|
|
22
|
+
const [firstName, setFirstName] = useState('');
|
|
23
|
+
const [email, setEmail] = useState('');
|
|
24
|
+
const [password, setPassword] = useState('');
|
|
25
|
+
const [busy, setBusy] = useState(false);
|
|
26
|
+
const [error, setError] = useState<string | null>(null);
|
|
27
|
+
const cardRef = useRef<HTMLDivElement>(null);
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (error) {
|
|
31
|
+
const t = setTimeout(() => setError(null), 4000);
|
|
32
|
+
return () => clearTimeout(t);
|
|
33
|
+
}
|
|
34
|
+
}, [error]);
|
|
35
|
+
|
|
36
|
+
async function onSubmit(e: FormEvent) {
|
|
37
|
+
e.preventDefault();
|
|
38
|
+
setBusy(true);
|
|
39
|
+
setError(null);
|
|
40
|
+
try {
|
|
41
|
+
await signUp({ email, password, firstName: firstName || undefined });
|
|
42
|
+
onSuccess?.();
|
|
43
|
+
} catch (err) {
|
|
44
|
+
setError(
|
|
45
|
+
err instanceof SlyxupError
|
|
46
|
+
? err.message
|
|
47
|
+
: 'Something went wrong. Try again.'
|
|
48
|
+
);
|
|
49
|
+
} finally {
|
|
50
|
+
setBusy(false);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function oauth(provider: 'google' | 'github') {
|
|
55
|
+
window.location.href = `/v1/oauth/${provider}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const missingKey =
|
|
59
|
+
!client.publishableKey ||
|
|
60
|
+
client.publishableKey === 'pk_test_missing' ||
|
|
61
|
+
client.publishableKey.includes('REPLACE');
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<div ref={cardRef} className={`slx-card${error ? ' slx-card-error' : ''}`}>
|
|
65
|
+
{missingKey && (
|
|
66
|
+
<p
|
|
67
|
+
style={{
|
|
68
|
+
fontSize: 12,
|
|
69
|
+
background: '#fff3cd',
|
|
70
|
+
border: '1px solid #ffe69c',
|
|
71
|
+
borderRadius: 8,
|
|
72
|
+
padding: '8px 10px',
|
|
73
|
+
marginBottom: 14,
|
|
74
|
+
lineHeight: 1.4,
|
|
75
|
+
}}
|
|
76
|
+
>
|
|
77
|
+
<strong>Setup:</strong> Add{' '}
|
|
78
|
+
<code>NEXT_PUBLIC_SLYXUP_PUBLISHABLE_KEY</code> — run{' '}
|
|
79
|
+
<code>npx @slyxup/cli keys create</code>
|
|
80
|
+
</p>
|
|
81
|
+
)}
|
|
82
|
+
<div className="slx-mark">
|
|
83
|
+
<KeyholeMark />
|
|
84
|
+
</div>
|
|
85
|
+
<h1 className="slx-title">Create your account</h1>
|
|
86
|
+
<p className="slx-subtitle">A minute to set up. Sign in forever after.</p>
|
|
87
|
+
|
|
88
|
+
{social && (
|
|
89
|
+
<>
|
|
90
|
+
<div className="slx-social">
|
|
91
|
+
<button
|
|
92
|
+
type="button"
|
|
93
|
+
className="slx-social-btn"
|
|
94
|
+
onClick={() => oauth('google')}
|
|
95
|
+
>
|
|
96
|
+
<GoogleIcon /> Continue with Google
|
|
97
|
+
</button>
|
|
98
|
+
<button
|
|
99
|
+
type="button"
|
|
100
|
+
className="slx-social-btn"
|
|
101
|
+
onClick={() => oauth('github')}
|
|
102
|
+
>
|
|
103
|
+
<GitHubIcon /> Continue with GitHub
|
|
104
|
+
</button>
|
|
105
|
+
</div>
|
|
106
|
+
<div className="slx-divider">or</div>
|
|
107
|
+
</>
|
|
108
|
+
)}
|
|
109
|
+
|
|
110
|
+
{error && (
|
|
111
|
+
<p className="slx-error-text" role="alert">
|
|
112
|
+
{error}
|
|
113
|
+
</p>
|
|
114
|
+
)}
|
|
115
|
+
|
|
116
|
+
<form onSubmit={onSubmit}>
|
|
117
|
+
<div className="slx-field">
|
|
118
|
+
<label className="slx-label" htmlFor="slx-signup-name">
|
|
119
|
+
First name
|
|
120
|
+
</label>
|
|
121
|
+
<input
|
|
122
|
+
id="slx-signup-name"
|
|
123
|
+
className="slx-input"
|
|
124
|
+
type="text"
|
|
125
|
+
autoComplete="given-name"
|
|
126
|
+
placeholder="Ada"
|
|
127
|
+
value={firstName}
|
|
128
|
+
onChange={(e) => setFirstName(e.target.value)}
|
|
129
|
+
/>
|
|
130
|
+
</div>
|
|
131
|
+
<div className="slx-field">
|
|
132
|
+
<label className="slx-label" htmlFor="slx-signup-email">
|
|
133
|
+
Email
|
|
134
|
+
</label>
|
|
135
|
+
<input
|
|
136
|
+
id="slx-signup-email"
|
|
137
|
+
className="slx-input"
|
|
138
|
+
type="email"
|
|
139
|
+
autoComplete="email"
|
|
140
|
+
placeholder="you@example.com"
|
|
141
|
+
value={email}
|
|
142
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
143
|
+
required
|
|
144
|
+
/>
|
|
145
|
+
</div>
|
|
146
|
+
<div className="slx-field">
|
|
147
|
+
<label className="slx-label" htmlFor="slx-signup-password">
|
|
148
|
+
Password
|
|
149
|
+
</label>
|
|
150
|
+
<input
|
|
151
|
+
id="slx-signup-password"
|
|
152
|
+
className="slx-input"
|
|
153
|
+
type="password"
|
|
154
|
+
autoComplete="new-password"
|
|
155
|
+
placeholder="At least 8 characters"
|
|
156
|
+
value={password}
|
|
157
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
158
|
+
required
|
|
159
|
+
minLength={8}
|
|
160
|
+
/>
|
|
161
|
+
<p className="slx-hint">
|
|
162
|
+
Use 8+ characters with a mix of letters and numbers.
|
|
163
|
+
</p>
|
|
164
|
+
</div>
|
|
165
|
+
<button className="slx-btn" type="submit" disabled={busy}>
|
|
166
|
+
{busy && <span className="slx-spinner" aria-hidden="true" />}
|
|
167
|
+
{busy ? 'Creating account…' : 'Create account'}
|
|
168
|
+
</button>
|
|
169
|
+
</form>
|
|
170
|
+
|
|
171
|
+
{onSignInClick && (
|
|
172
|
+
<p className="slx-footer">
|
|
173
|
+
Already have an account?{' '}
|
|
174
|
+
<button type="button" className="slx-link" onClick={onSignInClick}>
|
|
175
|
+
Sign in
|
|
176
|
+
</button>
|
|
177
|
+
</p>
|
|
178
|
+
)}
|
|
179
|
+
</div>
|
|
180
|
+
);
|
|
181
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { GitHubIcon, GoogleIcon } from '../../icons';
|
|
2
|
+
|
|
3
|
+
export interface SocialButtonsProps {
|
|
4
|
+
/** Show only these providers. Default: both */
|
|
5
|
+
providers?: Array<'google' | 'github'>;
|
|
6
|
+
/** OAuth start path base (default /v1/oauth) */
|
|
7
|
+
basePath?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const META = {
|
|
11
|
+
google: { label: 'Continue with Google', Icon: GoogleIcon },
|
|
12
|
+
github: { label: 'Continue with GitHub', Icon: GitHubIcon },
|
|
13
|
+
} as const;
|
|
14
|
+
|
|
15
|
+
/** Provider buttons that redirect to hosted OAuth start. */
|
|
16
|
+
export function SocialButtons({
|
|
17
|
+
providers = ['google', 'github'],
|
|
18
|
+
basePath = '/v1/oauth',
|
|
19
|
+
}: SocialButtonsProps) {
|
|
20
|
+
return (
|
|
21
|
+
<div className="slx-social">
|
|
22
|
+
{providers.map((p) => {
|
|
23
|
+
const { label, Icon } = META[p];
|
|
24
|
+
return (
|
|
25
|
+
<button
|
|
26
|
+
key={p}
|
|
27
|
+
type="button"
|
|
28
|
+
className="slx-social-btn"
|
|
29
|
+
onClick={() => {
|
|
30
|
+
window.location.href = `${basePath}/${p}`;
|
|
31
|
+
}}
|
|
32
|
+
>
|
|
33
|
+
<Icon /> {label}
|
|
34
|
+
</button>
|
|
35
|
+
);
|
|
36
|
+
})}
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { useAuth, useUser } from '@slyxup/react';
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
function initials(name: string | null | undefined, email: string): string {
|
|
5
|
+
if (name?.trim()) return name.trim().slice(0, 1).toUpperCase();
|
|
6
|
+
return email.slice(0, 1).toUpperCase();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Avatar + dropdown with profile actions and sign out. */
|
|
10
|
+
export function UserButton() {
|
|
11
|
+
const { isLoaded, user } = useUser();
|
|
12
|
+
const { signOut } = useAuth();
|
|
13
|
+
const [open, setOpen] = useState(false);
|
|
14
|
+
const wrapRef = useRef<HTMLDivElement>(null);
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
function onDocClick(e: MouseEvent) {
|
|
18
|
+
if (wrapRef.current && !wrapRef.current.contains(e.target as Node))
|
|
19
|
+
setOpen(false);
|
|
20
|
+
}
|
|
21
|
+
function onKey(e: KeyboardEvent) {
|
|
22
|
+
if (e.key === 'Escape') setOpen(false);
|
|
23
|
+
}
|
|
24
|
+
document.addEventListener('mousedown', onDocClick);
|
|
25
|
+
document.addEventListener('keydown', onKey);
|
|
26
|
+
return () => {
|
|
27
|
+
document.removeEventListener('mousedown', onDocClick);
|
|
28
|
+
document.removeEventListener('keydown', onKey);
|
|
29
|
+
};
|
|
30
|
+
}, []);
|
|
31
|
+
|
|
32
|
+
if (!isLoaded)
|
|
33
|
+
return <div className="slx-userbtn-avatar" aria-hidden="true" />;
|
|
34
|
+
const email = user?.email ?? '';
|
|
35
|
+
const name = user?.firstName;
|
|
36
|
+
|
|
37
|
+
async function onSignOut() {
|
|
38
|
+
await signOut();
|
|
39
|
+
setOpen(false);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<div className="slx-userbtn-wrap" ref={wrapRef}>
|
|
44
|
+
<button
|
|
45
|
+
type="button"
|
|
46
|
+
className="slx-userbtn-avatar"
|
|
47
|
+
onClick={() => setOpen((o) => !o)}
|
|
48
|
+
aria-haspopup="menu"
|
|
49
|
+
aria-expanded={open}
|
|
50
|
+
aria-label="Account menu"
|
|
51
|
+
>
|
|
52
|
+
{user?.avatarUrl ? (
|
|
53
|
+
<img src={user.avatarUrl} alt="" />
|
|
54
|
+
) : (
|
|
55
|
+
initials(name, email || '?')
|
|
56
|
+
)}
|
|
57
|
+
</button>
|
|
58
|
+
|
|
59
|
+
{open && (
|
|
60
|
+
<div className="slx-menu" role="menu">
|
|
61
|
+
<div className="slx-menu-header">
|
|
62
|
+
<p className="slx-menu-name">
|
|
63
|
+
{name
|
|
64
|
+
? `${name}${user?.lastName ? ` ${user.lastName}` : ''}`
|
|
65
|
+
: email.split('@')[0]}
|
|
66
|
+
</p>
|
|
67
|
+
<p className="slx-menu-email">{email}</p>
|
|
68
|
+
</div>
|
|
69
|
+
<button
|
|
70
|
+
type="button"
|
|
71
|
+
className="slx-menu-item"
|
|
72
|
+
role="menuitem"
|
|
73
|
+
onClick={() => setOpen(false)}
|
|
74
|
+
>
|
|
75
|
+
Profile settings
|
|
76
|
+
</button>
|
|
77
|
+
<button
|
|
78
|
+
type="button"
|
|
79
|
+
className="slx-menu-item slx-menu-item-danger"
|
|
80
|
+
role="menuitem"
|
|
81
|
+
onClick={onSignOut}
|
|
82
|
+
>
|
|
83
|
+
Sign out
|
|
84
|
+
</button>
|
|
85
|
+
</div>
|
|
86
|
+
)}
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { useAuth, useUser } from '@slyxup/react';
|
|
2
|
+
import { type FormEvent, useEffect, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
/** Edit first/last name + avatar URL. */
|
|
5
|
+
export function UserProfile() {
|
|
6
|
+
const { isLoaded, user, reload } = useUser();
|
|
7
|
+
const { client } = useAuth();
|
|
8
|
+
const [firstName, setFirstName] = useState('');
|
|
9
|
+
const [lastName, setLastName] = useState('');
|
|
10
|
+
const [avatarUrl, setAvatarUrl] = useState('');
|
|
11
|
+
const [busy, setBusy] = useState(false);
|
|
12
|
+
const [saved, setSaved] = useState(false);
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (user) {
|
|
16
|
+
setFirstName(user.firstName ?? '');
|
|
17
|
+
setLastName(user.lastName ?? '');
|
|
18
|
+
setAvatarUrl(user.avatarUrl ?? '');
|
|
19
|
+
}
|
|
20
|
+
}, [user]);
|
|
21
|
+
|
|
22
|
+
async function onSubmit(e: FormEvent) {
|
|
23
|
+
e.preventDefault();
|
|
24
|
+
setBusy(true);
|
|
25
|
+
setSaved(false);
|
|
26
|
+
try {
|
|
27
|
+
await client.users.update({ firstName, lastName, avatarUrl });
|
|
28
|
+
await reload();
|
|
29
|
+
setSaved(true);
|
|
30
|
+
setTimeout(() => setSaved(false), 2500);
|
|
31
|
+
} finally {
|
|
32
|
+
setBusy(false);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!isLoaded) return <div className="slx-card" aria-busy="true" />;
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className="slx-card">
|
|
40
|
+
<h1 className="slx-title">Profile</h1>
|
|
41
|
+
<p className="slx-subtitle">Signed in as {user?.email}</p>
|
|
42
|
+
|
|
43
|
+
{saved && (
|
|
44
|
+
<p className="slx-error-text" style={{ color: '#34a853' }}>
|
|
45
|
+
Profile saved.
|
|
46
|
+
</p>
|
|
47
|
+
)}
|
|
48
|
+
|
|
49
|
+
<form onSubmit={onSubmit}>
|
|
50
|
+
<div className="slx-field">
|
|
51
|
+
<label className="slx-label" htmlFor="slx-profile-first">
|
|
52
|
+
First name
|
|
53
|
+
</label>
|
|
54
|
+
<input
|
|
55
|
+
id="slx-profile-first"
|
|
56
|
+
className="slx-input"
|
|
57
|
+
type="text"
|
|
58
|
+
value={firstName}
|
|
59
|
+
onChange={(e) => setFirstName(e.target.value)}
|
|
60
|
+
/>
|
|
61
|
+
</div>
|
|
62
|
+
<div className="slx-field">
|
|
63
|
+
<label className="slx-label" htmlFor="slx-profile-last">
|
|
64
|
+
Last name
|
|
65
|
+
</label>
|
|
66
|
+
<input
|
|
67
|
+
id="slx-profile-last"
|
|
68
|
+
className="slx-input"
|
|
69
|
+
type="text"
|
|
70
|
+
value={lastName}
|
|
71
|
+
onChange={(e) => setLastName(e.target.value)}
|
|
72
|
+
/>
|
|
73
|
+
</div>
|
|
74
|
+
<div className="slx-field">
|
|
75
|
+
<label className="slx-label" htmlFor="slx-profile-avatar">
|
|
76
|
+
Avatar URL
|
|
77
|
+
</label>
|
|
78
|
+
<input
|
|
79
|
+
id="slx-profile-avatar"
|
|
80
|
+
className="slx-input"
|
|
81
|
+
type="url"
|
|
82
|
+
placeholder="https://…"
|
|
83
|
+
value={avatarUrl}
|
|
84
|
+
onChange={(e) => setAvatarUrl(e.target.value)}
|
|
85
|
+
/>
|
|
86
|
+
</div>
|
|
87
|
+
<button className="slx-btn" type="submit" disabled={busy}>
|
|
88
|
+
{busy ? 'Saving…' : 'Save changes'}
|
|
89
|
+
</button>
|
|
90
|
+
</form>
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
}
|
package/src/icons.tsx
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/** Shared icons (stroke = currentColor) */
|
|
2
|
+
|
|
3
|
+
export function KeyholeMark() {
|
|
4
|
+
return (
|
|
5
|
+
<svg
|
|
6
|
+
width="22"
|
|
7
|
+
height="22"
|
|
8
|
+
viewBox="0 0 24 24"
|
|
9
|
+
fill="none"
|
|
10
|
+
aria-hidden="true"
|
|
11
|
+
>
|
|
12
|
+
<circle cx="12" cy="9" r="3.2" fill="white" />
|
|
13
|
+
<path d="M10.6 11.5h2.8l.7 6h-4.2l.7-6z" fill="white" />
|
|
14
|
+
</svg>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function GoogleIcon() {
|
|
19
|
+
return (
|
|
20
|
+
<svg width="17" height="17" viewBox="0 0 18 18" aria-hidden="true">
|
|
21
|
+
<path
|
|
22
|
+
fill="#4285F4"
|
|
23
|
+
d="M17.64 9.2c0-.63-.06-1.25-.16-1.84H9v3.48h4.84a4.14 4.14 0 0 1-1.8 2.72v2.26h2.92a8.78 8.78 0 0 0 2.68-6.62z"
|
|
24
|
+
/>
|
|
25
|
+
<path
|
|
26
|
+
fill="#34A853"
|
|
27
|
+
d="M9 18c2.43 0 4.47-.8 5.96-2.18l-2.91-2.26c-.81.54-1.84.86-3.05.86-2.34 0-4.33-1.58-5.04-3.71H.96v2.33A9 9 0 0 0 9 18z"
|
|
28
|
+
/>
|
|
29
|
+
<path
|
|
30
|
+
fill="#FBBC05"
|
|
31
|
+
d="M3.96 10.71a5.41 5.41 0 0 1 0-3.42V4.96H.96a9 9 0 0 0 0 8.08l3-2.33z"
|
|
32
|
+
/>
|
|
33
|
+
<path
|
|
34
|
+
fill="#EA4335"
|
|
35
|
+
d="M9 3.58c1.32 0 2.5.45 3.44 1.35l2.58-2.59A9 9 0 0 0 .96 4.96l3 2.33C4.67 5.16 6.66 3.58 9 3.58z"
|
|
36
|
+
/>
|
|
37
|
+
</svg>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function GitHubIcon() {
|
|
42
|
+
return (
|
|
43
|
+
<svg
|
|
44
|
+
width="17"
|
|
45
|
+
height="17"
|
|
46
|
+
viewBox="0 0 16 16"
|
|
47
|
+
fill="currentColor"
|
|
48
|
+
aria-hidden="true"
|
|
49
|
+
>
|
|
50
|
+
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8z" />
|
|
51
|
+
</svg>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function CheckIcon() {
|
|
56
|
+
return (
|
|
57
|
+
<svg
|
|
58
|
+
width="22"
|
|
59
|
+
height="22"
|
|
60
|
+
viewBox="0 0 24 24"
|
|
61
|
+
fill="none"
|
|
62
|
+
stroke="currentColor"
|
|
63
|
+
strokeWidth="2.4"
|
|
64
|
+
strokeLinecap="round"
|
|
65
|
+
strokeLinejoin="round"
|
|
66
|
+
aria-hidden="true"
|
|
67
|
+
>
|
|
68
|
+
<path d="M20 6L9 17l-5-5" />
|
|
69
|
+
</svg>
|
|
70
|
+
);
|
|
71
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1 +1,37 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { injectStyles, CSS } from './styles';
|
|
2
|
+
export { KeyholeMark, GoogleIcon, GitHubIcon, CheckIcon } from './icons';
|
|
3
|
+
|
|
4
|
+
export { SignIn, type SignInProps } from './components/SignIn/SignIn';
|
|
5
|
+
export { SignUp, type SignUpProps } from './components/SignUp/SignUp';
|
|
6
|
+
export { UserButton } from './components/UserButton/UserButton';
|
|
7
|
+
export { UserProfile } from './components/UserProfile/UserProfile';
|
|
8
|
+
export {
|
|
9
|
+
ForgotPassword,
|
|
10
|
+
type ForgotPasswordProps,
|
|
11
|
+
} from './components/ForgotPassword/ForgotPassword';
|
|
12
|
+
export {
|
|
13
|
+
ResetPassword,
|
|
14
|
+
type ResetPasswordProps,
|
|
15
|
+
} from './components/ResetPassword/ResetPassword';
|
|
16
|
+
export {
|
|
17
|
+
EmailVerification,
|
|
18
|
+
type EmailVerificationProps,
|
|
19
|
+
} from './components/EmailVerification/EmailVerification';
|
|
20
|
+
export {
|
|
21
|
+
SocialButtons,
|
|
22
|
+
type SocialButtonsProps,
|
|
23
|
+
} from './components/SocialButtons/SocialButtons';
|
|
24
|
+
|
|
25
|
+
import { useEffect } from 'react';
|
|
26
|
+
import { injectStyles } from './styles';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Inject the SlyxUp stylesheet once. Render inside your app root
|
|
30
|
+
* (or rely on any component auto-injecting on mount).
|
|
31
|
+
*/
|
|
32
|
+
export function SlyxUpStyles(): null {
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
injectStyles();
|
|
35
|
+
}, []);
|
|
36
|
+
return null;
|
|
37
|
+
}
|