feat(web): add widget config dialog and layout management controls (#499)
All checks were successful
ci/woodpecker/push/web Pipeline was successful
All checks were successful
ci/woodpecker/push/web Pipeline was successful
Co-authored-by: Jason Woltje <jason@diversecanvas.com> Co-committed-by: Jason Woltje <jason@diversecanvas.com>
This commit was merged in pull request #499.
This commit is contained in:
183
apps/web/src/components/widgets/WidgetConfigDialog.tsx
Normal file
183
apps/web/src/components/widgets/WidgetConfigDialog.tsx
Normal file
@@ -0,0 +1,183 @@
|
||||
/**
|
||||
* WidgetConfigDialog — Per-widget settings dialog.
|
||||
*
|
||||
* Reads configSchema from the widget definition. When the schema is empty
|
||||
* (current state for all 7 widgets), shows a placeholder message.
|
||||
* As widgets gain configSchema definitions, this dialog will render
|
||||
* appropriate form controls.
|
||||
*/
|
||||
|
||||
import { useState } from "react";
|
||||
import type { ReactElement } from "react";
|
||||
import { getWidgetByName } from "./WidgetRegistry";
|
||||
|
||||
export interface WidgetConfigDialogProps {
|
||||
widgetId: string;
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export function WidgetConfigDialog({
|
||||
widgetId,
|
||||
open,
|
||||
onClose,
|
||||
}: WidgetConfigDialogProps): ReactElement | null {
|
||||
const [hoverClose, setHoverClose] = useState(false);
|
||||
|
||||
if (!open) return null;
|
||||
|
||||
// Extract widget type from ID (format: "WidgetType-suffix")
|
||||
const widgetType = widgetId.split("-")[0] ?? "";
|
||||
const widgetDef = getWidgetByName(widgetType);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Backdrop */}
|
||||
<div
|
||||
onClick={onClose}
|
||||
style={{
|
||||
position: "fixed",
|
||||
inset: 0,
|
||||
background: "rgba(0,0,0,0.4)",
|
||||
zIndex: 999,
|
||||
}}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
{/* Dialog */}
|
||||
<div
|
||||
role="dialog"
|
||||
aria-label="Widget Settings"
|
||||
style={{
|
||||
position: "fixed",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
width: 420,
|
||||
maxWidth: "90vw",
|
||||
background: "var(--surface)",
|
||||
border: "1px solid var(--border)",
|
||||
borderRadius: "var(--r-lg)",
|
||||
boxShadow: "var(--shadow-lg)",
|
||||
zIndex: 1000,
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
padding: "16px 20px",
|
||||
borderBottom: "1px solid var(--border)",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<h2
|
||||
style={{
|
||||
fontSize: "1rem",
|
||||
fontWeight: 700,
|
||||
color: "var(--text)",
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
{widgetDef?.displayName ?? "Widget"} Settings
|
||||
</h2>
|
||||
{widgetDef?.description && (
|
||||
<p
|
||||
style={{
|
||||
fontSize: "0.78rem",
|
||||
color: "var(--muted)",
|
||||
margin: "4px 0 0",
|
||||
}}
|
||||
>
|
||||
{widgetDef.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
aria-label="Close"
|
||||
style={{
|
||||
background: "none",
|
||||
border: "none",
|
||||
color: "var(--muted)",
|
||||
cursor: "pointer",
|
||||
padding: 4,
|
||||
fontSize: "1.2rem",
|
||||
lineHeight: 1,
|
||||
}}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div style={{ padding: "20px" }}>
|
||||
<div
|
||||
style={{
|
||||
padding: "24px 16px",
|
||||
textAlign: "center",
|
||||
borderRadius: "var(--r)",
|
||||
background: "var(--surface-2)",
|
||||
}}
|
||||
>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "0.85rem",
|
||||
color: "var(--muted)",
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
No configuration options available for this widget yet.
|
||||
</p>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "0.78rem",
|
||||
color: "var(--muted)",
|
||||
margin: "8px 0 0",
|
||||
opacity: 0.7,
|
||||
}}
|
||||
>
|
||||
Widget configuration will be added in a future update.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "flex-end",
|
||||
padding: "12px 20px",
|
||||
borderTop: "1px solid var(--border)",
|
||||
}}
|
||||
>
|
||||
<button
|
||||
onClick={onClose}
|
||||
onMouseEnter={(): void => {
|
||||
setHoverClose(true);
|
||||
}}
|
||||
onMouseLeave={(): void => {
|
||||
setHoverClose(false);
|
||||
}}
|
||||
style={{
|
||||
padding: "6px 16px",
|
||||
borderRadius: "var(--r)",
|
||||
border: "1px solid var(--border)",
|
||||
background: hoverClose ? "var(--surface-2)" : "transparent",
|
||||
color: "var(--text-2)",
|
||||
fontSize: "0.83rem",
|
||||
fontWeight: 500,
|
||||
cursor: "pointer",
|
||||
transition: "background 0.12s ease",
|
||||
}}
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user