Files
stack/apps/web/src/components/widgets/WidgetConfigDialog.tsx
Jason Woltje ff5a09c3fb
All checks were successful
ci/woodpecker/push/web Pipeline was successful
feat(web): add widget config dialog and layout management controls (#499)
Co-authored-by: Jason Woltje <jason@diversecanvas.com>
Co-committed-by: Jason Woltje <jason@diversecanvas.com>
2026-02-24 01:11:47 +00:00

184 lines
4.7 KiB
TypeScript

/**
* 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,
}}
>
&times;
</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>
</>
);
}