@slyxup/ui 0.2.1 → 0.2.3

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 (57) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +15 -0
  3. package/README.md +67 -3
  4. package/dist/components/BillingPortal/BillingPortal.d.ts.map +1 -1
  5. package/dist/components/BillingPortal/BillingPortal.js +2 -0
  6. package/dist/components/BillingPortal/BillingPortal.js.map +1 -1
  7. package/dist/components/EmailVerification/EmailVerification.d.ts.map +1 -1
  8. package/dist/components/EmailVerification/EmailVerification.js +2 -0
  9. package/dist/components/EmailVerification/EmailVerification.js.map +1 -1
  10. package/dist/components/ForgotPassword/ForgotPassword.d.ts.map +1 -1
  11. package/dist/components/ForgotPassword/ForgotPassword.js +2 -0
  12. package/dist/components/ForgotPassword/ForgotPassword.js.map +1 -1
  13. package/dist/components/PricingTable/PricingTable.d.ts.map +1 -1
  14. package/dist/components/PricingTable/PricingTable.js +2 -0
  15. package/dist/components/PricingTable/PricingTable.js.map +1 -1
  16. package/dist/components/ResetPassword/ResetPassword.d.ts.map +1 -1
  17. package/dist/components/ResetPassword/ResetPassword.js +2 -0
  18. package/dist/components/ResetPassword/ResetPassword.js.map +1 -1
  19. package/dist/components/SignIn/SignIn.d.ts +3 -1
  20. package/dist/components/SignIn/SignIn.d.ts.map +1 -1
  21. package/dist/components/SignIn/SignIn.js +5 -11
  22. package/dist/components/SignIn/SignIn.js.map +1 -1
  23. package/dist/components/SignUp/SignUp.d.ts.map +1 -1
  24. package/dist/components/SignUp/SignUp.js +4 -10
  25. package/dist/components/SignUp/SignUp.js.map +1 -1
  26. package/dist/components/SocialButtons/SocialButtons.d.ts +1 -1
  27. package/dist/components/SocialButtons/SocialButtons.d.ts.map +1 -1
  28. package/dist/components/SocialButtons/SocialButtons.js +7 -2
  29. package/dist/components/SocialButtons/SocialButtons.js.map +1 -1
  30. package/dist/components/UserButton/UserButton.d.ts.map +1 -1
  31. package/dist/components/UserButton/UserButton.js +2 -0
  32. package/dist/components/UserButton/UserButton.js.map +1 -1
  33. package/dist/components/UserProfile/UserProfile.d.ts +8 -2
  34. package/dist/components/UserProfile/UserProfile.d.ts.map +1 -1
  35. package/dist/components/UserProfile/UserProfile.js +202 -6
  36. package/dist/components/UserProfile/UserProfile.js.map +1 -1
  37. package/dist/index.d.ts +1 -1
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +1 -1
  40. package/dist/index.js.map +1 -1
  41. package/dist/styles.d.ts +6 -3
  42. package/dist/styles.d.ts.map +1 -1
  43. package/dist/styles.js +194 -73
  44. package/dist/styles.js.map +1 -1
  45. package/package.json +2 -2
  46. package/src/components/BillingPortal/BillingPortal.tsx +2 -0
  47. package/src/components/EmailVerification/EmailVerification.tsx +2 -0
  48. package/src/components/ForgotPassword/ForgotPassword.tsx +2 -0
  49. package/src/components/PricingTable/PricingTable.tsx +2 -0
  50. package/src/components/ResetPassword/ResetPassword.tsx +2 -0
  51. package/src/components/SignIn/SignIn.tsx +22 -17
  52. package/src/components/SignUp/SignUp.tsx +5 -13
  53. package/src/components/SocialButtons/SocialButtons.tsx +10 -3
  54. package/src/components/UserButton/UserButton.tsx +2 -0
  55. package/src/components/UserProfile/UserProfile.tsx +538 -56
  56. package/src/index.ts +4 -1
  57. package/src/styles.ts +194 -73
package/dist/styles.js CHANGED
@@ -1,41 +1,69 @@
1
1
  /**
2
2
  * SlyxUp UI — design tokens + stylesheet.
3
- * Injected once via <SlyxUpStyles />. Theme with CSS variables on :root or .slyxup-scope.
3
+ * Injected automatically by every SlyxUp component. Theme by overriding
4
+ * CSS variables on :root or .slyxup-scope.
4
5
  */
5
6
  export const FONT_LINK = `<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=DM+Sans:opsz,wght@9..40,300;9..40,400;9..40,500;9..40,600;9..40,700;9..40,800&family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet">`;
6
7
  export const CSS = `
7
8
  @import url('https://fonts.googleapis.com/css2?family=DM+Sans:opsz,wght@9..40,300;9..40,400;9..40,500;9..40,600;9..40,700;9..40,800&family=Space+Grotesk:wght@400;500;600;700&display=swap');
8
9
 
9
10
  .slyxup-root {
11
+ /* ── Tokens ── */
10
12
  --slx-accent: #5b5bd6;
11
13
  --slx-accent-hover: #4c4cc4;
12
- --slx-accent-soft: rgba(91, 91, 214, 0.14);
14
+ --slx-accent-soft: rgba(91, 91, 214, 0.12);
13
15
  --slx-bg: #ffffff;
16
+ --slx-bg-subtle: #f7f7fb;
14
17
  --slx-bg-page: #e9eaf6;
15
18
  --slx-ink: #16161d;
19
+ --slx-ink-strong: #0c0c12;
16
20
  --slx-muted: #6f6f7b;
17
- --slx-border: #d8d8e8;
21
+ --slx-border: #e3e3ee;
22
+ --slx-border-strong: #d3d3e2;
18
23
  --slx-danger: #d64550;
19
- --slx-radius: 12px;
24
+ --slx-success: #1f9d55;
25
+ --slx-radius-sm: 8px;
26
+ --slx-radius: 10px;
27
+ --slx-radius-lg: 14px;
20
28
  --slx-font: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
21
29
  --slx-display: "Space Grotesk", "DM Sans", sans-serif;
22
30
  --slx-mono: ui-monospace, SFMono-Regular, Menlo, monospace;
31
+ --slx-shadow-card:
32
+ 0 1px 2px rgba(18,18,28,.05),
33
+ 0 8px 24px -6px rgba(18,18,28,.09),
34
+ 0 24px 64px -16px rgba(18,18,28,.13);
35
+ --slx-shadow-pop:
36
+ 0 2px 6px rgba(18,18,28,.08),
37
+ 0 16px 48px -12px rgba(18,18,28,.18);
23
38
 
24
39
  font-family: var(--slx-font);
25
40
  color: var(--slx-ink);
26
41
  -webkit-font-smoothing: antialiased;
42
+ text-rendering: optimizeLegibility;
43
+ font-feature-settings: "cv11", "ss01";
27
44
  }
28
45
  @media (prefers-color-scheme: dark) {
29
46
  .slyxup-root:not(.slyxup-light) {
30
47
  --slx-accent: #8484f2;
31
48
  --slx-accent-hover: #9696f5;
32
- --slx-accent-soft: rgba(132, 132, 242, 0.14);
33
- --slx-bg: #191920;
34
- --slx-bg-page: #121218;
35
- --slx-ink: #f2f2f5;
49
+ --slx-accent-soft: rgba(132, 132, 242, 0.16);
50
+ --slx-bg: #17171f;
51
+ --slx-bg-subtle: #1e1e28;
52
+ --slx-bg-page: #101016;
53
+ --slx-ink: #f0f0f4;
54
+ --slx-ink-strong: #ffffff;
36
55
  --slx-muted: #9a9aa6;
37
- --slx-border: #2c2c36;
56
+ --slx-border: #292935;
57
+ --slx-border-strong: #343442;
38
58
  --slx-danger: #f0737d;
59
+ --slx-success: #4ade80;
60
+ --slx-shadow-card:
61
+ 0 1px 2px rgba(0,0,0,.25),
62
+ 0 12px 32px -8px rgba(0,0,0,.5),
63
+ 0 32px 72px -20px rgba(0,0,0,.55);
64
+ --slx-shadow-pop:
65
+ 0 4px 12px rgba(0,0,0,.4),
66
+ 0 24px 56px -12px rgba(0,0,0,.6);
39
67
  }
40
68
  }
41
69
 
@@ -46,6 +74,15 @@ export const CSS = `
46
74
  40%, 60% { transform: translateX(4px); }
47
75
  }
48
76
  @keyframes slx-spin { to { transform: rotate(360deg); } }
77
+ @keyframes slx-rise {
78
+ from { opacity: 0; transform: translateY(8px) scale(.985); }
79
+ to { opacity: 1; transform: translateY(0) scale(1); }
80
+ }
81
+ @keyframes slx-pop {
82
+ 0% { transform: scale(.5); opacity: 0; }
83
+ 60% { transform: scale(1.08); opacity: 1; }
84
+ 100% { transform: scale(1); }
85
+ }
49
86
  @media (prefers-reduced-motion: reduce) {
50
87
  .slyxup-root * { animation: none !important; transition: none !important; }
51
88
  }
@@ -53,101 +90,155 @@ export const CSS = `
53
90
  /* ── Card ── */
54
91
  .slx-card {
55
92
  width: 100%;
56
- max-width: 380px;
93
+ max-width: 400px;
57
94
  background: var(--slx-bg);
58
95
  border: 1px solid var(--slx-border);
59
- border-radius: calc(var(--slx-radius) + 4px);
60
- padding: 32px;
61
- box-shadow: 0 4px 12px rgba(16,16,29,.08), 0 16px 40px rgba(16,16,29,.12), 0 0 0 1px rgba(16,16,29,.04);
96
+ border-radius: var(--slx-radius-lg);
97
+ padding: 34px 34px 30px;
98
+ box-shadow: var(--slx-shadow-card);
62
99
  box-sizing: border-box;
100
+ animation: slx-rise .38s cubic-bezier(.22,.9,.32,1) both;
63
101
  }
64
- .slyxup-root .slx-card { background: #ffffff; }
102
+ .slyxup-root:not(.slyxup-light) .slx-card,
103
+ .slx-card { color: var(--slx-ink); }
65
104
  .slx-card-error { animation: slx-shake .45s cubic-bezier(.36,.07,.19,.97) both; }
66
105
 
67
106
  /* ── Header / keyhole mark ── */
68
107
  .slx-mark {
69
- width: 44px; height: 44px; border-radius: 12px;
108
+ width: 46px; height: 46px; border-radius: 13px;
70
109
  display: flex; align-items: center; justify-content: center;
71
- background: linear-gradient(135deg, var(--slx-accent), #9a6cf0);
72
- margin-bottom: 18px;
110
+ background: linear-gradient(140deg, var(--slx-accent) 0%, #8b5cf6 100%);
111
+ box-shadow:
112
+ inset 0 1px 0 rgba(255,255,255,.28),
113
+ 0 6px 16px -6px rgba(91,91,214,.55);
114
+ margin-bottom: 20px;
115
+ }
116
+ .slx-mark svg { display: block; filter: drop-shadow(0 1px 1px rgba(0,0,0,.18)); }
117
+ .slx-title {
118
+ font-family: var(--slx-display);
119
+ font-size: 21px; font-weight: 650; letter-spacing: -0.022em;
120
+ color: var(--slx-ink-strong);
121
+ margin: 0 0 6px;
73
122
  }
74
- .slx-mark svg { display: block; }
75
- .slx-title { font-family: var(--slx-display); font-size: 20px; font-weight: 650; letter-spacing: -0.02em; margin: 0 0 6px; }
76
- .slx-subtitle { font-size: 13.5px; color: var(--slx-muted); margin: 0 0 22px; line-height: 1.5; }
123
+ .slx-subtitle { font-size: 13.5px; color: var(--slx-muted); margin: 0 0 24px; line-height: 1.55; }
77
124
 
78
125
  /* ── Fields ── */
79
- .slx-field { margin-bottom: 14px; }
80
- .slx-label { display: block; font-size: 12.5px; font-weight: 550; margin-bottom: 6px; letter-spacing: 0.01em; }
126
+ .slx-field { margin-bottom: 15px; }
127
+ .slx-row {
128
+ display: flex; align-items: baseline; justify-content: space-between;
129
+ margin-bottom: 6px;
130
+ }
131
+ .slx-label {
132
+ display: inline-block;
133
+ font-size: 12.5px; font-weight: 550; letter-spacing: 0.01em;
134
+ color: var(--slx-ink);
135
+ }
81
136
  .slx-input {
82
137
  width: 100%; box-sizing: border-box;
83
- font: inherit; font-size: 14px; color: var(--slx-ink);
84
- background: transparent;
138
+ font: inherit; font-size: 14px; line-height: 1.4; color: var(--slx-ink);
139
+ background: var(--slx-bg-subtle);
85
140
  border: 1px solid var(--slx-border);
86
141
  border-radius: var(--slx-radius);
87
- padding: 10px 12px;
142
+ padding: 10px 13px;
88
143
  outline: none;
89
- transition: border-color .15s, box-shadow .15s;
144
+ transition: border-color .15s, box-shadow .15s, background .15s;
90
145
  }
91
- .slx-input::placeholder { color: var(--slx-muted); opacity: .75; }
146
+ .slx-input::placeholder { color: var(--slx-muted); opacity: .7; }
147
+ .slx-input:hover { border-color: var(--slx-border-strong); }
92
148
  .slx-input:focus-visible {
149
+ background: var(--slx-bg);
93
150
  border-color: var(--slx-accent);
94
- box-shadow: 0 0 0 3px var(--slx-accent-soft);
151
+ box-shadow: 0 0 0 3.5px var(--slx-accent-soft);
95
152
  }
96
153
  .slx-hint { font-size: 12px; color: var(--slx-muted); margin-top: 5px; }
97
154
  .slx-error-text {
98
155
  font-size: 13px; color: var(--slx-danger);
99
- background: color-mix(in srgb, var(--slx-danger) 9%, transparent);
100
- border-radius: 8px; padding: 9px 11px; margin: 0 0 14px; line-height: 1.4;
156
+ background: color-mix(in srgb, var(--slx-danger) 8%, transparent);
157
+ border: 1px solid color-mix(in srgb, var(--slx-danger) 26%, transparent);
158
+ border-left: 3px solid var(--slx-danger);
159
+ border-radius: var(--slx-radius-sm);
160
+ padding: 10px 12px; margin: 0 0 16px; line-height: 1.45;
161
+ }
162
+ .slx-setup-note {
163
+ font-size: 12px; line-height: 1.45;
164
+ color: #7a5c00;
165
+ background: #fff7dc;
166
+ border: 1px solid #f3e3a0;
167
+ border-radius: var(--slx-radius-sm);
168
+ padding: 9px 11px; margin: 0 0 16px;
169
+ }
170
+ .slyxup-root:not(.slyxup-light) .slx-setup-note {
171
+ color: #f5d878; background: rgba(250,204,21,.07); border-color: rgba(250,204,21,.25);
101
172
  }
173
+ .slx-setup-note code {
174
+ font-family: var(--slx-mono); font-size: 11px;
175
+ background: rgba(0,0,0,.05); border-radius: 4px; padding: 1px 4px;
176
+ }
177
+ .slyxup-root:not(.slyxup-light) .slx-setup-note code { background: rgba(255,255,255,.08); }
102
178
 
103
- /* ── Button ── */
179
+ /* ── Primary button ── */
104
180
  .slx-btn {
105
181
  width: 100%; box-sizing: border-box;
106
182
  font-family: var(--slx-font); font-size: 14px; font-weight: 600; letter-spacing: 0.01em;
107
- color: #fff; background: #0a0a0f;
183
+ color: #fff; background: linear-gradient(180deg, #23232e 0%, #0a0a0f 100%);
108
184
  border: 1px solid #0a0a0f; border-radius: var(--slx-radius);
109
185
  padding: 11px 14px; cursor: pointer;
186
+ box-shadow: 0 1px 2px rgba(10,10,15,.35), inset 0 1px 0 rgba(255,255,255,.08);
110
187
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
111
- transition: background .15s, transform .06s, border-color .15s;
188
+ transition: filter .15s, transform .06s, box-shadow .15s;
112
189
  }
113
- .slx-btn:hover { background: #1a1a23; border-color: #1a1a23; }
114
- .slx-btn:active { transform: scale(.985); background: #000; }
115
- .slx-btn:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(10,10,15,.14), 0 0 0 1px #0a0a0f; }
116
- .slx-btn[disabled] { opacity: .6; cursor: not-allowed; }
190
+ .slx-btn:hover { filter: brightness(1.22); }
191
+ .slx-btn:active { transform: scale(.985); filter: brightness(.92); }
192
+ .slx-btn:focus-visible { outline: none; box-shadow: 0 0 0 3.5px rgba(10,10,15,.16), 0 0 0 1.5px #0a0a0f; }
193
+ .slx-btn[disabled] { opacity: .55; cursor: not-allowed; }
117
194
  @media (prefers-color-scheme: dark) {
118
- .slyxup-root:not(.slyxup-light) .slx-btn { background: #f2f2f5; color: #0a0a0f; border-color: #f2f2f5; }
119
- .slyxup-root:not(.slyxup-light) .slx-btn:hover { background: #e6e6eb; border-color: #e6e6eb; }
195
+ .slyxup-root:not(.slyxup-light) .slx-btn {
196
+ background: linear-gradient(180deg, #ffffff 0%, #ececf1 100%);
197
+ color: #0a0a0f; border-color: #ececf1;
198
+ box-shadow: 0 1px 2px rgba(0,0,0,.5), inset 0 1px 0 rgba(255,255,255,.9);
199
+ }
200
+ .slyxup-root:not(.slyxup-light) .slx-btn:hover { filter: brightness(.94); }
120
201
  }
121
202
  .slx-spinner {
122
203
  width: 15px; height: 15px; flex: none;
123
204
  border: 2px solid rgba(255,255,255,.35); border-top-color: #fff;
124
205
  border-radius: 50%; animation: slx-spin .7s linear infinite;
125
206
  }
207
+ .slyxup-root:not(.slyxup-light) .slx-spinner { border-color: rgba(10,10,15,.25); border-top-color: #0a0a0f; }
126
208
 
127
209
  /* ── Social ── */
128
- .slx-social { display: grid; gap: 10px; margin-bottom: 16px; }
210
+ .slx-social { display: grid; gap: 9px; margin-bottom: 4px; }
129
211
  .slx-social-btn {
130
212
  width: 100%; box-sizing: border-box;
131
213
  font: inherit; font-size: 13.5px; font-weight: 550;
132
214
  color: var(--slx-ink); background: var(--slx-bg);
133
- border: 1px solid var(--slx-border); border-radius: var(--slx-radius);
215
+ border: 1px solid var(--slx-border-strong); border-radius: var(--slx-radius);
134
216
  padding: 10px 14px; cursor: pointer;
135
217
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
136
- transition: background .15s, border-color .15s;
218
+ transition: background .15s, border-color .15s, transform .06s, box-shadow .15s;
137
219
  }
138
- .slx-social-btn:hover { background: color-mix(in srgb, var(--slx-ink) 4%, var(--slx-bg)); }
139
- .slx-social-btn:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--slx-accent-soft); }
220
+ .slx-social-btn:hover {
221
+ background: var(--slx-bg-subtle);
222
+ border-color: var(--slx-border-strong);
223
+ box-shadow: 0 2px 8px -2px rgba(18,18,28,.12);
224
+ }
225
+ .slx-social-btn:active { transform: scale(.985); }
226
+ .slx-social-btn:focus-visible { outline: none; box-shadow: 0 0 0 3.5px var(--slx-accent-soft); }
227
+ .slx-social-btn svg { flex: none; }
140
228
 
141
229
  /* ── Divider & footer ── */
142
230
  .slx-divider {
143
- display: flex; align-items: center; gap: 12px;
144
- color: var(--slx-muted); font-size: 12px;
231
+ display: flex; align-items: center; gap: 14px;
232
+ color: var(--slx-muted); font-size: 11.5px;
233
+ letter-spacing: .04em; text-transform: uppercase;
145
234
  margin: 18px 0;
146
235
  }
147
236
  .slx-divider::before, .slx-divider::after {
148
- content: ""; height: 1px; flex: 1; background: var(--slx-border);
237
+ content: ""; height: 1px; flex: 1;
238
+ background: linear-gradient(90deg, transparent, var(--slx-border-strong));
149
239
  }
150
- .slx-footer { font-size: 13px; color: var(--slx-muted); margin-top: 20px; text-align: center; }
240
+ .slx-divider::after { background: linear-gradient(90deg, var(--slx-border-strong), transparent); }
241
+ .slx-footer { font-size: 13px; color: var(--slx-muted); margin-top: 22px; text-align: center; }
151
242
  .slx-link {
152
243
  color: var(--slx-accent); font-weight: 600; text-decoration: none; cursor: pointer;
153
244
  background: none; border: none; font: inherit; font-size: inherit;
@@ -155,13 +246,16 @@ export const CSS = `
155
246
  }
156
247
  .slx-link:hover { text-decoration: underline; color: var(--slx-accent-hover); }
157
248
  .slx-link:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--slx-accent-soft); border-radius: 4px; }
249
+ .slx-forgot { font-size: 12px; }
158
250
 
159
251
  /* ── Success state ── */
160
252
  .slx-success-icon {
161
- width: 46px; height: 46px; border-radius: 50%;
253
+ width: 48px; height: 48px; border-radius: 50%;
162
254
  display: flex; align-items: center; justify-content: center;
163
- background: color-mix(in srgb, #34a853 12%, transparent);
164
- color: #34a853; margin: 4px auto 18px;
255
+ background: color-mix(in srgb, var(--slx-success) 12%, transparent);
256
+ color: var(--slx-success); margin: 4px auto 18px;
257
+ animation: slx-pop .45s cubic-bezier(.22,.9,.32,1) both;
258
+ box-shadow: 0 0 0 6px color-mix(in srgb, var(--slx-success) 6%, transparent);
165
259
  }
166
260
 
167
261
  /* ── User button ── */
@@ -171,49 +265,76 @@ export const CSS = `
171
265
  border: 1px solid var(--slx-border); cursor: pointer;
172
266
  display: flex; align-items: center; justify-content: center;
173
267
  font-size: 14px; font-weight: 650; color: #fff;
174
- background: linear-gradient(135deg, var(--slx-accent), #9a6cf0);
268
+ background: linear-gradient(135deg, var(--slx-accent), #8b5cf6);
175
269
  padding: 0; overflow: hidden;
270
+ transition: box-shadow .15s, transform .06s;
176
271
  }
272
+ .slx-userbtn-avatar:hover { box-shadow: 0 0 0 3px var(--slx-accent-soft); }
273
+ .slx-userbtn-avatar:active { transform: scale(.96); }
177
274
  .slx-userbtn-avatar img { width: 100%; height: 100%; object-fit: cover; }
178
- .slx-userbtn-avatar:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--slx-accent-soft); }
275
+ .slx-userbtn-avatar:focus-visible { outline: none; box-shadow: 0 0 0 3.5px var(--slx-accent-soft); }
179
276
  .slx-menu {
180
277
  position: absolute; right: 0; top: calc(100% + 8px);
181
- min-width: 230px;
278
+ min-width: 240px;
182
279
  background: var(--slx-bg);
183
280
  border: 1px solid var(--slx-border);
184
- border-radius: var(--slx-radius);
185
- box-shadow: 0 8px 30px rgba(10,10,20,.14);
281
+ border-radius: var(--slx-radius-lg);
282
+ box-shadow: var(--slx-shadow-pop);
186
283
  overflow: hidden; z-index: 1000;
284
+ animation: slx-rise .22s cubic-bezier(.22,.9,.32,1) both;
187
285
  }
188
- .slx-menu-header { padding: 14px 16px; border-bottom: 1px solid var(--slx-border); }
189
- .slx-menu-name { font-size: 14px; font-weight: 600; margin: 0 0 2px; }
286
+ .slx-menu-header { padding: 14px 16px; border-bottom: 1px solid var(--slx-border); background: var(--slx-bg-subtle); }
287
+ .slx-menu-name { font-size: 14px; font-weight: 600; margin: 0 0 2px; color: var(--slx-ink-strong); }
190
288
  .slx-menu-email { font-size: 12.5px; color: var(--slx-muted); margin: 0; word-break: break-all; }
191
289
  .slx-menu-item {
192
290
  display: block; width: 100%; text-align: left; box-sizing: border-box;
193
291
  font: inherit; font-size: 13.5px; color: var(--slx-ink);
194
292
  background: none; border: none; padding: 10px 16px; cursor: pointer;
293
+ transition: background .12s;
195
294
  }
196
- .slx-menu-item:hover { background: color-mix(in srgb, var(--slx-ink) 5%, transparent); }
295
+ .slx-menu-item:hover { background: color-mix(in srgb, var(--slx-ink) 4%, transparent); }
197
296
  .slx-menu-item:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--slx-accent-soft); }
198
297
  .slx-menu-item-danger { color: var(--slx-danger); }
298
+ .slx-menu-item-danger:hover { background: color-mix(in srgb, var(--slx-danger) 7%, transparent); }
299
+
300
+ /* ── Badge (verified etc.) ── */
301
+ .slx-badge {
302
+ display: inline-flex; align-items: center; gap: 5px;
303
+ font-size: 11px; font-weight: 600; letter-spacing: .02em;
304
+ padding: 2.5px 9px; border-radius: 999px;
305
+ color: var(--slx-success);
306
+ background: color-mix(in srgb, var(--slx-success) 9%, transparent);
307
+ border: 1px solid color-mix(in srgb, var(--slx-success) 30%, transparent);
308
+ }
199
309
  `;
200
310
  let injected = false;
201
- /** Inject the SlyxUp stylesheet + DM Sans font once per document. */
311
+ /** Inject the SlyxUp stylesheet + fonts once per document. Idempotent + SSR-safe. */
202
312
  export function injectStyles() {
203
- if (injected || typeof document === 'undefined')
313
+ if (typeof document === 'undefined')
204
314
  return;
205
- // DM Sans font link
206
- if (!document.querySelector('link[href*="DM+Sans"]')) {
207
- const fontLink = document.createElement('link');
208
- fontLink.rel = 'stylesheet';
209
- fontLink.href =
210
- 'https://fonts.googleapis.com/css2?family=DM+Sans:opsz,wght@9..40,300;9..40,400;9..40,500;9..40,600;9..40,700;9..40,800&family=Space+Grotesk:wght@400;500;600;700&display=swap';
211
- document.head.appendChild(fontLink);
315
+ // Design tokens are scoped to .slyxup-root — apply to <html> so the
316
+ // components work in ANY host app without a wrapper element.
317
+ document.documentElement.classList.add('slyxup-root');
318
+ if (!document.querySelector('style[data-slyxup="styles"]')) {
319
+ if (!document.querySelector('link[href*="DM+Sans"]')) {
320
+ const fontLink = document.createElement('link');
321
+ fontLink.rel = 'stylesheet';
322
+ fontLink.href =
323
+ 'https://fonts.googleapis.com/css2?family=DM+Sans:opsz,wght@9..40,300;9..40,400;9..40,500;9..40,600;9..40,700;9..40,800&family=Space+Grotesk:wght@400;500;600;700&display=swap';
324
+ document.head.appendChild(fontLink);
325
+ }
326
+ const style = document.createElement('style');
327
+ style.setAttribute('data-slyxup', 'styles');
328
+ style.textContent = CSS;
329
+ document.head.appendChild(style);
330
+ injected = true;
212
331
  }
213
- const style = document.createElement('style');
214
- style.setAttribute('data-slyxup', 'styles');
215
- style.textContent = CSS;
216
- document.head.appendChild(style);
217
- injected = true;
332
+ else {
333
+ injected = true;
334
+ }
335
+ }
336
+ /** Test hook — whether styles are present. */
337
+ export function stylesInjected() {
338
+ return injected;
218
339
  }
219
340
  //# sourceMappingURL=styles.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"styles.js","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,CAAC,MAAM,SAAS,GAAG,6UAA6U,CAAC;AAEvW,MAAM,CAAC,MAAM,GAAG,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiMlB,CAAC;AAEF,IAAI,QAAQ,GAAG,KAAK,CAAC;AAErB,qEAAqE;AACrE,MAAM,UAAU,YAAY;IAC1B,IAAI,QAAQ,IAAI,OAAO,QAAQ,KAAK,WAAW;QAAE,OAAO;IACxD,oBAAoB;IACpB,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,uBAAuB,CAAC,EAAE,CAAC;QACrD,MAAM,QAAQ,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;QAChD,QAAQ,CAAC,GAAG,GAAG,YAAY,CAAC;QAC5B,QAAQ,CAAC,IAAI;YACX,+KAA+K,CAAC;QAClL,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;IACtC,CAAC;IACD,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IAC9C,KAAK,CAAC,YAAY,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;IAC5C,KAAK,CAAC,WAAW,GAAG,GAAG,CAAC;IACxB,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IACjC,QAAQ,GAAG,IAAI,CAAC;AAClB,CAAC"}
1
+ {"version":3,"file":"styles.js","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,CAAC,MAAM,SAAS,GAAG,6UAA6U,CAAC;AAEvW,MAAM,CAAC,MAAM,GAAG,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8SlB,CAAC;AAEF,IAAI,QAAQ,GAAG,KAAK,CAAC;AAErB,qFAAqF;AACrF,MAAM,UAAU,YAAY;IAC1B,IAAI,OAAO,QAAQ,KAAK,WAAW;QAAE,OAAO;IAC5C,oEAAoE;IACpE,6DAA6D;IAC7D,QAAQ,CAAC,eAAe,CAAC,SAAS,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IACtD,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,6BAA6B,CAAC,EAAE,CAAC;QAC3D,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,uBAAuB,CAAC,EAAE,CAAC;YACrD,MAAM,QAAQ,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;YAChD,QAAQ,CAAC,GAAG,GAAG,YAAY,CAAC;YAC5B,QAAQ,CAAC,IAAI;gBACX,+KAA+K,CAAC;YAClL,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;QACtC,CAAC;QACD,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QAC9C,KAAK,CAAC,YAAY,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;QAC5C,KAAK,CAAC,WAAW,GAAG,GAAG,CAAC;QACxB,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QACjC,QAAQ,GAAG,IAAI,CAAC;IAClB,CAAC;SAAM,CAAC;QACN,QAAQ,GAAG,IAAI,CAAC;IAClB,CAAC;AACH,CAAC;AAED,8CAA8C;AAC9C,MAAM,UAAU,cAAc;IAC5B,OAAO,QAAQ,CAAC;AAClB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slyxup/ui",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "@slyxup/ui — SlyxUp SDK",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -24,7 +24,7 @@
24
24
  },
25
25
  "dependencies": {
26
26
  "@slyxup/core": "0.2.1",
27
- "@slyxup/react": "0.2.1"
27
+ "@slyxup/react": "0.2.2"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@types/node": "^26.2.0",
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useEffect, useState } from 'react';
4
+ import { injectStyles } from '../../styles';
4
5
 
5
6
  interface Subscription {
6
7
  id: string;
@@ -28,6 +29,7 @@ export function BillingPortal({
28
29
  invoices,
29
30
  onCancel,
30
31
  }: BillingPortalProps) {
32
+ injectStyles();
31
33
  if (!subscription) {
32
34
  return (
33
35
  <div className="slx-card">
@@ -1,5 +1,6 @@
1
1
  import { type FormEvent, useEffect, useState } from 'react';
2
2
  import { CheckIcon, KeyholeMark } from '../../icons';
3
+ import { injectStyles } from '../../styles';
3
4
 
4
5
  export interface EmailVerificationProps {
5
6
  /** Verification token (from email link ?token=...). If absent, shows resend form. */
@@ -14,6 +15,7 @@ export function EmailVerification({
14
15
  apiUrl,
15
16
  onSuccess,
16
17
  }: EmailVerificationProps) {
18
+ injectStyles();
17
19
  const [status, setStatus] = useState<
18
20
  'verifying' | 'success' | 'error' | 'resend'
19
21
  >(token ? 'verifying' : 'resend');
@@ -1,6 +1,7 @@
1
1
  import { SlyxupClient } from '@slyxup/core';
2
2
  import { type FormEvent, useEffect, useState } from 'react';
3
3
  import { CheckIcon, KeyholeMark } from '../../icons';
4
+ import { injectStyles } from '../../styles';
4
5
 
5
6
  export interface ForgotPasswordProps {
6
7
  apiUrl?: string;
@@ -14,6 +15,7 @@ export function ForgotPassword({
14
15
  onSuccess,
15
16
  onBackToSignIn,
16
17
  }: ForgotPasswordProps) {
18
+ injectStyles();
17
19
  const [email, setEmail] = useState('');
18
20
  const [sent, setSent] = useState(false);
19
21
  const [busy, setBusy] = useState(false);
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useEffect, useState } from 'react';
4
+ import { injectStyles } from '../../styles';
4
5
 
5
6
  interface Plan {
6
7
  id: string;
@@ -21,6 +22,7 @@ export interface PricingTableProps {
21
22
 
22
23
  /** Clean pricing grid with popular badge */
23
24
  export function PricingTable({ plans, onSelect, loading }: PricingTableProps) {
25
+ injectStyles();
24
26
  if (loading) {
25
27
  return (
26
28
  <div
@@ -1,5 +1,6 @@
1
1
  import { type FormEvent, useEffect, useState } from 'react';
2
2
  import { CheckIcon, KeyholeMark } from '../../icons';
3
+ import { injectStyles } from '../../styles';
3
4
 
4
5
  export interface ResetPasswordProps {
5
6
  /** Reset token (from email link ?token=...) */
@@ -14,6 +15,7 @@ export function ResetPassword({
14
15
  apiUrl,
15
16
  onSuccess,
16
17
  }: ResetPasswordProps) {
18
+ injectStyles();
17
19
  const [password, setPassword] = useState('');
18
20
  const [done, setDone] = useState(false);
19
21
  const [busy, setBusy] = useState(false);
@@ -2,6 +2,7 @@ import { SlyxupError } from '@slyxup/core';
2
2
  import { useAuth } from '@slyxup/react';
3
3
  import { type FormEvent, useEffect, useRef, useState } from 'react';
4
4
  import { GitHubIcon, GoogleIcon, KeyholeMark } from '../../icons';
5
+ import { injectStyles } from '../../styles';
5
6
 
6
7
  export interface SignInProps {
7
8
  /** Show social buttons (default true) */
@@ -10,6 +11,8 @@ export interface SignInProps {
10
11
  onSuccess?: () => void;
11
12
  /** Switch to sign up */
12
13
  onSignUpClick?: () => void;
14
+ /** Show "Forgot password?" — navigates via this callback */
15
+ onForgotPasswordClick?: () => void;
13
16
  }
14
17
 
15
18
  /** Email/password + OAuth sign-in card. */
@@ -17,10 +20,12 @@ export function SignIn({
17
20
  social = true,
18
21
  onSuccess,
19
22
  onSignUpClick,
23
+ onForgotPasswordClick,
20
24
  }: SignInProps) {
25
+ injectStyles();
21
26
  const { signIn, client } = useAuth() as unknown as {
22
27
  signIn: ReturnType<typeof useAuth>['signIn'];
23
- client: { publishableKey?: string };
28
+ client: { publishableKey?: string; apiUrl: string };
24
29
  };
25
30
  const [email, setEmail] = useState('');
26
31
  const [password, setPassword] = useState('');
@@ -54,7 +59,7 @@ export function SignIn({
54
59
  }
55
60
 
56
61
  function oauth(provider: 'google' | 'github') {
57
- window.location.href = `/v1/oauth/${provider}`;
62
+ window.location.href = `${client.apiUrl}/v1/oauth/${provider}`;
58
63
  }
59
64
 
60
65
  const missingKey =
@@ -65,17 +70,7 @@ export function SignIn({
65
70
  return (
66
71
  <div ref={cardRef} className={`slx-card${error ? ' slx-card-error' : ''}`}>
67
72
  {missingKey && (
68
- <p
69
- style={{
70
- fontSize: 12,
71
- background: '#fff3cd',
72
- border: '1px solid #ffe69c',
73
- borderRadius: 8,
74
- padding: '8px 10px',
75
- marginBottom: 14,
76
- lineHeight: 1.4,
77
- }}
78
- >
73
+ <p className="slx-setup-note">
79
74
  <strong>Setup:</strong> Add{' '}
80
75
  <code>NEXT_PUBLIC_SLYXUP_PUBLISHABLE_KEY</code> to{' '}
81
76
  <code>.env.local</code> — run <code>npx @slyxup/cli keys create</code>
@@ -134,9 +129,20 @@ export function SignIn({
134
129
  />
135
130
  </div>
136
131
  <div className="slx-field">
137
- <label className="slx-label" htmlFor="slx-signin-password">
138
- Password
139
- </label>
132
+ <div className="slx-row">
133
+ <label className="slx-label" htmlFor="slx-signin-password">
134
+ Password
135
+ </label>
136
+ {onForgotPasswordClick && (
137
+ <button
138
+ type="button"
139
+ className="slx-link slx-forgot"
140
+ onClick={onForgotPasswordClick}
141
+ >
142
+ Forgot password?
143
+ </button>
144
+ )}
145
+ </div>
140
146
  <input
141
147
  id="slx-signin-password"
142
148
  className="slx-input"
@@ -146,7 +152,6 @@ export function SignIn({
146
152
  value={password}
147
153
  onChange={(e) => setPassword(e.target.value)}
148
154
  required
149
- minLength={8}
150
155
  />
151
156
  </div>
152
157
  <button className="slx-btn" type="submit" disabled={busy}>
@@ -2,6 +2,7 @@ import { SlyxupError } from '@slyxup/core';
2
2
  import { useAuth } from '@slyxup/react';
3
3
  import { type FormEvent, useEffect, useRef, useState } from 'react';
4
4
  import { GitHubIcon, GoogleIcon, KeyholeMark } from '../../icons';
5
+ import { injectStyles } from '../../styles';
5
6
 
6
7
  export interface SignUpProps {
7
8
  social?: boolean;
@@ -15,9 +16,10 @@ export function SignUp({
15
16
  onSuccess,
16
17
  onSignInClick,
17
18
  }: SignUpProps) {
19
+ injectStyles();
18
20
  const { signUp, client } = useAuth() as unknown as {
19
21
  signUp: ReturnType<typeof useAuth>['signUp'];
20
- client: { publishableKey?: string };
22
+ client: { publishableKey?: string; apiUrl: string };
21
23
  };
22
24
  const [firstName, setFirstName] = useState('');
23
25
  const [email, setEmail] = useState('');
@@ -52,7 +54,7 @@ export function SignUp({
52
54
  }
53
55
 
54
56
  function oauth(provider: 'google' | 'github') {
55
- window.location.href = `/v1/oauth/${provider}`;
57
+ window.location.href = `${client.apiUrl}/v1/oauth/${provider}`;
56
58
  }
57
59
 
58
60
  const missingKey =
@@ -63,17 +65,7 @@ export function SignUp({
63
65
  return (
64
66
  <div ref={cardRef} className={`slx-card${error ? ' slx-card-error' : ''}`}>
65
67
  {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
- >
68
+ <p className="slx-setup-note">
77
69
  <strong>Setup:</strong> Add{' '}
78
70
  <code>NEXT_PUBLIC_SLYXUP_PUBLISHABLE_KEY</code> — run{' '}
79
71
  <code>npx @slyxup/cli keys create</code>