chordia-ui 3.2.1 → 3.2.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chordia-ui",
3
- "version": "3.2.1",
3
+ "version": "3.2.2",
4
4
  "description": "Chordia Design System - UI components, tokens, and Tailwind preset",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs.js",
@@ -190,16 +190,40 @@ export default function LoginPage({
190
190
  // Sync React state with browser autofill (autofill doesn't fire onChange)
191
191
  const emailRef = useRef(null);
192
192
  const passwordRef = useRef(null);
193
+ const [autofilled, setAutofilled] = useState(false);
193
194
  useEffect(() => {
194
195
  const sync = () => {
195
196
  const e = emailRef.current;
196
197
  const p = passwordRef.current;
197
- if (e && e.value && !email) setEmail(e.value);
198
- if (p && p.value && !password) setPassword(p.value);
198
+ if (e && e.value) setEmail(e.value);
199
+ if (p && p.value) setPassword(p.value);
200
+ };
201
+ // Poll for autofill values at multiple intervals
202
+ const timers = [100, 300, 600, 1000, 2000].map(ms => setTimeout(sync, ms));
203
+
204
+ // Detect Chrome/Safari autofill via the :-webkit-autofill animation
205
+ const handleAnimationStart = (e) => {
206
+ if (e.animationName === 'onAutoFillStart' || e.target.matches(':-webkit-autofill')) {
207
+ setAutofilled(true);
208
+ setTimeout(sync, 50);
209
+ }
210
+ };
211
+ const eEl = emailRef.current;
212
+ const pEl = passwordRef.current;
213
+ eEl?.addEventListener('animationstart', handleAnimationStart);
214
+ pEl?.addEventListener('animationstart', handleAnimationStart);
215
+ // Also listen for input/change events that some password managers fire
216
+ const onInput = () => setTimeout(sync, 0);
217
+ eEl?.addEventListener('input', onInput);
218
+ pEl?.addEventListener('input', onInput);
219
+
220
+ return () => {
221
+ timers.forEach(clearTimeout);
222
+ eEl?.removeEventListener('animationstart', handleAnimationStart);
223
+ pEl?.removeEventListener('animationstart', handleAnimationStart);
224
+ eEl?.removeEventListener('input', onInput);
225
+ pEl?.removeEventListener('input', onInput);
199
226
  };
200
- // Check multiple times as autofill timing varies across browsers
201
- const timers = [100, 500, 1000, 2000].map(ms => setTimeout(sync, ms));
202
- return () => timers.forEach(clearTimeout);
203
227
  }, []); // eslint-disable-line react-hooks/exhaustive-deps
204
228
 
205
229
  // Switch to full-name screen when host app signals unregistered email
@@ -212,7 +236,7 @@ export default function LoginPage({
212
236
 
213
237
  const loading = externalLoading ?? internalLoading;
214
238
  const error = externalError ?? internalError;
215
- const canSubmit = email && password && !loading;
239
+ const canSubmit = (email && password && !loading) || (autofilled && !loading);
216
240
 
217
241
  // Error-aware focus handlers for sign-in fields
218
242
  const focusErr = (e) => {
@@ -241,10 +265,14 @@ export default function LoginPage({
241
265
  const handleSubmit = async (e) => {
242
266
  e.preventDefault();
243
267
  if (!canSubmit) return;
268
+ // Read directly from DOM if React state missed autofill
269
+ const submitEmail = email || emailRef.current?.value || '';
270
+ const submitPassword = password || passwordRef.current?.value || '';
271
+ if (!submitEmail || !submitPassword) return;
244
272
  setInternalError(null);
245
273
  setInternalLoading(true);
246
274
  try {
247
- await onLogin?.(email, password);
275
+ await onLogin?.(submitEmail, submitPassword);
248
276
  } catch (err) {
249
277
  setInternalError(err?.message || 'Something went wrong. Please try again.');
250
278
  } finally {
@@ -487,7 +515,7 @@ export default function LoginPage({
487
515
 
488
516
  <div style={{ display: 'flex', flexDirection: 'column', width: '100%', gap: 16 }}>
489
517
  <Field label="Enter one-time code" gap={12}>
490
- <div style={{ display: 'flex', gap: 12 }}>
518
+ <div style={{ display: 'flex', gap: 8 }}>
491
519
  {otpDigits.map((digit, i) => (
492
520
  <input key={i} ref={(el) => { otpRefs.current[i] = el; }}
493
521
  type="text" inputMode="numeric" maxLength={1} value={digit}
@@ -495,9 +523,9 @@ export default function LoginPage({
495
523
  onKeyDown={(e) => handleOtpKeyDown(i, e)}
496
524
  autoFocus={i === 0}
497
525
  style={{
498
- width: 56, height: 56, textAlign: 'center', fontSize: 20, fontWeight: 600,
526
+ flex: 1, minWidth: 0, height: 40, textAlign: 'center', fontSize: 16, fontWeight: 600,
499
527
  fontFamily: FF, color: 'var(--color-text)', border: '1px solid var(--color-input-border)',
500
- borderRadius: 8, outline: 'none', background: 'white', boxSizing: 'border-box',
528
+ borderRadius: 4, outline: 'none', background: 'white', boxSizing: 'border-box',
501
529
  transition: 'border-color 0.15s, box-shadow 0.15s', caretColor: GREEN,
502
530
  }}
503
531
  onFocus={focusGreen} onBlur={blurGray}
@@ -554,7 +582,7 @@ export default function LoginPage({
554
582
  </Field>
555
583
 
556
584
  <Field label="Enter one-time code" gap={12}>
557
- <div style={{ display: 'flex', gap: 12 }}>
585
+ <div style={{ display: 'flex', gap: 8 }}>
558
586
  {otpDigits.map((digit, i) => (
559
587
  <input key={i} ref={(el) => { otpRefs.current[i] = el; }}
560
588
  type="text" inputMode="numeric" maxLength={1} value={digit}
@@ -562,9 +590,9 @@ export default function LoginPage({
562
590
  onKeyDown={(e) => handleOtpKeyDown(i, e)}
563
591
  autoFocus={i === 0}
564
592
  style={{
565
- width: 56, height: 56, textAlign: 'center', fontSize: 20, fontWeight: 600,
593
+ flex: 1, minWidth: 0, height: 40, textAlign: 'center', fontSize: 16, fontWeight: 600,
566
594
  fontFamily: FF, color: 'var(--color-text)', border: '1px solid var(--color-input-border)',
567
- borderRadius: 8, outline: 'none', background: 'white', boxSizing: 'border-box',
595
+ borderRadius: 4, outline: 'none', background: 'white', boxSizing: 'border-box',
568
596
  transition: 'border-color 0.15s, box-shadow 0.15s', caretColor: GREEN,
569
597
  }}
570
598
  onFocus={focusGreen} onBlur={blurGray}
@@ -660,7 +688,7 @@ export default function LoginPage({
660
688
  </div>
661
689
 
662
690
  <NavRow text="Not a member yet?" linkText="Sign Up" onClick={() => { setView('signup'); onSignUp?.(); }} />
663
- <TermsFooter onTerms={onTerms} onPrivacyPolicy={onPrivacyPolicy} paddingTop={12} />
691
+ <TermsFooter onTerms={onTerms} onPrivacyPolicy={onPrivacyPolicy} paddingTop={16} />
664
692
  </div>
665
693
  </div>
666
694
  )}