fix(web): label interactive controls
This commit is contained in:
parent
16dddcb728
commit
01c2d31514
@ -100,8 +100,8 @@ export default function SettingsPage() {
|
||||
{error && <div style={{ color: "var(--nl-danger)", fontSize: "var(--nl-fs-sm)" }}>{error}</div>}
|
||||
{success && <div style={{ color: "var(--nl-status-success)", fontSize: "var(--nl-fs-sm)" }}>{success}</div>}
|
||||
<form onSubmit={handleChangePassword} style={{ display: "grid", gap: "var(--nl-space-3)" }}>
|
||||
<input className="input-shell" type="password" placeholder="Current password" value={currentPassword} onChange={(e) => setCurrentPassword(e.target.value)} required />
|
||||
<input className="input-shell" type="password" placeholder="New password" minLength={8} value={newPassword} onChange={(e) => setNewPassword(e.target.value)} required />
|
||||
<input className="input-shell" type="password" placeholder="Current password" aria-label="Current password" value={currentPassword} onChange={(e) => setCurrentPassword(e.target.value)} required />
|
||||
<input className="input-shell" type="password" placeholder="New password" aria-label="New password" minLength={8} value={newPassword} onChange={(e) => setNewPassword(e.target.value)} required />
|
||||
<button type="submit" disabled={isLoading} style={{ padding: "8px 16px", background: "var(--nl-accent-primary)", color: "var(--nl-on-accent)", border: "none", borderRadius: "var(--nl-radius-sm)", fontWeight: 600, justifySelf: "start" }}>
|
||||
{isLoading ? "Updating…" : "Update password"}
|
||||
</button>
|
||||
@ -176,15 +176,15 @@ MCP API base: ${MCP_SERVER_URL}
|
||||
<section className="surface-card" style={{ padding: "var(--nl-space-5)", display: "grid", gap: "var(--nl-space-3)", marginTop: "var(--nl-space-4)" }}>
|
||||
<strong>Send feedback</strong>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "140px 1fr", gap: "var(--nl-space-3)" }}>
|
||||
<select className="input-shell" value={feedbackType} onChange={(e) => setFeedbackType(e.target.value as typeof feedbackType)}>
|
||||
<select className="input-shell" value={feedbackType} onChange={(e) => setFeedbackType(e.target.value as typeof feedbackType)} aria-label="Feedback type">
|
||||
<option value="bug">Bug</option>
|
||||
<option value="feature">Feature request</option>
|
||||
<option value="praise">Praise</option>
|
||||
<option value="other">Other</option>
|
||||
</select>
|
||||
<input className="input-shell" placeholder="Title" value={feedbackTitle} onChange={(e) => setFeedbackTitle(e.target.value)} />
|
||||
<input className="input-shell" placeholder="Title" aria-label="Feedback title" value={feedbackTitle} onChange={(e) => setFeedbackTitle(e.target.value)} />
|
||||
</div>
|
||||
<textarea className="input-shell" placeholder="Details (optional)" rows={3} value={feedbackBody} onChange={(e) => setFeedbackBody(e.target.value)} style={{ resize: "vertical" }} />
|
||||
<textarea className="input-shell" placeholder="Details (optional)" aria-label="Feedback details" rows={3} value={feedbackBody} onChange={(e) => setFeedbackBody(e.target.value)} style={{ resize: "vertical" }} />
|
||||
<button
|
||||
onClick={handleSubmitFeedback}
|
||||
disabled={submittingFeedback || !feedbackTitle.trim()}
|
||||
|
||||
@ -99,6 +99,7 @@ export function ArtifactPanel({
|
||||
value={title}
|
||||
onChange={(event) => setTitle(event.target.value)}
|
||||
placeholder="Artifact title"
|
||||
aria-label="Artifact title"
|
||||
/>
|
||||
</div>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "minmax(140px, 180px) minmax(0, 1fr)", gap: "var(--nl-space-3)" }}>
|
||||
@ -106,6 +107,7 @@ export function ArtifactPanel({
|
||||
className="input-shell"
|
||||
value={artifactType}
|
||||
onChange={(event) => setArtifactType(event.target.value as "file" | "summary" | "extraction" | "citation" | "export")}
|
||||
aria-label="Artifact type"
|
||||
>
|
||||
<option value="file">file</option>
|
||||
<option value="summary">summary</option>
|
||||
@ -118,6 +120,7 @@ export function ArtifactPanel({
|
||||
value={blobPath}
|
||||
onChange={(event) => setBlobPath(event.target.value)}
|
||||
placeholder="Optional blob path"
|
||||
aria-label="Artifact blob path"
|
||||
/>
|
||||
</div>
|
||||
<textarea
|
||||
@ -125,6 +128,7 @@ export function ArtifactPanel({
|
||||
value={description}
|
||||
onChange={(event) => setDescription(event.target.value)}
|
||||
placeholder="Description"
|
||||
aria-label="Artifact description"
|
||||
style={{ minHeight: 96, resize: "vertical" }}
|
||||
/>
|
||||
<div style={{ display: "flex", justifyContent: "flex-end", gap: "var(--nl-space-2)" }}>
|
||||
@ -173,6 +177,7 @@ export function ArtifactPanel({
|
||||
void handleOpenArtifact(artifact);
|
||||
}}
|
||||
disabled={openingId === artifact.id}
|
||||
aria-label={`Open artifact: ${artifact.name}`}
|
||||
>
|
||||
{openingId === artifact.id ? "Opening…" : "Open"}
|
||||
</button>
|
||||
|
||||
@ -94,6 +94,7 @@ export function CreateNoteModal({ workspaces, defaultWorkspaceId, onCreated, onC
|
||||
<span style={{ fontWeight: 600, fontSize: "0.875rem" }}>Template</span>
|
||||
<select
|
||||
className="input"
|
||||
aria-label="Note template"
|
||||
value={templateId}
|
||||
onChange={(e) => {
|
||||
const v = e.target.value;
|
||||
@ -115,7 +116,7 @@ export function CreateNoteModal({ workspaces, defaultWorkspaceId, onCreated, onC
|
||||
|
||||
<label style={{ display: "grid", gap: "var(--nl-space-1)" }}>
|
||||
<span style={{ fontWeight: 600, fontSize: "0.875rem" }}>Workspace</span>
|
||||
<select value={workspaceId} onChange={(e) => setWorkspaceId(e.target.value)} className="input">
|
||||
<select value={workspaceId} onChange={(e) => setWorkspaceId(e.target.value)} className="input" aria-label="Workspace">
|
||||
{workspaces.map((ws) => (
|
||||
<option key={ws.id} value={ws.id}>
|
||||
{ws.name}
|
||||
@ -132,6 +133,7 @@ export function CreateNoteModal({ workspaces, defaultWorkspaceId, onCreated, onC
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
placeholder="Note title"
|
||||
aria-label="Note title"
|
||||
autoFocus
|
||||
/>
|
||||
</label>
|
||||
@ -143,6 +145,7 @@ export function CreateNoteModal({ workspaces, defaultWorkspaceId, onCreated, onC
|
||||
value={body}
|
||||
onChange={(e) => setBody(e.target.value)}
|
||||
placeholder="Note content..."
|
||||
aria-label="Note body"
|
||||
rows={6}
|
||||
style={{ resize: "vertical" }}
|
||||
/>
|
||||
@ -156,6 +159,7 @@ export function CreateNoteModal({ workspaces, defaultWorkspaceId, onCreated, onC
|
||||
value={tags}
|
||||
onChange={(e) => setTags(e.target.value)}
|
||||
placeholder="launch, meeting, review"
|
||||
aria-label="Note tags"
|
||||
/>
|
||||
</label>
|
||||
|
||||
|
||||
@ -51,12 +51,12 @@ export function CreateWorkspaceModal({ onCreated, onClose }: Props) {
|
||||
|
||||
<label style={{ display: "grid", gap: 6 }}>
|
||||
<span style={{ fontWeight: 600, fontSize: "var(--nl-fs-sm)" }}>Name</span>
|
||||
<input className="input-shell" type="text" required value={name} onChange={(e) => setName(e.target.value)} autoFocus />
|
||||
<input className="input-shell" type="text" required value={name} onChange={(e) => setName(e.target.value)} aria-label="Workspace name" autoFocus />
|
||||
</label>
|
||||
|
||||
<label style={{ display: "grid", gap: 6 }}>
|
||||
<span style={{ fontWeight: 600, fontSize: "var(--nl-fs-sm)" }}>Description</span>
|
||||
<textarea className="input-shell" value={description} onChange={(e) => setDescription(e.target.value)} rows={3} style={{ resize: "vertical" }} />
|
||||
<textarea className="input-shell" value={description} onChange={(e) => setDescription(e.target.value)} rows={3} style={{ resize: "vertical" }} aria-label="Workspace description" />
|
||||
</label>
|
||||
|
||||
<div style={{ display: "flex", justifyContent: "flex-end", gap: "var(--nl-space-3)" }}>
|
||||
|
||||
@ -97,6 +97,7 @@ export function LinkNoteModal({ noteId, workspaceId, existingLinkedIds, onLinked
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder="Search notes..."
|
||||
aria-label="Search notes to link"
|
||||
style={{ flex: 1 }}
|
||||
autoFocus
|
||||
/>
|
||||
@ -123,6 +124,7 @@ export function LinkNoteModal({ noteId, workspaceId, existingLinkedIds, onLinked
|
||||
border: selectedId === note.id ? "2px solid var(--nl-accent-primary)" : "2px solid transparent",
|
||||
}}
|
||||
onClick={() => setSelectedId(note.id)}
|
||||
aria-label={`Select note: ${note.title}`}
|
||||
>
|
||||
<div style={{ fontWeight: 600 }}>{note.title}</div>
|
||||
<div style={{ color: "var(--nl-text-secondary)", fontSize: "0.875rem" }}>{note.excerpt}</div>
|
||||
@ -134,7 +136,7 @@ export function LinkNoteModal({ noteId, workspaceId, existingLinkedIds, onLinked
|
||||
{selectedId && (
|
||||
<label style={{ display: "grid", gap: "var(--nl-space-1)" }}>
|
||||
<span style={{ fontWeight: 600, fontSize: "0.875rem" }}>Relationship type</span>
|
||||
<select value={relationshipType} onChange={(e) => setRelationshipType(e.target.value)} className="input">
|
||||
<select value={relationshipType} onChange={(e) => setRelationshipType(e.target.value)} className="input" aria-label="Relationship type">
|
||||
{RELATIONSHIP_TYPES.map((rt) => (
|
||||
<option key={rt} value={rt}>
|
||||
{rt.replace("_", " ")}
|
||||
|
||||
@ -25,9 +25,9 @@ const TOOLBAR_BTN_ACTIVE: React.CSSProperties = {
|
||||
color: "var(--nl-text-primary)",
|
||||
};
|
||||
|
||||
function ToolbarButton({ active, onClick, label }: { active: boolean; onClick: () => void; label: string }) {
|
||||
function ToolbarButton({ active, onClick, label, ariaLabel }: { active: boolean; onClick: () => void; label: string; ariaLabel: string }) {
|
||||
return (
|
||||
<button type="button" style={active ? TOOLBAR_BTN_ACTIVE : TOOLBAR_BTN} onClick={onClick} title={label}>
|
||||
<button type="button" style={active ? TOOLBAR_BTN_ACTIVE : TOOLBAR_BTN} onClick={onClick} title={label} aria-label={ariaLabel}>
|
||||
{label}
|
||||
</button>
|
||||
);
|
||||
@ -194,18 +194,18 @@ export function NoteEditor({
|
||||
|
||||
{editor && (
|
||||
<div style={{ display: "flex", gap: 4, flexWrap: "wrap", padding: "4px 0" }}>
|
||||
<ToolbarButton active={editor.isActive("bold")} onClick={() => editor.chain().focus().toggleBold().run()} label="B" />
|
||||
<ToolbarButton active={editor.isActive("italic")} onClick={() => editor.chain().focus().toggleItalic().run()} label="I" />
|
||||
<ToolbarButton active={editor.isActive("strike")} onClick={() => editor.chain().focus().toggleStrike().run()} label="S" />
|
||||
<ToolbarButton active={editor.isActive("bold")} onClick={() => editor.chain().focus().toggleBold().run()} label="B" ariaLabel="Bold" />
|
||||
<ToolbarButton active={editor.isActive("italic")} onClick={() => editor.chain().focus().toggleItalic().run()} label="I" ariaLabel="Italic" />
|
||||
<ToolbarButton active={editor.isActive("strike")} onClick={() => editor.chain().focus().toggleStrike().run()} label="S" ariaLabel="Strikethrough" />
|
||||
<span style={{ width: 1, background: "var(--nl-border-default)", margin: "0 4px" }} />
|
||||
<ToolbarButton active={editor.isActive("heading", { level: 1 })} onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()} label="H1" />
|
||||
<ToolbarButton active={editor.isActive("heading", { level: 2 })} onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()} label="H2" />
|
||||
<ToolbarButton active={editor.isActive("heading", { level: 3 })} onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()} label="H3" />
|
||||
<ToolbarButton active={editor.isActive("heading", { level: 1 })} onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()} label="H1" ariaLabel="Heading level 1" />
|
||||
<ToolbarButton active={editor.isActive("heading", { level: 2 })} onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()} label="H2" ariaLabel="Heading level 2" />
|
||||
<ToolbarButton active={editor.isActive("heading", { level: 3 })} onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()} label="H3" ariaLabel="Heading level 3" />
|
||||
<span style={{ width: 1, background: "var(--nl-border-default)", margin: "0 4px" }} />
|
||||
<ToolbarButton active={editor.isActive("bulletList")} onClick={() => editor.chain().focus().toggleBulletList().run()} label="•" />
|
||||
<ToolbarButton active={editor.isActive("orderedList")} onClick={() => editor.chain().focus().toggleOrderedList().run()} label="1." />
|
||||
<ToolbarButton active={editor.isActive("codeBlock")} onClick={() => editor.chain().focus().toggleCodeBlock().run()} label="<>" />
|
||||
<ToolbarButton active={editor.isActive("blockquote")} onClick={() => editor.chain().focus().toggleBlockquote().run()} label="❝" />
|
||||
<ToolbarButton active={editor.isActive("bulletList")} onClick={() => editor.chain().focus().toggleBulletList().run()} label="•" ariaLabel="Bullet list" />
|
||||
<ToolbarButton active={editor.isActive("orderedList")} onClick={() => editor.chain().focus().toggleOrderedList().run()} label="1." ariaLabel="Numbered list" />
|
||||
<ToolbarButton active={editor.isActive("codeBlock")} onClick={() => editor.chain().focus().toggleCodeBlock().run()} label="<>" ariaLabel="Code block" />
|
||||
<ToolbarButton active={editor.isActive("blockquote")} onClick={() => editor.chain().focus().toggleBlockquote().run()} label="❝" ariaLabel="Block quote" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -244,7 +244,7 @@ export function NoteEditor({
|
||||
{explainResult && (
|
||||
<div style={{ background: "var(--nl-surface-card)", border: "1px solid var(--nl-border-default)", borderRadius: "var(--nl-radius-md)", padding: "var(--nl-space-3)", fontSize: "var(--nl-fs-sm)", color: "var(--nl-text-secondary)", display: "flex", gap: 8, alignItems: "flex-start" }}>
|
||||
<div style={{ flex: 1 }}>{explainResult}</div>
|
||||
<button type="button" style={{ ...TOOLBAR_BTN, fontSize: 12 }} onClick={() => setExplainResult(null)}>✕</button>
|
||||
<button type="button" style={{ ...TOOLBAR_BTN, fontSize: 12 }} onClick={() => setExplainResult(null)} aria-label="Dismiss explanation">✕</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@ -84,7 +84,7 @@ export function SurveyBanner() {
|
||||
<span><strong>{survey.title}</strong> — Quick survey ({survey.questions.length} questions)</span>
|
||||
<div style={{ display: "flex", gap: "var(--nl-space-3)" }}>
|
||||
<button onClick={handleStart} style={{ background: "var(--nl-accent-primary)", color: "var(--nl-on-accent)", border: "none", borderRadius: "var(--nl-radius-sm)", padding: "4px 12px", fontWeight: 600, cursor: "pointer" }}>Start</button>
|
||||
<button onClick={dismiss} style={{ background: "none", border: "none", color: "var(--nl-text-secondary)", cursor: "pointer" }}>×</button>
|
||||
<button onClick={dismiss} aria-label="Dismiss survey" style={{ background: "none", border: "none", color: "var(--nl-text-secondary)", cursor: "pointer" }}>×</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -125,6 +125,7 @@ export function SurveyBanner() {
|
||||
placeholder="Your answer…"
|
||||
value={getTextValue()}
|
||||
onChange={(e) => handleAnswer(question, e.target.value)}
|
||||
aria-label="Survey answer"
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -134,6 +135,7 @@ export function SurveyBanner() {
|
||||
<button
|
||||
key={n}
|
||||
onClick={() => handleAnswer(question, String(n))}
|
||||
aria-label={`Rate ${n}`}
|
||||
style={{
|
||||
width: 36, height: 36,
|
||||
borderRadius: "var(--nl-radius-sm)",
|
||||
@ -155,6 +157,7 @@ export function SurveyBanner() {
|
||||
<button
|
||||
key={opt.id}
|
||||
onClick={() => handleAnswer(question, opt.id)}
|
||||
aria-label={`Choose ${opt.text}`}
|
||||
style={{
|
||||
padding: "4px 12px",
|
||||
borderRadius: "var(--nl-radius-sm)",
|
||||
|
||||
@ -51,6 +51,7 @@ export function TaskReviewPanel({
|
||||
value={description}
|
||||
onChange={(event) => setDescription(event.target.value)}
|
||||
placeholder="Description"
|
||||
aria-label="Task description"
|
||||
style={{ minHeight: 96, resize: "vertical" }}
|
||||
/>
|
||||
<div style={{ display: "flex", justifyContent: "flex-end" }}>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user