feat(web/ui5+ui7): migrate 12 components to @bytelyst/ui primitives
Finishes UI5 and kicks off UI7 by migrating the remaining form-heavy components plus the note-detail right-rail panels. Drops legacy class matches from 92 → 67 (-25) and raw interactive controls from 38 → 25 (-13). Ratchet baseline updated to the new floor. Components migrated: UI5 finish: - NoteEditor.tsx — surface-card wrapper → Card, title input → Input, Tiptap editor className updated to use border + bg classes instead of input-shell. Toolbar buttons left as raw (intentional, tightly styled icon controls). - SmartActionsPanel.tsx — result panel surface-muted → Tailwind bg-[var(--nl-surface-muted)] utility. - ArtifactPanel.tsx — section→Card, badge→Badge, all three input-shell inputs/selects/textareas→Input/Select/Textarea, surface-muted form shell + per-artifact row → Tailwind bg-utility, raw <button> Open → Button. - CommandPalette.tsx — surface-card command sheet → Tailwind layered classes, search input → Input (now ref-forwarded), kind badge → Badge. UI7 component pass: - MetadataPanel.tsx — section→Card, tag badge→Badge. - LinkedNotesPanel.tsx — section→Card, surface-muted link row → Tailwind bg-utility with hover state. - PalaceStats.tsx — section→Card, inline styles → Tailwind utilities. - ExtractedTasksPanel.tsx — surface-muted row → Tailwind. - NoteVersionsPanel.tsx — all three section/surface-card variants → Card + raw button → preserved (interactive disclosure). - Pagination.tsx — raw <button> Previous/Next → Button, surface-muted → built-in secondary variant. - TaskReviewPanel.tsx — full migration: section→Card, badge→Badge, input-shell + textarea + raw button → Input/Textarea/Button. - SurveyBanner.tsx — survey answer input-shell → Input. Adapter changes: - web/src/components/ui/Primitives.tsx — Input and Textarea now use React.forwardRef so callers like CommandPalette can attach refs. Verified: - pnpm --filter @notelett/web run typecheck: passes - pnpm --filter @notelett/web test: 96/96 still pass - pnpm run audit:ui:ratchet: at new baseline (25/67/0/0) - pnpm run audit:ui: legacy class matches now in dashboard / search / workspaces / notes-detail / palace / chat pages (UI6/UI7 page targets)
This commit is contained in:
parent
d5e857dbf7
commit
2408f43426
@ -1,7 +1,7 @@
|
||||
{
|
||||
"//": "Baseline UI drift counts captured 2026-05-23 after UI5 settings + modals slice. ui-drift-ratchet.sh compares current counts against these and FAILS if any category EXCEEDS its baseline. To lower the baseline after migrating screens, run scripts/ui-drift-ratchet.sh --update.",
|
||||
"raw_interactive_controls": 38,
|
||||
"legacy_global_surface_classes": 92,
|
||||
"//": "Baseline UI drift counts. Updated 2026-05-23T08:33:24Z by scripts/ui-drift-ratchet.sh --update. Commit alongside the migration that lowered the counts.",
|
||||
"raw_interactive_controls": 25,
|
||||
"legacy_global_surface_classes": 67,
|
||||
"hardcoded_color_literals": 0,
|
||||
"direct_bytelyst_ui_imports_outside_adapter": 0
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
import { useRef, useState } from "react";
|
||||
import { StateNotice } from "@/components/StateNotice";
|
||||
import { Button } from "@/components/ui/Primitives";
|
||||
import { Badge, Button, Card, Input, Select, Textarea } from "@/components/ui/Primitives";
|
||||
import { getArtifactReadUrl, uploadArtifact } from "@/lib/blob-client";
|
||||
import { toast } from "@/lib/toast";
|
||||
import { getEmptyState, toUserFacingState, type UserFacingState } from "@/lib/user-facing-states";
|
||||
@ -91,53 +91,46 @@ export function ArtifactPanel({
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="surface-card" style={{ padding: "var(--nl-space-5)", display: "grid", gap: "var(--nl-space-3)" }}>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", gap: "var(--nl-space-3)", alignItems: "center" }}>
|
||||
<div style={{ fontWeight: 700 }}>Artifacts</div>
|
||||
<span className="badge">blob-backed view</span>
|
||||
<Card padding="md" className="grid gap-3">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="font-bold">Artifacts</div>
|
||||
<Badge variant="info">blob-backed view</Badge>
|
||||
</div>
|
||||
<div className="surface-muted" style={{ padding: "var(--nl-space-3)", display: "grid", gap: "var(--nl-space-3)" }}>
|
||||
<div style={{ display: "grid", gap: "var(--nl-space-2)" }}>
|
||||
<label htmlFor="artifact-title" style={{ color: "var(--nl-text-secondary)" }}>
|
||||
Add artifact
|
||||
</label>
|
||||
<input
|
||||
<div className="grid gap-3 rounded-[var(--nl-radius-sm)] bg-[color:var(--nl-surface-muted)] p-3">
|
||||
<Input
|
||||
id="artifact-title"
|
||||
className="input-shell"
|
||||
label="Add artifact"
|
||||
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)" }}>
|
||||
<select
|
||||
className="input-shell"
|
||||
<div className="grid gap-3 [grid-template-columns:minmax(140px,180px)_minmax(0,1fr)]">
|
||||
<Select
|
||||
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>
|
||||
<option value="extraction">extraction</option>
|
||||
<option value="citation">citation</option>
|
||||
<option value="export">export</option>
|
||||
</select>
|
||||
<input
|
||||
className="input-shell"
|
||||
options={[
|
||||
{ value: "file", label: "file" },
|
||||
{ value: "summary", label: "summary" },
|
||||
{ value: "extraction", label: "extraction" },
|
||||
{ value: "citation", label: "citation" },
|
||||
{ value: "export", label: "export" },
|
||||
]}
|
||||
/>
|
||||
<Input
|
||||
value={blobPath}
|
||||
onChange={(event) => setBlobPath(event.target.value)}
|
||||
placeholder="Optional blob path"
|
||||
aria-label="Artifact blob path"
|
||||
/>
|
||||
</div>
|
||||
<textarea
|
||||
className="input-shell"
|
||||
<Textarea
|
||||
value={description}
|
||||
onChange={(event) => setDescription(event.target.value)}
|
||||
placeholder="Description"
|
||||
aria-label="Artifact description"
|
||||
style={{ minHeight: 96, resize: "vertical" }}
|
||||
className="min-h-[96px] resize-y"
|
||||
/>
|
||||
<div style={{ display: "flex", justifyContent: "flex-end", gap: "var(--nl-space-2)" }}>
|
||||
<input ref={fileInputRef} type="file" hidden onChange={handleFileUpload} />
|
||||
@ -171,17 +164,18 @@ export function ArtifactPanel({
|
||||
<StateNotice state={getEmptyState("blob", "artifacts")} compact />
|
||||
) : null}
|
||||
{artifacts.map((artifact) => (
|
||||
<div key={artifact.id} className="surface-muted" style={{ padding: "var(--nl-space-3)", display: "flex", justifyContent: "space-between", gap: "var(--nl-space-3)", alignItems: "center" }}>
|
||||
<div style={{ display: "grid", gap: 4 }}>
|
||||
<div key={artifact.id} className="flex items-center justify-between gap-3 rounded-[var(--nl-radius-sm)] bg-[color:var(--nl-surface-muted)] p-3">
|
||||
<div className="grid gap-1">
|
||||
<strong>{artifact.name}</strong>
|
||||
<span style={{ color: "var(--nl-text-secondary)" }}>{artifact.type}</span>
|
||||
<span className="text-[color:var(--nl-text-secondary)]">{artifact.type}</span>
|
||||
</div>
|
||||
<div style={{ display: "flex", gap: "var(--nl-space-3)", alignItems: "center" }}>
|
||||
<span style={{ color: "var(--nl-text-secondary)" }}>{artifact.status}</span>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-[color:var(--nl-text-secondary)]">{artifact.status}</span>
|
||||
{artifact.blobPath ? (
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
className="badge"
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
void handleOpenArtifact(artifact);
|
||||
}}
|
||||
@ -189,11 +183,11 @@ export function ArtifactPanel({
|
||||
aria-label={`Open artifact: ${artifact.name}`}
|
||||
>
|
||||
{openingId === artifact.id ? "Opening…" : "Open"}
|
||||
</button>
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { Badge, Input } from "@/components/ui/Primitives";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useKeyboardShortcuts } from "@/lib/use-keyboard-shortcuts";
|
||||
@ -176,25 +177,21 @@ export function CommandPalette() {
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
<div
|
||||
className="surface-card"
|
||||
className="grid overflow-hidden rounded-[var(--nl-radius-md)] border border-[color:var(--nl-border-default)] bg-[color:var(--nl-surface-card)] [grid-template-rows:auto_1fr]"
|
||||
style={{
|
||||
width: "min(560px, 100%)",
|
||||
maxHeight: "70vh",
|
||||
overflow: "hidden",
|
||||
display: "grid",
|
||||
gridTemplateRows: "auto 1fr",
|
||||
boxShadow: "var(--nl-command-shadow)",
|
||||
}}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div style={{ padding: "var(--nl-space-3) var(--nl-space-4)", borderBottom: "1px solid var(--nl-border-default)" }}>
|
||||
<input
|
||||
<Input
|
||||
ref={inputRef}
|
||||
className="input-shell"
|
||||
placeholder="Jump to note, workspace, or action…"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
style={{ width: "100%", fontSize: "var(--nl-fs-md)" }}
|
||||
className="w-full"
|
||||
/>
|
||||
<div style={{ marginTop: 8, fontSize: "var(--nl-fs-sm)", color: "var(--nl-text-secondary)" }}>
|
||||
<kbd style={{ opacity: 0.8 }}>↑</kbd> <kbd style={{ opacity: 0.8 }}>↓</kbd> navigate · <kbd style={{ opacity: 0.8 }}>↵</kbd> open ·{" "}
|
||||
@ -226,9 +223,9 @@ export function CommandPalette() {
|
||||
>
|
||||
<div style={{ fontWeight: 600 }}>{item.label}</div>
|
||||
<div style={{ fontSize: "var(--nl-fs-sm)", color: "var(--nl-text-secondary)" }}>
|
||||
<span className="badge" style={{ marginRight: 8 }}>
|
||||
<Badge variant="neutral" className="mr-2">
|
||||
{item.kind}
|
||||
</span>
|
||||
</Badge>
|
||||
{item.hint}
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
@ -107,7 +107,7 @@ export function ExtractedTasksPanel({
|
||||
{proposals.length === 0 ? null : (
|
||||
<ul style={{ listStyle: "none", margin: 0, padding: 0, display: "grid", gap: "var(--nl-space-2)" }}>
|
||||
{proposals.map((task) => (
|
||||
<li key={task.id} className="surface-muted" style={{ padding: "var(--nl-space-3)", display: "flex", gap: "var(--nl-space-2)", flexWrap: "wrap", alignItems: "center", justifyContent: "space-between" }}>
|
||||
<li key={task.id} className="flex flex-wrap items-center justify-between gap-2 rounded-[var(--nl-radius-sm)] bg-[color:var(--nl-surface-muted)] p-3">
|
||||
<span>{task.title}</span>
|
||||
<span style={{ display: "flex", gap: 8 }}>
|
||||
<Button
|
||||
|
||||
@ -1,16 +1,21 @@
|
||||
import Link from "next/link";
|
||||
import { Card } from "@/components/ui/Primitives";
|
||||
import type { LinkedNote } from "@/lib/types";
|
||||
|
||||
export function LinkedNotesPanel({ linkedNotes }: { linkedNotes: LinkedNote[] }) {
|
||||
return (
|
||||
<section className="surface-card" style={{ padding: "var(--nl-space-5)", display: "grid", gap: "var(--nl-space-3)" }}>
|
||||
<div style={{ fontWeight: 700 }}>Linked notes</div>
|
||||
<Card padding="md" className="grid gap-3">
|
||||
<div className="font-bold">Linked notes</div>
|
||||
{linkedNotes.map((linkedNote) => (
|
||||
<Link key={linkedNote.id} href={`/notes/${linkedNote.id}`} className="surface-muted" style={{ padding: "var(--nl-space-3)", display: "grid", gap: 4 }}>
|
||||
<Link
|
||||
key={linkedNote.id}
|
||||
href={`/notes/${linkedNote.id}`}
|
||||
className="grid gap-1 rounded-[var(--nl-radius-sm)] bg-[color:var(--nl-surface-muted)] p-3 transition-colors hover:bg-[color:var(--nl-surface-elevated)]"
|
||||
>
|
||||
<strong>{linkedNote.title}</strong>
|
||||
<span style={{ color: "var(--nl-text-secondary)" }}>{linkedNote.relationship}</span>
|
||||
<span className="text-[color:var(--nl-text-secondary)]">{linkedNote.relationship}</span>
|
||||
</Link>
|
||||
))}
|
||||
</section>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,26 +1,27 @@
|
||||
import Link from "next/link";
|
||||
import { Badge, Card } from "@/components/ui/Primitives";
|
||||
import type { NoteDetail } from "@/lib/types";
|
||||
|
||||
export function MetadataPanel({ note }: { note: NoteDetail }) {
|
||||
return (
|
||||
<section className="surface-card" style={{ padding: "var(--nl-space-5)", display: "grid", gap: "var(--nl-space-3)" }}>
|
||||
<div style={{ fontWeight: 700 }}>Metadata</div>
|
||||
<div style={{ display: "grid", gap: "var(--nl-space-2)" }}>
|
||||
<div style={{ color: "var(--nl-text-secondary)" }}>Owner: {note.metadata.owner}</div>
|
||||
<div style={{ color: "var(--nl-text-secondary)" }}>Source: {note.metadata.source}</div>
|
||||
<Link href="/reviews" style={{ color: "var(--nl-text-secondary)" }}>
|
||||
<Card padding="md" className="grid gap-3">
|
||||
<div className="font-bold">Metadata</div>
|
||||
<div className="grid gap-2 text-[color:var(--nl-text-secondary)]">
|
||||
<div>Owner: {note.metadata.owner}</div>
|
||||
<div>Source: {note.metadata.source}</div>
|
||||
<Link href="/reviews" className="text-[color:var(--nl-text-secondary)]">
|
||||
Review state: {note.metadata.reviewState}
|
||||
</Link>
|
||||
<div style={{ color: "var(--nl-text-secondary)" }}>Tasks: {note.metadata.taskCount}</div>
|
||||
<div style={{ color: "var(--nl-text-secondary)" }}>Artifacts: {note.metadata.artifactCount}</div>
|
||||
<div>Tasks: {note.metadata.taskCount}</div>
|
||||
<div>Artifacts: {note.metadata.artifactCount}</div>
|
||||
</div>
|
||||
<div style={{ display: "flex", gap: "var(--nl-space-2)", flexWrap: "wrap" }}>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{note.tags.map((tag) => (
|
||||
<Link key={tag} href={`/search?q=${encodeURIComponent(tag)}`} className="badge">
|
||||
#{tag}
|
||||
<Link key={tag} href={`/search?q=${encodeURIComponent(tag)}`}>
|
||||
<Badge variant="neutral">#{tag}</Badge>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ import { useEditor, EditorContent } from "@tiptap/react";
|
||||
import StarterKit from "@tiptap/starter-kit";
|
||||
import Placeholder from "@tiptap/extension-placeholder";
|
||||
import type { NoteDetail } from "@/lib/types";
|
||||
import { Card, Input } from "@/components/ui/Primitives";
|
||||
import { useDebounce } from "@/lib/use-debounce";
|
||||
import { copilotTransform, type CopilotAction, type CopilotTone } from "@/lib/copilot-client";
|
||||
import { toast } from "@/lib/toast";
|
||||
@ -87,7 +88,7 @@ export function NoteEditor({
|
||||
content: note.body,
|
||||
editorProps: {
|
||||
attributes: {
|
||||
class: "input-shell",
|
||||
class: "notelett-tiptap rounded-[var(--nl-radius-md)] border border-[color:var(--nl-border-default)] bg-[color:var(--nl-input-bg)] p-3",
|
||||
style: "min-height:360px;outline:none;line-height:1.7;",
|
||||
},
|
||||
},
|
||||
@ -178,23 +179,20 @@ export function NoteEditor({
|
||||
);
|
||||
|
||||
return (
|
||||
<section className="surface-card" style={{ padding: "var(--nl-space-6)", display: "grid", gap: "var(--nl-space-4)" }}>
|
||||
<Card padding="lg" className="grid gap-4">
|
||||
<form
|
||||
style={{ display: "grid", gap: "var(--nl-space-4)" }}
|
||||
className="grid gap-4"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
handleSave();
|
||||
}}
|
||||
>
|
||||
<div style={{ display: "grid", gap: "var(--nl-space-2)" }}>
|
||||
<label htmlFor="note-title" style={{ color: "var(--nl-text-secondary)" }}>Title</label>
|
||||
<input
|
||||
<Input
|
||||
id="note-title"
|
||||
className="input-shell"
|
||||
label="Title"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ display: "grid", gap: "var(--nl-space-2)" }}>
|
||||
<label style={{ color: "var(--nl-text-secondary)" }}>Body</label>
|
||||
@ -278,6 +276,6 @@ export function NoteEditor({
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { Card } from "@/components/ui/Primitives";
|
||||
import { listNoteVersions, type NoteVersionRow } from "@/lib/notes-client";
|
||||
|
||||
export function NoteVersionsPanel({ noteId, workspaceId }: { noteId: string; workspaceId: string }) {
|
||||
@ -22,59 +23,42 @@ export function NoteVersionsPanel({ noteId, workspaceId }: { noteId: string; wor
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<section className="surface-card" style={{ padding: "var(--nl-space-4)", color: "var(--nl-text-secondary)" }}>
|
||||
<Card padding="sm" className="text-[color:var(--nl-text-secondary)]">
|
||||
{error}
|
||||
</section>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
if (items.length === 0) {
|
||||
return (
|
||||
<section className="surface-card" style={{ padding: "var(--nl-space-4)", color: "var(--nl-text-secondary)" }}>
|
||||
<Card padding="sm" className="text-[color:var(--nl-text-secondary)]">
|
||||
No saved versions yet. Versions are created when you edit title or body.
|
||||
</section>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="surface-card" style={{ padding: "var(--nl-space-5)", display: "grid", gap: "var(--nl-space-3)" }}>
|
||||
<div style={{ fontWeight: 700 }}>Version history</div>
|
||||
<ul style={{ listStyle: "none", margin: 0, padding: 0, display: "grid", gap: "var(--nl-space-2)" }}>
|
||||
<Card padding="md" className="grid gap-3">
|
||||
<div className="font-bold">Version history</div>
|
||||
<ul className="m-0 grid list-none gap-2 p-0">
|
||||
{items.map((v) => (
|
||||
<li key={v.id} className="surface-muted" style={{ padding: "var(--nl-space-3)" }}>
|
||||
<li key={v.id} className="rounded-[var(--nl-radius-sm)] bg-[color:var(--nl-surface-muted)] p-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setOpenId((o) => (o === v.id ? null : v.id))}
|
||||
style={{
|
||||
width: "100%",
|
||||
textAlign: "left",
|
||||
background: "none",
|
||||
border: "none",
|
||||
color: "var(--nl-text-primary)",
|
||||
cursor: "pointer",
|
||||
fontWeight: 600,
|
||||
}}
|
||||
className="w-full cursor-pointer border-0 bg-transparent text-left font-semibold text-[color:var(--nl-text-primary)]"
|
||||
>
|
||||
{new Date(v.savedAt).toLocaleString()} · {v.source} · {v.title.slice(0, 48)}
|
||||
{v.title.length > 48 ? "…" : ""}
|
||||
</button>
|
||||
{openId === v.id ? (
|
||||
<pre
|
||||
style={{
|
||||
marginTop: 8,
|
||||
whiteSpace: "pre-wrap",
|
||||
fontSize: "var(--nl-fs-sm)",
|
||||
color: "var(--nl-text-secondary)",
|
||||
maxHeight: 200,
|
||||
overflow: "auto",
|
||||
}}
|
||||
>
|
||||
<pre className="mt-2 max-h-[200px] overflow-auto whitespace-pre-wrap text-[length:var(--nl-fs-sm)] text-[color:var(--nl-text-secondary)]">
|
||||
{v.body.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim()}
|
||||
</pre>
|
||||
) : null}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@/components/ui/Primitives";
|
||||
|
||||
interface PaginationProps {
|
||||
offset: number;
|
||||
limit: number;
|
||||
@ -14,26 +16,26 @@ export function Pagination({ offset, limit, total, onPageChange }: PaginationPro
|
||||
if (totalPages <= 1) return null;
|
||||
|
||||
return (
|
||||
<div style={{ display: "flex", alignItems: "center", justifyContent: "center", gap: "var(--nl-space-3)", padding: "var(--nl-space-4) 0" }}>
|
||||
<button
|
||||
<div className="flex items-center justify-center gap-3 py-4">
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
disabled={currentPage <= 1}
|
||||
onClick={() => onPageChange(Math.max(0, offset - limit))}
|
||||
className="surface-muted"
|
||||
style={{ padding: "6px 14px", border: "none", fontSize: "var(--nl-fs-sm)" }}
|
||||
>
|
||||
Previous
|
||||
</button>
|
||||
<span style={{ fontSize: "var(--nl-fs-sm)", color: "var(--nl-text-secondary)" }}>
|
||||
</Button>
|
||||
<span className="text-[length:var(--nl-fs-sm)] text-[color:var(--nl-text-secondary)]">
|
||||
Page {currentPage} of {totalPages}
|
||||
</span>
|
||||
<button
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
disabled={currentPage >= totalPages}
|
||||
onClick={() => onPageChange(offset + limit)}
|
||||
className="surface-muted"
|
||||
style={{ padding: "6px 14px", border: "none", fontSize: "var(--nl-fs-sm)" }}
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { Card } from "@/components/ui/Primitives";
|
||||
import { getPalaceStats, type PalaceStats as PalaceStatsData } from "@/lib/palace-client";
|
||||
|
||||
export function PalaceStats() {
|
||||
@ -33,26 +34,19 @@ export function PalaceStats() {
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="surface-card" style={{ padding: "var(--nl-space-5)" }}>
|
||||
<div style={{ fontWeight: 700, fontSize: "var(--nl-text-lg)", marginBottom: "var(--nl-space-3)" }}>
|
||||
Memory Palace
|
||||
</div>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(100px, 1fr))", gap: "var(--nl-space-3)" }}>
|
||||
<Card padding="md">
|
||||
<div className="mb-3 text-[length:var(--nl-text-lg)] font-bold">Memory Palace</div>
|
||||
<div className="grid gap-3 [grid-template-columns:repeat(auto-fit,minmax(100px,1fr))]">
|
||||
{items.map(({ label, value }) => (
|
||||
<div
|
||||
key={label}
|
||||
style={{
|
||||
textAlign: "center",
|
||||
padding: "var(--nl-space-3)",
|
||||
borderRadius: "var(--nl-radius-md)",
|
||||
border: "1px solid var(--nl-border-subtle)",
|
||||
}}
|
||||
className="rounded-[var(--nl-radius-md)] border border-[color:var(--nl-border-subtle)] p-3 text-center"
|
||||
>
|
||||
<div style={{ fontSize: "1.5rem", fontWeight: 700, color: "var(--nl-accent-primary)" }}>{value}</div>
|
||||
<div style={{ fontSize: "0.75rem", color: "var(--nl-text-secondary)" }}>{label}</div>
|
||||
<div className="text-2xl font-bold text-[color:var(--nl-accent-primary)]">{value}</div>
|
||||
<div className="text-xs text-[color:var(--nl-text-secondary)]">{label}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
@ -164,7 +164,7 @@ export function SmartActionsPanel({
|
||||
|
||||
{/* Result display */}
|
||||
{result && (
|
||||
<div className="surface-muted" style={{ padding: "var(--nl-space-4)", display: "grid", gap: "var(--nl-space-3)" }}>
|
||||
<div className="grid gap-3 rounded-[var(--nl-radius-sm)] bg-[color:var(--nl-surface-muted)] p-4">
|
||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||
<strong style={{ fontSize: "var(--nl-fs-sm)" }}>Result</strong>
|
||||
<div style={{ display: "flex", gap: "var(--nl-space-2)" }}>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState, useCallback, useRef } from "react";
|
||||
import { Button } from "@/components/ui/Primitives";
|
||||
import { Button, Input } from "@/components/ui/Primitives";
|
||||
import { getSurveyClient } from "@/lib/survey-client";
|
||||
import { toast } from "@/lib/toast";
|
||||
import type { ActiveSurvey, Question, QuestionAnswer } from "@bytelyst/survey-client";
|
||||
@ -121,8 +121,7 @@ export function SurveyBanner() {
|
||||
</div>
|
||||
|
||||
{isTextType && (
|
||||
<input
|
||||
className="input-shell"
|
||||
<Input
|
||||
placeholder="Your answer…"
|
||||
value={getTextValue()}
|
||||
onChange={(e) => handleAnswer(question, e.target.value)}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { useState } from "react";
|
||||
import { Badge, Button, Card, Input, Textarea } from "@/components/ui/Primitives";
|
||||
import type { NoteTask } from "@/lib/types";
|
||||
|
||||
export function TaskReviewPanel({
|
||||
@ -28,61 +29,51 @@ export function TaskReviewPanel({
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="surface-card" style={{ padding: "var(--nl-space-5)", display: "grid", gap: "var(--nl-space-3)" }}>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", gap: "var(--nl-space-3)", alignItems: "center" }}>
|
||||
<div style={{ fontWeight: 700 }}>Task extraction review</div>
|
||||
<span className="badge">backend-backed review</span>
|
||||
<Card padding="md" className="grid gap-3">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="font-bold">Task extraction review</div>
|
||||
<Badge variant="info">backend-backed review</Badge>
|
||||
</div>
|
||||
<div className="surface-muted" style={{ padding: "var(--nl-space-3)", display: "grid", gap: "var(--nl-space-3)" }}>
|
||||
<div style={{ display: "grid", gap: "var(--nl-space-2)" }}>
|
||||
<label htmlFor="task-title" style={{ color: "var(--nl-text-secondary)" }}>
|
||||
Add task
|
||||
</label>
|
||||
<input
|
||||
<div className="grid gap-3 rounded-[var(--nl-radius-sm)] bg-[color:var(--nl-surface-muted)] p-3">
|
||||
<Input
|
||||
id="task-title"
|
||||
className="input-shell"
|
||||
label="Add task"
|
||||
value={title}
|
||||
onChange={(event) => setTitle(event.target.value)}
|
||||
placeholder="Task title"
|
||||
/>
|
||||
</div>
|
||||
<textarea
|
||||
className="input-shell"
|
||||
<Textarea
|
||||
value={description}
|
||||
onChange={(event) => setDescription(event.target.value)}
|
||||
placeholder="Description"
|
||||
aria-label="Task description"
|
||||
style={{ minHeight: 96, resize: "vertical" }}
|
||||
className="min-h-[96px] resize-y"
|
||||
/>
|
||||
<div style={{ display: "flex", justifyContent: "flex-end" }}>
|
||||
<button
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
type="button"
|
||||
disabled={isCreating}
|
||||
loading={isCreating}
|
||||
onClick={() => {
|
||||
void handleCreateTask();
|
||||
}}
|
||||
style={{
|
||||
border: "none",
|
||||
borderRadius: "var(--nl-radius-md)",
|
||||
padding: "8px 12px",
|
||||
background: "var(--nl-accent-primary)",
|
||||
color: "var(--nl-text-primary)",
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
{isCreating ? "Adding…" : "Add task"}
|
||||
</button>
|
||||
Add task
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{tasks.map((task) => (
|
||||
<div key={task.id} className="surface-muted" style={{ padding: "var(--nl-space-3)", display: "flex", justifyContent: "space-between", gap: "var(--nl-space-3)", alignItems: "center" }}>
|
||||
<div style={{ display: "grid", gap: 4 }}>
|
||||
<div
|
||||
key={task.id}
|
||||
className="flex items-center justify-between gap-3 rounded-[var(--nl-radius-sm)] bg-[color:var(--nl-surface-muted)] p-3"
|
||||
>
|
||||
<div className="grid gap-1">
|
||||
<strong>{task.title}</strong>
|
||||
<span style={{ color: "var(--nl-text-secondary)" }}>Source: {task.source}</span>
|
||||
<span className="text-[color:var(--nl-text-secondary)]">Source: {task.source}</span>
|
||||
</div>
|
||||
<span style={{ color: "var(--nl-text-secondary)" }}>{task.status}</span>
|
||||
<span className="text-[color:var(--nl-text-secondary)]">{task.status}</span>
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { forwardRef } from "react";
|
||||
import {
|
||||
AlertBanner as BytelystAlertBanner,
|
||||
FormSection as BytelystFormSection,
|
||||
@ -285,29 +286,31 @@ export function StatusBadge({ className, status, tone, ...props }: NoteLettStatu
|
||||
return <BytelystStatusBadge className={className} tone={tone ?? (status ? getStatusTone(status) : undefined)} {...props} />;
|
||||
}
|
||||
|
||||
export function Input({ className, ...props }: InputProps) {
|
||||
export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
|
||||
{ className, ...props },
|
||||
ref,
|
||||
) {
|
||||
return (
|
||||
<BytelystInput
|
||||
className={mergeClassNames(
|
||||
"rounded-[var(--nl-radius-sm)]",
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
className={mergeClassNames("rounded-[var(--nl-radius-sm)]", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export function Textarea({ className, ...props }: TextareaProps) {
|
||||
export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(function Textarea(
|
||||
{ className, ...props },
|
||||
ref,
|
||||
) {
|
||||
return (
|
||||
<BytelystTextarea
|
||||
className={mergeClassNames(
|
||||
"rounded-[var(--nl-radius-sm)]",
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
className={mergeClassNames("rounded-[var(--nl-radius-sm)]", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export function Select({ className, ...props }: SelectProps) {
|
||||
return (
|
||||
|
||||
Loading…
Reference in New Issue
Block a user