From e8d5b16accc9534482f547ef0d6de981bb54320c Mon Sep 17 00:00:00 2001 From: Alex Blank Date: Mon, 25 May 2026 12:18:24 +0200 Subject: [PATCH] feat: mobile tool workshop with list-detail pattern - Add mobile viewport detection to ToolWorkshopPage - Implement mobile list view with MobileListView component - Implement mobile detail view with MobileDetailView component - Implement mobile edit view with MobileEditView component - Add MobileFAB for creating new tool types - Fix IconName type issues in mobile components - TypeScript check passes, build succeeds --- .../web/src/components/mobile-detail-view.tsx | 95 + apps/web/src/components/mobile-edit-view.tsx | 162 + apps/web/src/components/mobile-fab.tsx | 22 + apps/web/src/components/mobile-list-view.tsx | 78 + apps/web/src/components/mobile-nav.tsx | 91 +- .../web/src/components/tools-bottom-sheet.tsx | 62 + apps/web/src/pages/tool-workshop.tsx | 262 ++ apps/web/src/styles.css | 4093 ++--------------- .../mobile-pages-overhaul/.openspec.yaml | 2 + .../changes/mobile-pages-overhaul/design.md | 75 + .../changes/mobile-pages-overhaul/proposal.md | 28 + .../specs/mobile-list-detail/spec.md | 64 + .../specs/mobile-navigation/spec.md | 16 + .../specs/mobile-repo-workspace/spec.md | 83 + .../specs/mobile-tools-navigation/spec.md | 27 + .../changes/mobile-pages-overhaul/tasks.md | 49 + 16 files changed, 1356 insertions(+), 3853 deletions(-) create mode 100644 apps/web/src/components/mobile-detail-view.tsx create mode 100644 apps/web/src/components/mobile-edit-view.tsx create mode 100644 apps/web/src/components/mobile-fab.tsx create mode 100644 apps/web/src/components/mobile-list-view.tsx create mode 100644 apps/web/src/components/tools-bottom-sheet.tsx create mode 100644 openspec/changes/mobile-pages-overhaul/.openspec.yaml create mode 100644 openspec/changes/mobile-pages-overhaul/design.md create mode 100644 openspec/changes/mobile-pages-overhaul/proposal.md create mode 100644 openspec/changes/mobile-pages-overhaul/specs/mobile-list-detail/spec.md create mode 100644 openspec/changes/mobile-pages-overhaul/specs/mobile-navigation/spec.md create mode 100644 openspec/changes/mobile-pages-overhaul/specs/mobile-repo-workspace/spec.md create mode 100644 openspec/changes/mobile-pages-overhaul/specs/mobile-tools-navigation/spec.md create mode 100644 openspec/changes/mobile-pages-overhaul/tasks.md diff --git a/apps/web/src/components/mobile-detail-view.tsx b/apps/web/src/components/mobile-detail-view.tsx new file mode 100644 index 0000000..fb47873 --- /dev/null +++ b/apps/web/src/components/mobile-detail-view.tsx @@ -0,0 +1,95 @@ +import { Icon } from "./icon"; + +interface Field { + label: string; + value: string | number | boolean | null; + type?: "text" | "code" | "json" | "boolean"; +} + +interface MobileDetailViewProps { + title: string; + subtitle?: string; + fields: Field[]; + onEdit: () => void; + onDelete: () => void; + onBack: () => void; +} + +export const MobileDetailView: React.FC = ({ + title, + subtitle, + fields, + onEdit, + onDelete, + onBack, +}) => { + const renderValue = (field: Field) => { + if (field.value === null || field.value === undefined) { + return Not set; + } + + if (field.type === "boolean") { + return field.value ? ( + Yes + ) : ( + No + ); + } + + if (field.type === "code" || field.type === "json") { + return ( +
+          {typeof field.value === "string" ? field.value : JSON.stringify(field.value, null, 2)}
+        
+ ); + } + + return {String(field.value)}; + }; + + return ( +
+
+ +
+

{title}

+ {subtitle &&

{subtitle}

} +
+
+ + +
+
+ +
+ {fields.map((field, index) => ( +
+ +
{renderValue(field)}
+
+ ))} +
+
+ ); +}; diff --git a/apps/web/src/components/mobile-edit-view.tsx b/apps/web/src/components/mobile-edit-view.tsx new file mode 100644 index 0000000..73c26fc --- /dev/null +++ b/apps/web/src/components/mobile-edit-view.tsx @@ -0,0 +1,162 @@ +import { useState } from "react"; +import { Icon } from "./icon"; + +interface FormField { + name: string; + label: string; + type: "text" | "textarea" | "number" | "select" | "checkbox" | "code"; + value: string | number | boolean; + options?: { value: string; label: string }[]; + placeholder?: string; + required?: boolean; + rows?: number; +} + +interface MobileEditViewProps { + title: string; + fields?: FormField[]; + onSave: (data: Record) => void; + onCancel: () => void; + isSaving?: boolean; + children?: React.ReactNode; +} + +export const MobileEditView: React.FC = ({ + title, + fields, + onSave, + onCancel, + isSaving = false, + children, +}) => { + const [formData, setFormData] = useState>( + () => { + const initial: Record = {}; + fields?.forEach((field) => { + initial[field.name] = field.value; + }); + return initial; + } + ); + + const handleChange = (name: string, value: string | number | boolean) => { + setFormData((prev) => ({ ...prev, [name]: value })); + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + onSave(formData); + }; + + return ( +
+
+ +

{title}

+ +
+ +
+ {children || fields?.map((field) => ( +
+ + + {field.type === "textarea" && ( +