chati-dev 4.3.0 → 4.4.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.
- package/README.md +18 -13
- package/bin/chati.js +32 -4
- package/framework/agents/build/dev.md +9 -5
- package/framework/agents/discover/brief.md +2 -0
- package/framework/agents/discover/brownfield-wu.md +2 -0
- package/framework/agents/discover/greenfield-wu.md +18 -1
- package/framework/agents/plan/detail.md +2 -0
- package/framework/agents/plan/tasks.md +31 -0
- package/framework/agents/plan/ux-brand-architect.md +21 -2
- package/framework/agents/plan/ux-component-engineer.md +10 -0
- package/framework/agents/quality/qa-implementation.md +1 -1
- package/framework/agents/quality/qa-planning.md +1 -1
- package/framework/agents/quality/qa-visual.md +33 -21
- package/framework/config.yaml +5 -4
- package/framework/constitution.md +58 -11
- package/framework/context/governance.md +12 -10
- package/framework/context/root.md +2 -2
- package/framework/data/entity-registry.yaml +12 -4
- package/framework/data/qa-rubrics.yaml +335 -0
- package/framework/domains/constitution.yaml +3 -3
- package/framework/executors/json-validate.js +80 -0
- package/framework/executors/npm-script.js +72 -0
- package/framework/executors/path-check.js +67 -0
- package/framework/executors/registry.yaml +36 -0
- package/framework/hooks/advance-trigger.js +47 -3
- package/framework/hooks/constitution-guard.js +9 -4
- package/framework/hooks/git-push-authority.js +113 -0
- package/framework/hooks/license-guard.js +59 -9
- package/framework/hooks/model-governance.js +40 -17
- package/framework/hooks/prism-engine.js +78 -11
- package/framework/hooks/session-digest.js +12 -3
- package/framework/hooks/settings.json +7 -3
- package/framework/hooks/style-guard.js +52 -3
- package/framework/hooks/team-quality-gate.js +43 -5
- package/framework/i18n/en.yaml +3 -3
- package/framework/i18n/es.yaml +3 -3
- package/framework/i18n/fr.yaml +3 -3
- package/framework/i18n/pt.yaml +3 -3
- package/framework/intelligence/confidence.yaml +85 -6
- package/framework/intelligence/context-engine.md +9 -5
- package/framework/intelligence/decision-engine.md +1 -1
- package/framework/orchestrator/chati-router.js +59 -5
- package/framework/orchestrator/chati-update.md +19 -3
- package/framework/orchestrator/chati.md +20 -1
- package/framework/quality-gates/planning-gate.md +2 -0
- package/framework/rules/human-writing-style.md +47 -0
- package/framework/scaffold/motion-premium/README.md +12 -2
- package/framework/scaffold/motion-premium/lib/animations/tokens.ts.template +1 -1
- package/framework/scaffold/motion-premium/scaffold.yaml +6 -1
- package/framework/scaffold/motion-premium-3d/README.md +10 -0
- package/framework/scaffold/motion-premium-3d/components/webgl/WebGLContext.tsx.template +1 -1
- package/framework/scaffold/motion-premium-3d/scaffold.yaml +3 -0
- package/framework/scaffold/saas-dashboard/README.md +58 -0
- package/framework/scaffold/saas-dashboard/app/dashboard/page.tsx.template +546 -0
- package/framework/scaffold/saas-dashboard/app/globals.css.template +746 -0
- package/framework/scaffold/saas-dashboard/app/layout.tsx.template +192 -0
- package/framework/scaffold/saas-dashboard/app/shell.css.template +1070 -0
- package/framework/scaffold/saas-dashboard/components/data/Chart.tsx.template +725 -0
- package/framework/scaffold/saas-dashboard/components/data/DataTable.tsx.template +623 -0
- package/framework/scaffold/saas-dashboard/components/data/DataTable.types.ts.template +104 -0
- package/framework/scaffold/saas-dashboard/components/data/DataTablePagination.tsx.template +234 -0
- package/framework/scaffold/saas-dashboard/components/data/SkeletonBlock.tsx.template +111 -0
- package/framework/scaffold/saas-dashboard/components/data/StatCard.tsx.template +393 -0
- package/framework/scaffold/saas-dashboard/components/data/StatCardGrid.tsx.template +121 -0
- package/framework/scaffold/saas-dashboard/components/feedback/EmptyState.tsx.template +281 -0
- package/framework/scaffold/saas-dashboard/components/interaction/CommandPalette.tsx.template +767 -0
- package/framework/scaffold/saas-dashboard/components/interaction/Field.tsx.template +351 -0
- package/framework/scaffold/saas-dashboard/components/interaction/Form.tsx.template +424 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Badge.tsx.template +193 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Button.tsx.template +261 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Card.tsx.template +287 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Input.tsx.template +310 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Spinner.tsx.template +135 -0
- package/framework/scaffold/saas-dashboard/components/primitives/index.ts.template +60 -0
- package/framework/scaffold/saas-dashboard/components/shell/AppShell.tsx.template +174 -0
- package/framework/scaffold/saas-dashboard/components/shell/Breadcrumb.tsx.template +186 -0
- package/framework/scaffold/saas-dashboard/components/shell/Container.tsx.template +86 -0
- package/framework/scaffold/saas-dashboard/components/shell/NavItem.tsx.template +147 -0
- package/framework/scaffold/saas-dashboard/components/shell/Sidebar.tsx.template +378 -0
- package/framework/scaffold/saas-dashboard/components/shell/SidebarContext.tsx.template +169 -0
- package/framework/scaffold/saas-dashboard/components/shell/ThemeToggle.tsx.template +128 -0
- package/framework/scaffold/saas-dashboard/components/shell/Topbar.tsx.template +301 -0
- package/framework/scaffold/saas-dashboard/lib/brand.ts.template +324 -0
- package/framework/scaffold/saas-dashboard/scaffold.yaml +225 -0
- package/framework/schemas/session.schema.json +5 -0
- package/framework/schemas/task.schema.json +9 -0
- package/framework/scripts/visual-qa.js +101 -3
- package/framework/tasks/orchestrator-health.md +4 -4
- package/framework/tasks/qa-impl-verdict.md +11 -5
- package/framework/templates/brandbook-html-tmpl.md +1 -1
- package/framework/templates/qa-gate-tmpl.yaml +24 -11
- package/package.json +4 -3
- package/src/config/context-file-generator.js +0 -6
- package/src/dashboard/renderer.js +0 -36
- package/src/executors/runner.js +204 -0
- package/src/installer/core.js +13 -3
- package/src/installer/templates.js +5 -6
- package/src/intelligence/registry-manager.js +1 -1
- package/src/license/client.js +9 -3
- package/src/license/commands.js +12 -2
- package/src/license/machine-id.js +42 -1
- package/src/memory/gotchas.js +58 -40
- package/src/memory/magic-docs.js +1 -1
- package/src/memory/session-digest.js +9 -4
- package/src/orchestrator/cli.js +186 -15
- package/src/orchestrator/doctor.js +98 -16
- package/src/orchestrator/pipeline-manager.js +44 -22
- package/src/orchestrator/session-manager.js +64 -8
- package/src/telemetry/sender.js +7 -19
- package/src/terminal/run-team.js +3 -3
- package/src/upgrade/tracked-files-detector.js +34 -16
- package/src/utils/feature-flags.js +1 -1
- package/src/utils/flatten-entities.js +4 -40
- package/src/utils/schema-validator.js +0 -14
- package/src/wizard/i18n.js +3 -3
- package/src/api/index.js +0 -120
- package/src/autonomy/autonomous-gate.js +0 -294
- package/src/autonomy/build-loop.js +0 -281
- package/src/autonomy/build-state.js +0 -286
- package/src/autonomy/cause-analyzer.js +0 -177
- package/src/autonomy/escalation.js +0 -214
- package/src/autonomy/index.js +0 -51
- package/src/autonomy/mode-manager.js +0 -225
- package/src/autonomy/mode-suggester.js +0 -283
- package/src/autonomy/progress-reporter.js +0 -275
- package/src/autonomy/safety-net.js +0 -370
- package/src/config/agent-customizer.js +0 -231
- package/src/decision/analyzer.js +0 -291
- package/src/decision/engine.js +0 -250
- package/src/decision/index.js +0 -38
- package/src/decision/registry-healer.js +0 -468
- package/src/decision/registry-updater.js +0 -339
- package/src/extensions/loader.js +0 -145
- package/src/extensions/registry.js +0 -134
- package/src/gates/circuit-breaker.js +0 -151
- package/src/gates/g1-planning-complete.js +0 -154
- package/src/gates/g2-qa-planning.js +0 -156
- package/src/gates/g3-implementation.js +0 -215
- package/src/gates/g4-qa-implementation.js +0 -240
- package/src/gates/g5-deploy-ready.js +0 -181
- package/src/gates/gate-base.js +0 -185
- package/src/gates/index.js +0 -46
- package/src/health/auto-fix.js +0 -216
- package/src/health/engine.js +0 -246
- package/src/merger/semantic-merger.js +0 -292
- package/src/preview/detector.js +0 -238
- package/src/preview/index.js +0 -20
- package/src/preview/launcher.js +0 -235
- package/src/preview/log-buffer.js +0 -103
- package/src/quality/metrics-collector.js +0 -281
- package/src/quality/test-runner.js +0 -366
- package/src/tasks/executor.js +0 -195
- package/src/tasks/index.js +0 -4
- package/src/tasks/loader.js +0 -210
- package/src/tasks/router.js +0 -182
- package/src/utils/event-bus.js +0 -126
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* components/interaction/Field.tsx
|
|
3
|
+
* SaaS Dashboard Preset -- D_interaction phase
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS EXISTS
|
|
6
|
+
* ---------------
|
|
7
|
+
* The Field primitive is the accessibility contract boundary for every input
|
|
8
|
+
* in the dashboard. Two failure modes plague dashboard forms:
|
|
9
|
+
*
|
|
10
|
+
* 1. Placeholder-only labels: placeholder disappears on focus, leaving
|
|
11
|
+
* users with short-term memory limitations (or filling multiple fields
|
|
12
|
+
* rapidly) unable to recall what a field is for. WCAG 1.3.1 requires
|
|
13
|
+
* programmatic labels. This component makes it structurally impossible
|
|
14
|
+
* to render an input without a <label>.
|
|
15
|
+
*
|
|
16
|
+
* 2. Error announced too late: error text that appears only near the
|
|
17
|
+
* submit button is not announced to screen reader users focused inside
|
|
18
|
+
* the form. The `role="alert"` on the error message here fires
|
|
19
|
+
* immediately when error text appears, regardless of focus position.
|
|
20
|
+
*
|
|
21
|
+
* ARCHITECTURE DECISIONS
|
|
22
|
+
* ----------------------
|
|
23
|
+
* 1. Field is the composition root; it renders the <label>, hint, and error.
|
|
24
|
+
* The <Input> sub-component handles only the input element itself.
|
|
25
|
+
* This separation means the Field wrapper can be reused with <textarea>
|
|
26
|
+
* or <select> by passing `as="textarea"` / `as="select"`, without
|
|
27
|
+
* duplicating label/hint/error logic.
|
|
28
|
+
*
|
|
29
|
+
* 2. `labelHidden` prop renders the label text inside a `.sr-only` span
|
|
30
|
+
* so visually minimal forms (e.g., inline search) still have SR labels.
|
|
31
|
+
* The label element is NEVER removed from the DOM.
|
|
32
|
+
*
|
|
33
|
+
* 3. Icon slots use absolute positioning within a `position:relative` wrapper.
|
|
34
|
+
* The input's padding-left / padding-right adjusts via inline style when
|
|
35
|
+
* icons are present. This avoids flexbox-inside-input hacks and keeps
|
|
36
|
+
* the input text baseline aligned regardless of icon presence.
|
|
37
|
+
*
|
|
38
|
+
* 4. The `required` prop adds an asterisk in the label AND sets
|
|
39
|
+
* `aria-required="true"` on the input. Both are needed: the asterisk
|
|
40
|
+
* is a visual convention; aria-required communicates to SR users who
|
|
41
|
+
* may not see the asterisk.
|
|
42
|
+
*
|
|
43
|
+
* 5. Focus ring: implemented globally via `:focus-visible` in globals.css.
|
|
44
|
+
* The input itself does NOT set outline -- it relies on the global rule,
|
|
45
|
+
* which applies the --shadow-focus token that includes a 2px ring with
|
|
46
|
+
* 2px offset.
|
|
47
|
+
*
|
|
48
|
+
* STATE TRANSITIONS
|
|
49
|
+
* -----------------
|
|
50
|
+
* resting -> border: var(--color-border-default)
|
|
51
|
+
* focus -> border: var(--color-border-emphasis); transition duration-normal
|
|
52
|
+
* error -> border: var(--color-danger); bg: var(--color-danger-subtle) tint
|
|
53
|
+
* disabled -> opacity 0.5; cursor: not-allowed
|
|
54
|
+
*
|
|
55
|
+
* TOKEN USAGE
|
|
56
|
+
* -----------
|
|
57
|
+
* All properties reference CSS custom properties from globals.css.
|
|
58
|
+
* No raw hex values, no raw pixel literals.
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
'use client';
|
|
62
|
+
|
|
63
|
+
import React, {
|
|
64
|
+
forwardRef,
|
|
65
|
+
useId,
|
|
66
|
+
type InputHTMLAttributes,
|
|
67
|
+
type TextareaHTMLAttributes,
|
|
68
|
+
type SelectHTMLAttributes,
|
|
69
|
+
type ReactNode,
|
|
70
|
+
} from 'react';
|
|
71
|
+
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// Types
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
type InputAs = 'input' | 'textarea' | 'select';
|
|
77
|
+
|
|
78
|
+
export interface FieldProps {
|
|
79
|
+
/** Always rendered as a real <label>. Never omitted. */
|
|
80
|
+
label: string;
|
|
81
|
+
/** Hint text below the input; linked via aria-describedby. */
|
|
82
|
+
hint?: string;
|
|
83
|
+
/** Error message. Overrides hint. Sets aria-invalid and role=alert. */
|
|
84
|
+
error?: string;
|
|
85
|
+
/** Visually hides the label but keeps it in the DOM for screen readers. */
|
|
86
|
+
labelHidden?: boolean;
|
|
87
|
+
/** Lucide icon rendered inside the input on the left. */
|
|
88
|
+
iconLeft?: ReactNode;
|
|
89
|
+
/** Lucide icon or button rendered inside the input on the right. */
|
|
90
|
+
iconRight?: ReactNode;
|
|
91
|
+
/** Adds asterisk and aria-required. */
|
|
92
|
+
required?: boolean;
|
|
93
|
+
/** Element to render. Default: 'input'. */
|
|
94
|
+
as?: InputAs;
|
|
95
|
+
/** Passed to underlying input element. */
|
|
96
|
+
inputProps?: InputHTMLAttributes<HTMLInputElement>;
|
|
97
|
+
/** Passed when as='textarea'. */
|
|
98
|
+
textareaProps?: TextareaHTMLAttributes<HTMLTextAreaElement>;
|
|
99
|
+
/** Passed when as='select'. */
|
|
100
|
+
selectProps?: SelectHTMLAttributes<HTMLSelectElement>;
|
|
101
|
+
/** Children for select options when as='select'. */
|
|
102
|
+
children?: ReactNode;
|
|
103
|
+
/** Additional class on the field wrapper. */
|
|
104
|
+
className?: string;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// Shared input styles (CSS-in-JS-style object used across all variants)
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
function buildInputStyle({
|
|
112
|
+
hasError,
|
|
113
|
+
isDisabled,
|
|
114
|
+
iconLeftPresent,
|
|
115
|
+
iconRightPresent,
|
|
116
|
+
}: {
|
|
117
|
+
hasError: boolean;
|
|
118
|
+
isDisabled: boolean;
|
|
119
|
+
iconLeftPresent: boolean;
|
|
120
|
+
iconRightPresent: boolean;
|
|
121
|
+
}): React.CSSProperties {
|
|
122
|
+
return {
|
|
123
|
+
display: 'block',
|
|
124
|
+
width: '100%',
|
|
125
|
+
minHeight: '40px',
|
|
126
|
+
paddingTop: 'var(--spacing-input-pad-v)',
|
|
127
|
+
paddingBottom: 'var(--spacing-input-pad-v)',
|
|
128
|
+
paddingLeft: iconLeftPresent ? '36px' : 'var(--spacing-input-pad-h)',
|
|
129
|
+
paddingRight: iconRightPresent ? '36px' : 'var(--spacing-input-pad-h)',
|
|
130
|
+
backgroundColor: 'var(--color-bg-sunken)',
|
|
131
|
+
color: hasError ? 'var(--color-text-primary)' : 'var(--color-text-primary)',
|
|
132
|
+
fontSize: 'var(--type-body)',
|
|
133
|
+
lineHeight: 'var(--type-body-lh)',
|
|
134
|
+
fontFamily: 'var(--font-sans)',
|
|
135
|
+
borderRadius: 'var(--radius-default)',
|
|
136
|
+
border: hasError
|
|
137
|
+
? '1px solid var(--color-danger)'
|
|
138
|
+
: '1px solid var(--color-border-default)',
|
|
139
|
+
outline: 'none',
|
|
140
|
+
opacity: isDisabled ? 0.5 : 1,
|
|
141
|
+
cursor: isDisabled ? 'not-allowed' : 'text',
|
|
142
|
+
transition: `border-color var(--duration-normal) var(--ease-in-out)`,
|
|
143
|
+
// Focus border via CSS :focus-visible in globals.css (:focus-visible + border-color)
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
// Icon slot helper
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
|
|
151
|
+
function IconSlot({
|
|
152
|
+
side,
|
|
153
|
+
children,
|
|
154
|
+
}: {
|
|
155
|
+
side: 'left' | 'right';
|
|
156
|
+
children: ReactNode;
|
|
157
|
+
}) {
|
|
158
|
+
return (
|
|
159
|
+
<span
|
|
160
|
+
aria-hidden="true"
|
|
161
|
+
style={{
|
|
162
|
+
position: 'absolute',
|
|
163
|
+
top: '50%',
|
|
164
|
+
transform: 'translateY(-50%)',
|
|
165
|
+
[side === 'left' ? 'left' : 'right']: '10px',
|
|
166
|
+
display: 'flex',
|
|
167
|
+
alignItems: 'center',
|
|
168
|
+
justifyContent: 'center',
|
|
169
|
+
color: 'var(--color-text-muted)',
|
|
170
|
+
pointerEvents: 'none',
|
|
171
|
+
zIndex: 1,
|
|
172
|
+
}}
|
|
173
|
+
>
|
|
174
|
+
{children}
|
|
175
|
+
</span>
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
// Field component
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
|
|
183
|
+
export const Field = forwardRef<HTMLDivElement, FieldProps>(
|
|
184
|
+
function Field(
|
|
185
|
+
{
|
|
186
|
+
label,
|
|
187
|
+
hint,
|
|
188
|
+
error,
|
|
189
|
+
labelHidden = false,
|
|
190
|
+
iconLeft,
|
|
191
|
+
iconRight,
|
|
192
|
+
required = false,
|
|
193
|
+
as: As = 'input',
|
|
194
|
+
inputProps,
|
|
195
|
+
textareaProps,
|
|
196
|
+
selectProps,
|
|
197
|
+
children,
|
|
198
|
+
className,
|
|
199
|
+
},
|
|
200
|
+
ref,
|
|
201
|
+
) {
|
|
202
|
+
const inputId = useId();
|
|
203
|
+
const hintId = useId();
|
|
204
|
+
const errorId = useId();
|
|
205
|
+
|
|
206
|
+
const hasError = Boolean(error);
|
|
207
|
+
const isDisabled =
|
|
208
|
+
As === 'input'
|
|
209
|
+
? Boolean(inputProps?.disabled)
|
|
210
|
+
: As === 'textarea'
|
|
211
|
+
? Boolean(textareaProps?.disabled)
|
|
212
|
+
: Boolean(selectProps?.disabled);
|
|
213
|
+
|
|
214
|
+
// aria-describedby links input to hint or error message
|
|
215
|
+
const describedBy = hasError ? errorId : hint ? hintId : undefined;
|
|
216
|
+
|
|
217
|
+
const inputStyle = buildInputStyle({
|
|
218
|
+
hasError,
|
|
219
|
+
isDisabled,
|
|
220
|
+
iconLeftPresent: Boolean(iconLeft),
|
|
221
|
+
iconRightPresent: Boolean(iconRight),
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
return (
|
|
225
|
+
<div
|
|
226
|
+
ref={ref}
|
|
227
|
+
className={className}
|
|
228
|
+
style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-2)' }}
|
|
229
|
+
>
|
|
230
|
+
{/* Label */}
|
|
231
|
+
<label
|
|
232
|
+
htmlFor={inputId}
|
|
233
|
+
style={{
|
|
234
|
+
display: 'flex',
|
|
235
|
+
alignItems: 'center',
|
|
236
|
+
gap: 'var(--space-1)',
|
|
237
|
+
fontSize: 'var(--type-small)',
|
|
238
|
+
lineHeight: 'var(--type-small-lh)',
|
|
239
|
+
fontWeight: 500,
|
|
240
|
+
color: 'var(--color-text-primary)',
|
|
241
|
+
}}
|
|
242
|
+
>
|
|
243
|
+
{labelHidden ? (
|
|
244
|
+
<span className="sr-only">{label}</span>
|
|
245
|
+
) : (
|
|
246
|
+
<span>{label}</span>
|
|
247
|
+
)}
|
|
248
|
+
{required && (
|
|
249
|
+
<span
|
|
250
|
+
aria-hidden="true"
|
|
251
|
+
style={{ color: 'var(--color-danger)', marginLeft: 'var(--space-px)' }}
|
|
252
|
+
>
|
|
253
|
+
*
|
|
254
|
+
</span>
|
|
255
|
+
)}
|
|
256
|
+
</label>
|
|
257
|
+
|
|
258
|
+
{/* Input wrapper (relative for icon slots) */}
|
|
259
|
+
<div style={{ position: 'relative' }}>
|
|
260
|
+
{iconLeft && <IconSlot side="left">{iconLeft}</IconSlot>}
|
|
261
|
+
|
|
262
|
+
{As === 'textarea' ? (
|
|
263
|
+
<textarea
|
|
264
|
+
id={inputId}
|
|
265
|
+
aria-required={required || undefined}
|
|
266
|
+
aria-invalid={hasError || undefined}
|
|
267
|
+
aria-describedby={describedBy}
|
|
268
|
+
disabled={isDisabled}
|
|
269
|
+
{...textareaProps}
|
|
270
|
+
style={{
|
|
271
|
+
...inputStyle,
|
|
272
|
+
minHeight: '80px',
|
|
273
|
+
resize: 'vertical',
|
|
274
|
+
cursor: isDisabled ? 'not-allowed' : 'auto',
|
|
275
|
+
...textareaProps?.style,
|
|
276
|
+
}}
|
|
277
|
+
/>
|
|
278
|
+
) : As === 'select' ? (
|
|
279
|
+
<select
|
|
280
|
+
id={inputId}
|
|
281
|
+
aria-required={required || undefined}
|
|
282
|
+
aria-invalid={hasError || undefined}
|
|
283
|
+
aria-describedby={describedBy}
|
|
284
|
+
disabled={isDisabled}
|
|
285
|
+
{...selectProps}
|
|
286
|
+
style={{
|
|
287
|
+
...inputStyle,
|
|
288
|
+
appearance: 'none',
|
|
289
|
+
cursor: isDisabled ? 'not-allowed' : 'pointer',
|
|
290
|
+
paddingRight: '36px',
|
|
291
|
+
...selectProps?.style,
|
|
292
|
+
}}
|
|
293
|
+
>
|
|
294
|
+
{children}
|
|
295
|
+
</select>
|
|
296
|
+
) : (
|
|
297
|
+
<input
|
|
298
|
+
id={inputId}
|
|
299
|
+
aria-required={required || undefined}
|
|
300
|
+
aria-invalid={hasError || undefined}
|
|
301
|
+
aria-describedby={describedBy}
|
|
302
|
+
disabled={isDisabled}
|
|
303
|
+
{...inputProps}
|
|
304
|
+
style={{
|
|
305
|
+
...inputStyle,
|
|
306
|
+
...inputProps?.style,
|
|
307
|
+
}}
|
|
308
|
+
/>
|
|
309
|
+
)}
|
|
310
|
+
|
|
311
|
+
{iconRight && <IconSlot side="right">{iconRight}</IconSlot>}
|
|
312
|
+
</div>
|
|
313
|
+
|
|
314
|
+
{/* Error message (role=alert for immediate SR announcement) */}
|
|
315
|
+
{hasError && (
|
|
316
|
+
<p
|
|
317
|
+
id={errorId}
|
|
318
|
+
role="alert"
|
|
319
|
+
style={{
|
|
320
|
+
fontSize: 'var(--type-caption)',
|
|
321
|
+
lineHeight: 'var(--type-caption-lh)',
|
|
322
|
+
color: 'var(--color-danger)',
|
|
323
|
+
margin: 0,
|
|
324
|
+
}}
|
|
325
|
+
>
|
|
326
|
+
{error}
|
|
327
|
+
</p>
|
|
328
|
+
)}
|
|
329
|
+
|
|
330
|
+
{/* Hint (shown when no error) */}
|
|
331
|
+
{!hasError && hint && (
|
|
332
|
+
<p
|
|
333
|
+
id={hintId}
|
|
334
|
+
style={{
|
|
335
|
+
fontSize: 'var(--type-caption)',
|
|
336
|
+
lineHeight: 'var(--type-caption-lh)',
|
|
337
|
+
color: 'var(--color-text-muted)',
|
|
338
|
+
margin: 0,
|
|
339
|
+
}}
|
|
340
|
+
>
|
|
341
|
+
{hint}
|
|
342
|
+
</p>
|
|
343
|
+
)}
|
|
344
|
+
</div>
|
|
345
|
+
);
|
|
346
|
+
},
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
Field.displayName = 'Field';
|
|
350
|
+
|
|
351
|
+
export default Field;
|