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
This commit is contained in:
@@ -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<MobileDetailViewProps> = ({
|
||||
title,
|
||||
subtitle,
|
||||
fields,
|
||||
onEdit,
|
||||
onDelete,
|
||||
onBack,
|
||||
}) => {
|
||||
const renderValue = (field: Field) => {
|
||||
if (field.value === null || field.value === undefined) {
|
||||
return <span className="text-muted">Not set</span>;
|
||||
}
|
||||
|
||||
if (field.type === "boolean") {
|
||||
return field.value ? (
|
||||
<span className="badge badge-success">Yes</span>
|
||||
) : (
|
||||
<span className="badge badge-secondary">No</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (field.type === "code" || field.type === "json") {
|
||||
return (
|
||||
<pre className="mobile-detail-code">
|
||||
{typeof field.value === "string" ? field.value : JSON.stringify(field.value, null, 2)}
|
||||
</pre>
|
||||
);
|
||||
}
|
||||
|
||||
return <span>{String(field.value)}</span>;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mobile-detail-view">
|
||||
<header className="mobile-detail-header">
|
||||
<button
|
||||
className="mobile-detail-back"
|
||||
onClick={onBack}
|
||||
type="button"
|
||||
aria-label="Go back"
|
||||
>
|
||||
<Icon name="arrow-left" size="md" />
|
||||
</button>
|
||||
<div className="mobile-detail-header-content">
|
||||
<h1 className="mobile-detail-title">{title}</h1>
|
||||
{subtitle && <p className="mobile-detail-subtitle">{subtitle}</p>}
|
||||
</div>
|
||||
<div className="mobile-detail-actions">
|
||||
<button
|
||||
className="mobile-detail-action"
|
||||
onClick={onEdit}
|
||||
type="button"
|
||||
aria-label="Edit"
|
||||
>
|
||||
<Icon name="edit" size="sm" />
|
||||
</button>
|
||||
<button
|
||||
className="mobile-detail-action mobile-detail-action-danger"
|
||||
onClick={onDelete}
|
||||
type="button"
|
||||
aria-label="Delete"
|
||||
>
|
||||
<Icon name="delete" size="sm" />
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="mobile-detail-fields">
|
||||
{fields.map((field, index) => (
|
||||
<div key={index} className="mobile-detail-field">
|
||||
<label className="mobile-detail-field-label">{field.label}</label>
|
||||
<div className="mobile-detail-field-value">{renderValue(field)}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user