@slyxup/ui 0.1.0 → 0.2.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 (58) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +105 -0
  3. package/dist/components/EmailVerification/EmailVerification.d.ts +9 -0
  4. package/dist/components/EmailVerification/EmailVerification.d.ts.map +1 -0
  5. package/dist/components/EmailVerification/EmailVerification.js +72 -0
  6. package/dist/components/EmailVerification/EmailVerification.js.map +1 -0
  7. package/dist/components/ForgotPassword/ForgotPassword.d.ts +8 -0
  8. package/dist/components/ForgotPassword/ForgotPassword.d.ts.map +1 -0
  9. package/dist/components/ForgotPassword/ForgotPassword.js +43 -0
  10. package/dist/components/ForgotPassword/ForgotPassword.js.map +1 -0
  11. package/dist/components/ResetPassword/ResetPassword.d.ts +9 -0
  12. package/dist/components/ResetPassword/ResetPassword.d.ts.map +1 -0
  13. package/dist/components/ResetPassword/ResetPassword.js +50 -0
  14. package/dist/components/ResetPassword/ResetPassword.js.map +1 -0
  15. package/dist/components/SignIn/SignIn.d.ts +11 -0
  16. package/dist/components/SignIn/SignIn.d.ts.map +1 -0
  17. package/dist/components/SignIn/SignIn.js +42 -0
  18. package/dist/components/SignIn/SignIn.js.map +1 -0
  19. package/dist/components/SignUp/SignUp.d.ts +8 -0
  20. package/dist/components/SignUp/SignUp.d.ts.map +1 -0
  21. package/dist/components/SignUp/SignUp.js +43 -0
  22. package/dist/components/SignUp/SignUp.js.map +1 -0
  23. package/dist/components/SocialButtons/SocialButtons.d.ts +9 -0
  24. package/dist/components/SocialButtons/SocialButtons.d.ts.map +1 -0
  25. package/dist/components/SocialButtons/SocialButtons.js +16 -0
  26. package/dist/components/SocialButtons/SocialButtons.js.map +1 -0
  27. package/dist/components/UserButton/UserButton.d.ts +3 -0
  28. package/dist/components/UserButton/UserButton.d.ts.map +1 -0
  29. package/dist/components/UserButton/UserButton.js +43 -0
  30. package/dist/components/UserButton/UserButton.js.map +1 -0
  31. package/dist/components/UserProfile/UserProfile.d.ts +3 -0
  32. package/dist/components/UserProfile/UserProfile.d.ts.map +1 -0
  33. package/dist/components/UserProfile/UserProfile.js +38 -0
  34. package/dist/components/UserProfile/UserProfile.js.map +1 -0
  35. package/dist/icons.d.ts +6 -0
  36. package/dist/icons.d.ts.map +1 -0
  37. package/dist/icons.js +15 -0
  38. package/dist/icons.js.map +1 -0
  39. package/dist/index.d.ts +15 -1
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +22 -1
  42. package/dist/index.js.map +1 -1
  43. package/dist/styles.d.ts +8 -0
  44. package/dist/styles.d.ts.map +1 -0
  45. package/dist/styles.js +198 -0
  46. package/dist/styles.js.map +1 -0
  47. package/package.json +30 -2
  48. package/src/components/EmailVerification/EmailVerification.tsx +180 -0
  49. package/src/components/ForgotPassword/ForgotPassword.tsx +122 -0
  50. package/src/components/ResetPassword/ResetPassword.tsx +120 -0
  51. package/src/components/SignIn/SignIn.tsx +143 -0
  52. package/src/components/SignUp/SignUp.tsx +156 -0
  53. package/src/components/SocialButtons/SocialButtons.tsx +39 -0
  54. package/src/components/UserButton/UserButton.tsx +89 -0
  55. package/src/components/UserProfile/UserProfile.tsx +93 -0
  56. package/src/icons.tsx +71 -0
  57. package/src/index.ts +37 -1
  58. package/src/styles.ts +199 -0
@@ -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 const placeholder = 'ui';
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
+ }
package/src/styles.ts ADDED
@@ -0,0 +1,199 @@
1
+ /**
2
+ * SlyxUp UI — design tokens + stylesheet.
3
+ * Injected once via <SlyxUpStyles />. Theme with CSS variables on :root or .slyxup-scope.
4
+ */
5
+
6
+ export const CSS = `
7
+ .slyxup-root {
8
+ --slx-accent: #5b5bd6;
9
+ --slx-accent-hover: #4c4cc4;
10
+ --slx-accent-soft: rgba(91, 91, 214, 0.1);
11
+ --slx-bg: #ffffff;
12
+ --slx-bg-page: #f7f7fa;
13
+ --slx-ink: #16161d;
14
+ --slx-muted: #6f6f7b;
15
+ --slx-border: #e6e6ec;
16
+ --slx-danger: #d64550;
17
+ --slx-radius: 12px;
18
+ --slx-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
19
+ --slx-mono: ui-monospace, SFMono-Regular, Menlo, monospace;
20
+
21
+ font-family: var(--slx-font);
22
+ color: var(--slx-ink);
23
+ -webkit-font-smoothing: antialiased;
24
+ }
25
+ @media (prefers-color-scheme: dark) {
26
+ .slyxup-root:not(.slyxup-light) {
27
+ --slx-accent: #8484f2;
28
+ --slx-accent-hover: #9696f5;
29
+ --slx-accent-soft: rgba(132, 132, 242, 0.14);
30
+ --slx-bg: #191920;
31
+ --slx-bg-page: #121218;
32
+ --slx-ink: #f2f2f5;
33
+ --slx-muted: #9a9aa6;
34
+ --slx-border: #2c2c36;
35
+ --slx-danger: #f0737d;
36
+ }
37
+ }
38
+
39
+ @keyframes slx-shake {
40
+ 10%, 90% { transform: translateX(-1px); }
41
+ 20%, 80% { transform: translateX(2px); }
42
+ 30%, 50%, 70% { transform: translateX(-4px); }
43
+ 40%, 60% { transform: translateX(4px); }
44
+ }
45
+ @keyframes slx-spin { to { transform: rotate(360deg); } }
46
+ @media (prefers-reduced-motion: reduce) {
47
+ .slyxup-root * { animation: none !important; transition: none !important; }
48
+ }
49
+
50
+ /* ── Card ── */
51
+ .slx-card {
52
+ width: 100%;
53
+ max-width: 380px;
54
+ background: var(--slx-bg);
55
+ border: 1px solid var(--slx-border);
56
+ border-radius: calc(var(--slx-radius) + 4px);
57
+ padding: 32px;
58
+ box-shadow: 0 1px 2px rgba(10,10,20,.04), 0 8px 24px rgba(10,10,20,.06);
59
+ box-sizing: border-box;
60
+ }
61
+ .slx-card-error { animation: slx-shake .45s cubic-bezier(.36,.07,.19,.97) both; }
62
+
63
+ /* ── Header / keyhole mark ── */
64
+ .slx-mark {
65
+ width: 44px; height: 44px; border-radius: 12px;
66
+ display: flex; align-items: center; justify-content: center;
67
+ background: linear-gradient(135deg, var(--slx-accent), #9a6cf0);
68
+ margin-bottom: 18px;
69
+ }
70
+ .slx-mark svg { display: block; }
71
+ .slx-title { font-size: 19px; font-weight: 650; letter-spacing: -0.02em; margin: 0 0 6px; }
72
+ .slx-subtitle { font-size: 13.5px; color: var(--slx-muted); margin: 0 0 22px; line-height: 1.5; }
73
+
74
+ /* ── Fields ── */
75
+ .slx-field { margin-bottom: 14px; }
76
+ .slx-label { display: block; font-size: 12.5px; font-weight: 550; margin-bottom: 6px; letter-spacing: 0.01em; }
77
+ .slx-input {
78
+ width: 100%; box-sizing: border-box;
79
+ font: inherit; font-size: 14px; color: var(--slx-ink);
80
+ background: transparent;
81
+ border: 1px solid var(--slx-border);
82
+ border-radius: var(--slx-radius);
83
+ padding: 10px 12px;
84
+ outline: none;
85
+ transition: border-color .15s, box-shadow .15s;
86
+ }
87
+ .slx-input::placeholder { color: var(--slx-muted); opacity: .75; }
88
+ .slx-input:focus-visible {
89
+ border-color: var(--slx-accent);
90
+ box-shadow: 0 0 0 3px var(--slx-accent-soft);
91
+ }
92
+ .slx-hint { font-size: 12px; color: var(--slx-muted); margin-top: 5px; }
93
+ .slx-error-text {
94
+ font-size: 13px; color: var(--slx-danger);
95
+ background: color-mix(in srgb, var(--slx-danger) 9%, transparent);
96
+ border-radius: 8px; padding: 9px 11px; margin: 0 0 14px; line-height: 1.4;
97
+ }
98
+
99
+ /* ── Button ── */
100
+ .slx-btn {
101
+ width: 100%; box-sizing: border-box;
102
+ font: inherit; font-size: 14px; font-weight: 600; letter-spacing: 0.01em;
103
+ color: #fff; background: var(--slx-accent);
104
+ border: none; border-radius: var(--slx-radius);
105
+ padding: 11px 14px; cursor: pointer;
106
+ display: inline-flex; align-items: center; justify-content: center; gap: 8px;
107
+ transition: background .15s, transform .06s;
108
+ }
109
+ .slx-btn:hover { background: var(--slx-accent-hover); }
110
+ .slx-btn:active { transform: scale(.985); }
111
+ .slx-btn:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--slx-accent-soft), 0 0 0 1px var(--slx-accent); }
112
+ .slx-btn[disabled] { opacity: .6; cursor: not-allowed; }
113
+ .slx-spinner {
114
+ width: 15px; height: 15px; flex: none;
115
+ border: 2px solid rgba(255,255,255,.35); border-top-color: #fff;
116
+ border-radius: 50%; animation: slx-spin .7s linear infinite;
117
+ }
118
+
119
+ /* ── Social ── */
120
+ .slx-social { display: grid; gap: 10px; margin-bottom: 16px; }
121
+ .slx-social-btn {
122
+ width: 100%; box-sizing: border-box;
123
+ font: inherit; font-size: 13.5px; font-weight: 550;
124
+ color: var(--slx-ink); background: var(--slx-bg);
125
+ border: 1px solid var(--slx-border); border-radius: var(--slx-radius);
126
+ padding: 10px 14px; cursor: pointer;
127
+ display: inline-flex; align-items: center; justify-content: center; gap: 10px;
128
+ transition: background .15s, border-color .15s;
129
+ }
130
+ .slx-social-btn:hover { background: color-mix(in srgb, var(--slx-ink) 4%, var(--slx-bg)); }
131
+ .slx-social-btn:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--slx-accent-soft); }
132
+
133
+ /* ── Divider & footer ── */
134
+ .slx-divider {
135
+ display: flex; align-items: center; gap: 12px;
136
+ color: var(--slx-muted); font-size: 12px;
137
+ margin: 18px 0;
138
+ }
139
+ .slx-divider::before, .slx-divider::after {
140
+ content: ""; height: 1px; flex: 1; background: var(--slx-border);
141
+ }
142
+ .slx-footer { font-size: 13px; color: var(--slx-muted); margin-top: 20px; text-align: center; }
143
+ .slx-link { color: var(--slx-accent); font-weight: 550; text-decoration: none; cursor: pointer; }
144
+ .slx-link:hover { text-decoration: underline; }
145
+ .slx-link:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--slx-accent-soft); border-radius: 4px; }
146
+
147
+ /* ── Success state ── */
148
+ .slx-success-icon {
149
+ width: 46px; height: 46px; border-radius: 50%;
150
+ display: flex; align-items: center; justify-content: center;
151
+ background: color-mix(in srgb, #34a853 12%, transparent);
152
+ color: #34a853; margin: 4px auto 18px;
153
+ }
154
+
155
+ /* ── User button ── */
156
+ .slx-userbtn-wrap { position: relative; display: inline-block; }
157
+ .slx-userbtn-avatar {
158
+ width: 36px; height: 36px; border-radius: 50%;
159
+ border: 1px solid var(--slx-border); cursor: pointer;
160
+ display: flex; align-items: center; justify-content: center;
161
+ font-size: 14px; font-weight: 650; color: #fff;
162
+ background: linear-gradient(135deg, var(--slx-accent), #9a6cf0);
163
+ padding: 0; overflow: hidden;
164
+ }
165
+ .slx-userbtn-avatar img { width: 100%; height: 100%; object-fit: cover; }
166
+ .slx-userbtn-avatar:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--slx-accent-soft); }
167
+ .slx-menu {
168
+ position: absolute; right: 0; top: calc(100% + 8px);
169
+ min-width: 230px;
170
+ background: var(--slx-bg);
171
+ border: 1px solid var(--slx-border);
172
+ border-radius: var(--slx-radius);
173
+ box-shadow: 0 8px 30px rgba(10,10,20,.14);
174
+ overflow: hidden; z-index: 1000;
175
+ }
176
+ .slx-menu-header { padding: 14px 16px; border-bottom: 1px solid var(--slx-border); }
177
+ .slx-menu-name { font-size: 14px; font-weight: 600; margin: 0 0 2px; }
178
+ .slx-menu-email { font-size: 12.5px; color: var(--slx-muted); margin: 0; word-break: break-all; }
179
+ .slx-menu-item {
180
+ display: block; width: 100%; text-align: left; box-sizing: border-box;
181
+ font: inherit; font-size: 13.5px; color: var(--slx-ink);
182
+ background: none; border: none; padding: 10px 16px; cursor: pointer;
183
+ }
184
+ .slx-menu-item:hover { background: color-mix(in srgb, var(--slx-ink) 5%, transparent); }
185
+ .slx-menu-item:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--slx-accent-soft); }
186
+ .slx-menu-item-danger { color: var(--slx-danger); }
187
+ `;
188
+
189
+ let injected = false;
190
+
191
+ /** Inject the SlyxUp stylesheet once per document. */
192
+ export function injectStyles(): void {
193
+ if (injected || typeof document === 'undefined') return;
194
+ const style = document.createElement('style');
195
+ style.setAttribute('data-slyxup', 'styles');
196
+ style.textContent = CSS;
197
+ document.head.appendChild(style);
198
+ injected = true;
199
+ }