# Creating widgets Widgets are the building blocks of every OPI. Each widget type is a pair of files: a React component that handles rendering, and a definition object that registers the widget with the editor. --- ## File layout Create a new directory under `src/components/Widgets/` named after your widget: ``` src/components/Widgets/ └── MyWidget/ ├── MyWidget.ts # -> definition (registry entry) ├── MyWidgetComp.tsx # -> React component └── index.ts # -> Barrel file for exports (optional but recommended). ``` --- The order of the following steps is not mandatory, but it's recommended for a smoother development experience. The editor reads the definition file to display the widget in the palette, so writing it first allows you to test your component more easily as you build it. ## Step 1 - Write the definition file The definition file exports a `WidgetDefinition` object. From this file, the editor is able to read all metadata (label, icon, category, default properties) and render the appropriate editing menus. Defining this file first allows you to test your component easier when you start writing it. Example: ```ts // src/components/Widgets/MyWidget/MyWidget.ts import { MyWidgetComp } from "./MyWidgetComp"; import { COMMON_PROPS, PROPERTY_SCHEMAS, TEXT_PROPS } from "@src/types/widgetProperties"; import type { WidgetDefinition } from "@src/types/widgets"; import MyIcon from "@mui/icons-material/Category"; // pick any MUI icon export const MyWidget: WidgetDefinition = { component: MyWidgetComp, widgetName: "MyWidget", // unique key - used in saved .opi.json files widgetLabel: "My Widget", // display name in the editor palette widgetIcon: MyIcon, category: "Basic", // palette group defaultProperties: { label: { ...PROPERTY_SCHEMAS.label, value: "My Widget" }, ...COMMON_PROPS, // x, y, width, height, tooltip, visible ...TEXT_PROPS, // fonts, text alignment, colors pvName: PROPERTY_SCHEMAS.pvName, // add only if the widget reads a PV }, }; ``` :::{note} Creating a barrel file (`index.ts`) in the widget directory is optional but recommended for better code organization and easier imports. It allows you to centralize exports and keep the main registry file clean. ::: For convenience, some **property groups** are provided (see `widgetProperties.ts`): | Export | Properties included | | -------------- | --------------------------------------------------------------------------------------------------------------------------------- | | `COMMON_PROPS` | `x`, `y`, `width`, `height`, `tooltip`, `visible`, `borderColor`, `borderWidth`, `borderRadius`, `borderStyle`, `backgroundColor` | | `TEXT_PROPS` | `textColor`, `fontSize`, `fontFamily`, `fontBold`, `fontItalic`, `fontUnderlined`, `textHAlign`, `textVAlign` | | `PLOT_PROPS` | `pvNames`, `plotTitle`, `xAxisTitle`, `yAxisTitle`, `lineColors`, `logscaleY` | For more details and all available properties, see `PROPERTY_SCHEMAS` definition in `src/types/widgetProperties.ts`. Looking into the existing widgets in `src/components/Widgets` is also recommended to get familiarized with the structure and patterns. --- ## Step 2 - Register the widget to the palette Add a named export to the `src/components/Widgets/index.ts`, so the `WidgetRegistry` picks it up automatically: ```ts // src/components/Widgets/index.ts export { MyWidget } from "./MyWidget"; // ... existing exports ``` From now on, your widget will appear in the editor palette under its `category`, can be dragged onto the canvas, and will be serialised/deserialised using `widgetName` as the key. ## Step 3 - Writing the React component Now it's time to actually design your widget. The component should receive a single `data` prop of type `Widget` (the runtime version of `WidgetDefinition`). Use `data.editableProperties` to read property values, and `data.pvData`/`data.multiPvData` for live EPICS data (injected automatically at render time by `WidgetRenderer`). All properties defined in your definition file will be available for usage by your component. ```tsx // src/components/Widgets/MyWidget/MyWidgetComp.tsx import type { WidgetUpdate } from "@src/types/widgets"; export function MyWidgetComp({ data }: WidgetUpdate) { const label = data.editableProperties.label?.value as string; const bg = data.editableProperties.backgroundColor?.value as string; const pv = data.pvData; return (