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>
184 lines
4.7 KiB
TypeScript
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,
|
|
}}
|
|
>
|
|
×
|
|
</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>
|
|
</>
|
|
);
|
|
}
|