tekivex-ui 2.5.1 → 2.5.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 (80) hide show
  1. package/README.md +436 -83
  2. package/dist/TkxForm-DSMDo2Ol.js +388 -0
  3. package/dist/TkxForm-DwV_n-eE.cjs +12 -0
  4. package/dist/headless.cjs +1 -1
  5. package/dist/headless.js +396 -195
  6. package/dist/i18n.cjs +1 -0
  7. package/dist/i18n.js +757 -0
  8. package/dist/index.cjs +27 -22
  9. package/dist/index.d.ts +2 -1
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +4994 -5089
  12. package/dist/quantum.cjs +91 -0
  13. package/dist/quantum.js +2312 -0
  14. package/dist/realtime.cjs +25 -0
  15. package/dist/realtime.js +944 -0
  16. package/dist/security-C369oOWH.cjs +1 -0
  17. package/dist/security-CzAmaMqa.js +97 -0
  18. package/dist/src/components/TkxDataGrid.d.ts +9 -1
  19. package/dist/src/components/TkxDataGrid.d.ts.map +1 -1
  20. package/dist/src/components/TkxDropdown.d.ts +41 -0
  21. package/dist/src/components/TkxDropdown.d.ts.map +1 -0
  22. package/dist/src/components/TkxLiveFeed.d.ts +891 -0
  23. package/dist/src/components/TkxLiveFeed.d.ts.map +1 -0
  24. package/dist/src/components/TkxLiveLog.d.ts +892 -0
  25. package/dist/src/components/TkxLiveLog.d.ts.map +1 -0
  26. package/dist/src/components/TkxLiveMetrics.d.ts +24 -0
  27. package/dist/src/components/TkxLiveMetrics.d.ts.map +1 -0
  28. package/dist/src/components/TkxPlayground.d.ts +15 -0
  29. package/dist/src/components/TkxPlayground.d.ts.map +1 -0
  30. package/dist/src/components/TkxQuantumForm.d.ts +20 -0
  31. package/dist/src/components/TkxQuantumForm.d.ts.map +1 -0
  32. package/dist/src/components/TkxRealTimeChart.d.ts +27 -0
  33. package/dist/src/components/TkxRealTimeChart.d.ts.map +1 -0
  34. package/dist/src/components/TkxTable.d.ts +11 -1
  35. package/dist/src/components/TkxTable.d.ts.map +1 -1
  36. package/dist/src/components/TkxThemeBuilder.d.ts +9 -0
  37. package/dist/src/components/TkxThemeBuilder.d.ts.map +1 -0
  38. package/dist/src/components/index.d.ts +1 -0
  39. package/dist/src/components/index.d.ts.map +1 -1
  40. package/dist/src/engine/index.d.ts +2 -0
  41. package/dist/src/engine/index.d.ts.map +1 -1
  42. package/dist/src/engine/quantum-ai.d.ts +201 -0
  43. package/dist/src/engine/quantum-ai.d.ts.map +1 -0
  44. package/dist/src/engine/tkx.d.ts.map +1 -1
  45. package/dist/src/headless/index.d.ts +6 -0
  46. package/dist/src/headless/index.d.ts.map +1 -1
  47. package/dist/src/headless/useInfiniteQuery.d.ts +35 -0
  48. package/dist/src/headless/useInfiniteQuery.d.ts.map +1 -0
  49. package/dist/src/headless/useSSE.d.ts +36 -0
  50. package/dist/src/headless/useSSE.d.ts.map +1 -0
  51. package/dist/src/headless/useWebSocket.d.ts +33 -0
  52. package/dist/src/headless/useWebSocket.d.ts.map +1 -0
  53. package/dist/src/quantum/index.d.ts +9 -0
  54. package/dist/src/quantum/index.d.ts.map +1 -0
  55. package/dist/src/realtime/index.d.ts +9 -0
  56. package/dist/src/realtime/index.d.ts.map +1 -0
  57. package/dist/{TkxForm-BWK4LqY3.js → tkx-BJ10JtMv.js} +372 -721
  58. package/dist/tkx-BeNyC0w7.cjs +4 -0
  59. package/package.json +16 -1
  60. package/src/components/TkxDataGrid.tsx +72 -0
  61. package/src/components/TkxDropdown.tsx +802 -0
  62. package/src/components/TkxLiveFeed.tsx +329 -0
  63. package/src/components/TkxLiveLog.tsx +470 -0
  64. package/src/components/TkxLiveMetrics.tsx +291 -0
  65. package/src/components/TkxPlayground.tsx +736 -0
  66. package/src/components/TkxQuantumForm.tsx +592 -0
  67. package/src/components/TkxRealTimeChart.tsx +381 -0
  68. package/src/components/TkxTable.tsx +351 -77
  69. package/src/components/TkxThemeBuilder.tsx +685 -0
  70. package/src/components/index.ts +4 -0
  71. package/src/engine/index.ts +2 -0
  72. package/src/engine/quantum-ai.ts +1045 -0
  73. package/src/engine/tkx.ts +95 -4
  74. package/src/headless/index.ts +6 -0
  75. package/src/headless/useInfiniteQuery.ts +153 -0
  76. package/src/headless/useSSE.ts +165 -0
  77. package/src/headless/useWebSocket.ts +229 -0
  78. package/src/quantum/index.ts +39 -0
  79. package/src/realtime/index.ts +21 -0
  80. package/dist/TkxForm-ljQjX7KD.cjs +0 -15
@@ -0,0 +1,592 @@
1
+ // ── TkxQuantumForm ────────────────────────────────────────────────────────────
2
+ // Quantum-AI powered smart form. Field types, validation rules, labels, and
3
+ // placeholders are automatically inferred from field names via
4
+ // inferFieldIntelligence() from the quantum-ai engine.
5
+
6
+ import { useState, useCallback, useEffect } from 'react';
7
+ import { useTheme } from '../themes';
8
+ import { inferFieldIntelligence, type FieldIntelligence } from '../engine/quantum-ai';
9
+ import { tkx } from '../engine/tkx';
10
+ import { sanitizeString } from '../engine/security';
11
+
12
+ // ── Public API ────────────────────────────────────────────────────────────────
13
+
14
+ export interface QuantumFieldConfig {
15
+ name: string;
16
+ label?: string;
17
+ type?: string;
18
+ required?: boolean;
19
+ placeholder?: string;
20
+ }
21
+
22
+ export interface TkxQuantumFormProps {
23
+ fields: QuantumFieldConfig[];
24
+ onSubmit: (values: Record<string, string>) => void | Promise<void>;
25
+ submitLabel?: string;
26
+ layout?: 'vertical' | 'horizontal';
27
+ showConfidence?: boolean;
28
+ showQuantumState?: boolean;
29
+ }
30
+
31
+ // ── Validation runner ─────────────────────────────────────────────────────────
32
+
33
+ function runValidation(
34
+ rule: string,
35
+ ruleValue: number | string | undefined,
36
+ fieldValue: string,
37
+ allValues: Record<string, string>,
38
+ ): boolean {
39
+ const v = fieldValue.trim();
40
+ switch (rule) {
41
+ case 'required':
42
+ return v !== '';
43
+ case 'email':
44
+ case 'pattern':
45
+ if (typeof ruleValue === 'string') {
46
+ try {
47
+ return v === '' || new RegExp(ruleValue).test(v);
48
+ } catch {
49
+ return true;
50
+ }
51
+ }
52
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v);
53
+ case 'phone':
54
+ return v === '' || /^\+?[\d\s\-()]{7,15}$/.test(v);
55
+ case 'url':
56
+ return v === '' || /^https?:\/\/.+/.test(v);
57
+ case 'minLength':
58
+ case 'min': {
59
+ const n = Number(ruleValue ?? 0);
60
+ return v === '' || (isNaN(Number(v)) ? v.length >= n : Number(v) >= n);
61
+ }
62
+ case 'maxLength':
63
+ case 'max': {
64
+ const n = Number(ruleValue ?? Infinity);
65
+ return v === '' || (isNaN(Number(v)) ? v.length <= n : Number(v) <= n);
66
+ }
67
+ case 'integer':
68
+ return v === '' || /^\d+$/.test(v);
69
+ case 'alphanumeric':
70
+ return v === '' || /^[a-zA-Z0-9]+$/.test(v);
71
+ case 'match': {
72
+ const otherField = typeof ruleValue === 'string' ? ruleValue : 'password';
73
+ return v === (allValues[otherField] ?? '');
74
+ }
75
+ default:
76
+ return true;
77
+ }
78
+ }
79
+
80
+ // ── Bloch Sphere qubit visualizer ─────────────────────────────────────────────
81
+
82
+ interface QubitProps {
83
+ phase: number;
84
+ collapsed: boolean;
85
+ primaryColor: string;
86
+ borderColor: string;
87
+ }
88
+
89
+ function QubitVisualizer({ phase, collapsed, primaryColor, borderColor }: QubitProps) {
90
+ const cx = 12;
91
+ const cy = 12;
92
+ const r = 10;
93
+ const theta = collapsed ? 0 : phase;
94
+ const phi = collapsed ? 0 : phase * 1.618;
95
+ const tipX = cx + r * Math.sin(theta) * Math.cos(phi);
96
+ const tipY = cy - r * Math.cos(theta);
97
+
98
+ return (
99
+ <svg
100
+ width="24"
101
+ height="24"
102
+ viewBox="0 0 24 24"
103
+ aria-hidden="true"
104
+ style={{ flexShrink: 0, opacity: collapsed ? 0.4 : 1, transition: 'opacity 0.6s' }}
105
+ >
106
+ <circle cx={cx} cy={cy} r={r} fill="none" stroke={borderColor} strokeWidth="0.8" />
107
+ <ellipse cx={cx} cy={cy} rx={r} ry={r * 0.3} fill="none" stroke={borderColor} strokeWidth="0.5" strokeDasharray="2 2" />
108
+ <line x1={cx} y1={cy - r} x2={cx} y2={cy + r} stroke={borderColor} strokeWidth="0.5" />
109
+ <line
110
+ x1={cx} y1={cy} x2={tipX} y2={tipY}
111
+ stroke={primaryColor} strokeWidth="1.5"
112
+ style={{ transition: collapsed ? 'all 0.6s ease-out' : 'none' }}
113
+ />
114
+ <circle cx={tipX} cy={tipY} r="2" fill={primaryColor}
115
+ style={{ transition: collapsed ? 'all 0.6s ease-out' : 'none' }} />
116
+ </svg>
117
+ );
118
+ }
119
+
120
+ // ── Confidence Badge ──────────────────────────────────────────────────────────
121
+
122
+ function ConfidenceBadge({
123
+ confidence,
124
+ theme,
125
+ }: {
126
+ confidence: number;
127
+ theme: ReturnType<typeof useTheme>;
128
+ }) {
129
+ // confidence is 0-1 in the existing engine
130
+ const pct = Math.round(confidence * 100);
131
+ const color =
132
+ pct >= 80 ? theme.success :
133
+ pct >= 60 ? theme.warning :
134
+ theme.danger;
135
+ return (
136
+ <span
137
+ style={{
138
+ display: 'inline-flex',
139
+ alignItems: 'center',
140
+ gap: '2px',
141
+ fontSize: '10px',
142
+ fontWeight: 600,
143
+ padding: '1px 5px',
144
+ borderRadius: '999px',
145
+ backgroundColor: `${color}22`,
146
+ color,
147
+ border: `1px solid ${color}44`,
148
+ marginLeft: '6px',
149
+ verticalAlign: 'middle',
150
+ letterSpacing: '0.03em',
151
+ }}
152
+ >
153
+ ⚛ {pct}%
154
+ </span>
155
+ );
156
+ }
157
+
158
+ // ── Single field renderer ─────────────────────────────────────────────────────
159
+
160
+ interface FieldRendererProps {
161
+ config: QuantumFieldConfig;
162
+ intel: FieldIntelligence;
163
+ qubitPhase: number;
164
+ value: string;
165
+ error: string;
166
+ touched: boolean;
167
+ showConfidence: boolean;
168
+ showQuantumState: boolean;
169
+ collapsed: boolean;
170
+ onChange: (v: string) => void;
171
+ onBlur: () => void;
172
+ theme: ReturnType<typeof useTheme>;
173
+ mounted: boolean;
174
+ index: number;
175
+ }
176
+
177
+ function FieldRenderer({
178
+ config,
179
+ intel,
180
+ qubitPhase,
181
+ value,
182
+ error,
183
+ touched,
184
+ showConfidence,
185
+ showQuantumState,
186
+ collapsed,
187
+ onChange,
188
+ onBlur,
189
+ theme,
190
+ mounted,
191
+ index,
192
+ }: FieldRendererProps) {
193
+ const resolvedType = config.type ?? intel.type;
194
+ const resolvedLabel = config.label ?? intel.label;
195
+ const resolvedPlaceholder = config.placeholder ?? intel.placeholder;
196
+ const resolvedRequired =
197
+ config.required !== undefined
198
+ ? config.required
199
+ : intel.validations.some((v) => v.rule === 'required');
200
+
201
+ const safeLabel = sanitizeString(resolvedLabel);
202
+ const isTextarea = resolvedType === 'textarea';
203
+ const hasError = touched && error !== '';
204
+ const fieldId = `qf-${config.name}`;
205
+
206
+ const inputStyle: React.CSSProperties = {
207
+ width: '100%',
208
+ padding: '9px 12px',
209
+ border: `1.5px solid ${hasError ? theme.danger : theme.border}`,
210
+ borderRadius: '8px',
211
+ backgroundColor: theme.surface,
212
+ color: theme.text,
213
+ fontSize: '14px',
214
+ fontFamily: 'inherit',
215
+ outline: 'none',
216
+ boxSizing: 'border-box',
217
+ transition: 'border-color 0.15s, box-shadow 0.15s',
218
+ resize: isTextarea ? 'vertical' : undefined,
219
+ minHeight: isTextarea ? '80px' : undefined,
220
+ };
221
+
222
+ return (
223
+ <div
224
+ style={{
225
+ display: 'flex',
226
+ flexDirection: 'column',
227
+ gap: '4px',
228
+ opacity: mounted ? 1 : 0,
229
+ transform: mounted ? 'scaleY(1)' : 'scaleY(0.92)',
230
+ transition: `opacity 0.35s ease ${index * 60}ms, transform 0.35s ease ${index * 60}ms`,
231
+ transformOrigin: 'top',
232
+ }}
233
+ >
234
+ {/* Label row */}
235
+ <div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
236
+ {showQuantumState && (
237
+ <QubitVisualizer
238
+ phase={qubitPhase}
239
+ collapsed={collapsed}
240
+ primaryColor={theme.primary}
241
+ borderColor={theme.border}
242
+ />
243
+ )}
244
+ <label
245
+ htmlFor={fieldId}
246
+ style={{ fontSize: '13px', fontWeight: 500, color: theme.text, cursor: 'pointer' }}
247
+ >
248
+ {safeLabel}
249
+ {resolvedRequired && (
250
+ <span aria-hidden="true" style={{ color: theme.danger, marginLeft: '3px' }}>*</span>
251
+ )}
252
+ </label>
253
+ {showConfidence && <ConfidenceBadge confidence={intel.confidence} theme={theme} />}
254
+ </div>
255
+
256
+ {/* Input / Textarea */}
257
+ {isTextarea ? (
258
+ <textarea
259
+ id={fieldId}
260
+ value={value}
261
+ onChange={(e) => onChange(e.target.value)}
262
+ onBlur={onBlur}
263
+ placeholder={resolvedPlaceholder}
264
+ aria-invalid={hasError}
265
+ aria-required={resolvedRequired}
266
+ style={inputStyle}
267
+ />
268
+ ) : (
269
+ <input
270
+ id={fieldId}
271
+ type={resolvedType}
272
+ value={value}
273
+ onChange={(e) => onChange(e.target.value)}
274
+ onBlur={onBlur}
275
+ placeholder={resolvedPlaceholder}
276
+ aria-invalid={hasError}
277
+ aria-required={resolvedRequired}
278
+ style={inputStyle}
279
+ />
280
+ )}
281
+
282
+ {/* Error */}
283
+ {hasError && (
284
+ <span
285
+ role="alert"
286
+ style={{
287
+ fontSize: '12px',
288
+ color: theme.danger,
289
+ display: 'flex',
290
+ alignItems: 'center',
291
+ gap: '4px',
292
+ }}
293
+ >
294
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
295
+ <path d="M12 2C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10S17.523 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z" />
296
+ </svg>
297
+ {sanitizeString(error)}
298
+ </span>
299
+ )}
300
+ </div>
301
+ );
302
+ }
303
+
304
+ // ── Deterministic qubit phase from field name ─────────────────────────────────
305
+
306
+ function fieldPhase(name: string): number {
307
+ let h = 2166136261;
308
+ for (let i = 0; i < name.length; i++) {
309
+ h ^= name.charCodeAt(i);
310
+ h = Math.imul(h, 16777619) >>> 0;
311
+ }
312
+ return (h & 0xffff) / 0xffff * Math.PI * 2;
313
+ }
314
+
315
+ // ── Main Component ────────────────────────────────────────────────────────────
316
+
317
+ export function TkxQuantumForm({
318
+ fields,
319
+ onSubmit,
320
+ submitLabel = 'Submit',
321
+ layout = 'vertical',
322
+ showConfidence = true,
323
+ showQuantumState = false,
324
+ }: TkxQuantumFormProps) {
325
+ const theme = useTheme();
326
+
327
+ // Compute inferred intelligence once per field name
328
+ const intelligences = fields.map((f) => inferFieldIntelligence(f.name));
329
+
330
+ const [values, setValues] = useState<Record<string, string>>(() =>
331
+ Object.fromEntries(fields.map((f) => [f.name, ''])),
332
+ );
333
+ const [errors, setErrors] = useState<Record<string, string>>({});
334
+ const [touched, setTouched] = useState<Record<string, boolean>>({});
335
+ const [submitting, setSubmitting] = useState(false);
336
+ const [mounted, setMounted] = useState(false);
337
+ const [collapsed, setCollapsed] = useState(false);
338
+
339
+ useEffect(() => {
340
+ const id = requestAnimationFrame(() => setMounted(true));
341
+ return () => cancelAnimationFrame(id);
342
+ }, []);
343
+
344
+ const validateField = useCallback(
345
+ (name: string, currentValues: Record<string, string>): string => {
346
+ const fieldConfig = fields.find((f) => f.name === name);
347
+ const intel = intelligences[fields.findIndex((f) => f.name === name)];
348
+ if (!fieldConfig || !intel) return '';
349
+ const value = currentValues[name] ?? '';
350
+
351
+ for (const { rule, value: ruleValue, message } of intel.validations) {
352
+ if (rule === 'required' && fieldConfig.required === false) continue;
353
+ if (!runValidation(rule, ruleValue, value, currentValues)) {
354
+ return message;
355
+ }
356
+ }
357
+ return '';
358
+ },
359
+ // eslint-disable-next-line react-hooks/exhaustive-deps
360
+ [fields],
361
+ );
362
+
363
+ const validateAll = useCallback(
364
+ (currentValues: Record<string, string>): Record<string, string> => {
365
+ const errs: Record<string, string> = {};
366
+ for (const field of fields) {
367
+ const err = validateField(field.name, currentValues);
368
+ if (err) errs[field.name] = err;
369
+ }
370
+ return errs;
371
+ },
372
+ [fields, validateField],
373
+ );
374
+
375
+ const handleChange = useCallback(
376
+ (name: string, value: string) => {
377
+ setValues((prev) => {
378
+ const next = { ...prev, [name]: value };
379
+ if (touched[name]) {
380
+ setErrors((e) => ({ ...e, [name]: validateField(name, next) }));
381
+ // Re-validate confirm password when password changes (entanglement)
382
+ if (name === 'password') {
383
+ const confirmName = fields.find((f) =>
384
+ ['confirmpassword', 'confirm_password', 'passwordconfirm'].includes(
385
+ f.name.toLowerCase().replace(/[\s_-]/g, ''),
386
+ ),
387
+ )?.name;
388
+ if (confirmName && touched[confirmName]) {
389
+ setErrors((e) => ({ ...e, [confirmName]: validateField(confirmName, next) }));
390
+ }
391
+ }
392
+ }
393
+ return next;
394
+ });
395
+ setCollapsed(true);
396
+ },
397
+ [fields, touched, validateField],
398
+ );
399
+
400
+ const handleBlur = useCallback(
401
+ (name: string) => {
402
+ setTouched((prev) => ({ ...prev, [name]: true }));
403
+ setErrors((prev) => ({ ...prev, [name]: validateField(name, values) }));
404
+ },
405
+ [values, validateField],
406
+ );
407
+
408
+ const handleSubmit = useCallback(
409
+ async (e: React.FormEvent) => {
410
+ e.preventDefault();
411
+ const allTouched = Object.fromEntries(fields.map((f) => [f.name, true]));
412
+ setTouched(allTouched);
413
+ const errs = validateAll(values);
414
+ setErrors(errs);
415
+ if (Object.keys(errs).length > 0) return;
416
+ setSubmitting(true);
417
+ try {
418
+ await onSubmit(values);
419
+ } finally {
420
+ setSubmitting(false);
421
+ }
422
+ },
423
+ [fields, values, validateAll, onSubmit],
424
+ );
425
+
426
+ // Overall confidence: average of all inferred confidences (0–1), converted to %
427
+ const overallConfidence = intelligences.length > 0
428
+ ? Math.round(
429
+ (intelligences.reduce((s, i) => s + i.confidence, 0) / intelligences.length) * 100,
430
+ )
431
+ : 0;
432
+ const confidenceColor =
433
+ overallConfidence >= 80 ? theme.success :
434
+ overallConfidence >= 60 ? theme.warning :
435
+ theme.danger;
436
+
437
+ const isHorizontal = layout === 'horizontal';
438
+
439
+ return (
440
+ <form
441
+ onSubmit={handleSubmit}
442
+ noValidate
443
+ className={tkx('w-full')}
444
+ style={{
445
+ backgroundColor: theme.surface,
446
+ border: `1px solid ${theme.border}`,
447
+ borderRadius: '12px',
448
+ padding: '24px',
449
+ position: 'relative',
450
+ boxSizing: 'border-box',
451
+ }}
452
+ >
453
+ {/* ⚛ Quantum AI badge */}
454
+ <span
455
+ aria-label="Powered by Quantum AI"
456
+ style={{
457
+ position: 'absolute',
458
+ top: '16px',
459
+ right: '16px',
460
+ fontSize: '10px',
461
+ fontWeight: 700,
462
+ padding: '3px 8px',
463
+ borderRadius: '999px',
464
+ backgroundColor: `${theme.primary}20`,
465
+ color: theme.primary,
466
+ border: `1px solid ${theme.primary}44`,
467
+ letterSpacing: '0.04em',
468
+ userSelect: 'none',
469
+ }}
470
+ >
471
+ ⚛ Quantum AI
472
+ </span>
473
+
474
+ {/* Overall confidence meter */}
475
+ {showConfidence && (
476
+ <div style={{ marginBottom: '20px', paddingRight: '100px' }}>
477
+ <div
478
+ style={{
479
+ display: 'flex',
480
+ alignItems: 'center',
481
+ justifyContent: 'space-between',
482
+ marginBottom: '4px',
483
+ }}
484
+ >
485
+ <span style={{ fontSize: '11px', color: theme.textMuted, fontWeight: 500 }}>
486
+ Form Intelligence Confidence
487
+ </span>
488
+ <span style={{ fontSize: '11px', fontWeight: 700, color: confidenceColor }}>
489
+ {overallConfidence}%
490
+ </span>
491
+ </div>
492
+ <div
493
+ style={{
494
+ height: '4px',
495
+ borderRadius: '4px',
496
+ backgroundColor: theme.border,
497
+ overflow: 'hidden',
498
+ }}
499
+ >
500
+ <div
501
+ style={{
502
+ height: '100%',
503
+ width: `${overallConfidence}%`,
504
+ backgroundColor: confidenceColor,
505
+ borderRadius: '4px',
506
+ transition: 'width 0.8s ease',
507
+ }}
508
+ />
509
+ </div>
510
+ </div>
511
+ )}
512
+
513
+ {/* Fields */}
514
+ <div
515
+ style={{
516
+ display: 'grid',
517
+ gridTemplateColumns: isHorizontal ? 'repeat(auto-fill, minmax(260px, 1fr))' : '1fr',
518
+ gap: '16px',
519
+ marginBottom: '20px',
520
+ }}
521
+ >
522
+ {fields.map((field, index) => (
523
+ <FieldRenderer
524
+ key={field.name}
525
+ config={field}
526
+ intel={intelligences[index]!}
527
+ qubitPhase={fieldPhase(field.name)}
528
+ value={values[field.name] ?? ''}
529
+ error={errors[field.name] ?? ''}
530
+ touched={touched[field.name] ?? false}
531
+ showConfidence={showConfidence}
532
+ showQuantumState={showQuantumState}
533
+ collapsed={collapsed}
534
+ onChange={(v) => handleChange(field.name, v)}
535
+ onBlur={() => handleBlur(field.name)}
536
+ theme={theme}
537
+ mounted={mounted}
538
+ index={index}
539
+ />
540
+ ))}
541
+ </div>
542
+
543
+ {/* Submit */}
544
+ <button
545
+ type="submit"
546
+ disabled={submitting}
547
+ style={{
548
+ width: '100%',
549
+ padding: '10px 20px',
550
+ borderRadius: '8px',
551
+ border: 'none',
552
+ backgroundColor: submitting ? `${theme.primary}88` : theme.primary,
553
+ color: theme.bg,
554
+ fontSize: '14px',
555
+ fontWeight: 700,
556
+ fontFamily: 'inherit',
557
+ cursor: submitting ? 'not-allowed' : 'pointer',
558
+ transition: 'background-color 0.15s, opacity 0.15s',
559
+ display: 'flex',
560
+ alignItems: 'center',
561
+ justifyContent: 'center',
562
+ gap: '8px',
563
+ }}
564
+ >
565
+ {submitting ? (
566
+ <>
567
+ <svg
568
+ width="14"
569
+ height="14"
570
+ viewBox="0 0 24 24"
571
+ fill="none"
572
+ stroke="currentColor"
573
+ strokeWidth="2.5"
574
+ style={{ animation: 'qf-spin 0.8s linear infinite' }}
575
+ aria-hidden="true"
576
+ >
577
+ <circle cx="12" cy="12" r="10" strokeOpacity="0.25" />
578
+ <path d="M12 2a10 10 0 0 1 10 10" />
579
+ </svg>
580
+ Processing…
581
+ </>
582
+ ) : (
583
+ sanitizeString(submitLabel)
584
+ )}
585
+ </button>
586
+
587
+ <style>{`@keyframes qf-spin { to { transform: rotate(360deg); } }`}</style>
588
+ </form>
589
+ );
590
+ }
591
+
592
+ TkxQuantumForm.displayName = 'TkxQuantumForm';