Components use PascalCase directories with co-located files:
src/Components/MyComponent/
├── MyComponent.tsx # Component implementation
├── MyComponent.scss # Styles (optional)
├── MyComponent.test.tsx # Unit tests
└── index.ts # Re-export (optional)
- Use PatternFly 6 components from
@patternfly/react-core - CSS utility classes use
pf-v6-*prefix (notpf-v5-*) - Import icons from
@patternfly/react-icons - Use
@patternfly/react-component-groupsfor higher-level patterns - The dashboard grid uses
@patternfly/widgetized-dashboard(prerelease)
- Atoms live in
src/state/withcamelCasenaming:{name}Atom.ts - Export the atom as a named export:
export const myAtom = atom<Type>(initialValue) - Derived atoms for computed values; write-only atoms for async side effects
- Keep atoms minimal — store only the data needed
- Custom hooks live in
src/hooks/withuseprefix:use{Name}.ts - Tests go in
src/hooks/tests/use{Name}.test.ts - Prefer hooks over direct atom access in components for complex logic
- Hooks that fetch data should handle loading/error states
Widget identifiers use "widgetType#uuid" format. Always use the helpers:
import { getWidgetIdentifier, mapWidgetDefaults, widgetIdSeparator } from '../api/dashboard-templates';
// Create: "myWidget#550e8400-..."
const id = getWidgetIdentifier('myWidget');
// Parse: ["myWidget", "550e8400-..."]
const [type, uuid] = mapWidgetDefaults(id);Widgets are registered in the backend widget mapping. Each entry defines:
{
scope: string; // Federated module scope (e.g., "insights")
module: string; // Module path (e.g., "./RhelWidget")
importName: string; // Export name (e.g., "default")
defaults: {
w: number; // Default width in columns
h: number; // Default height in rows
maxH: number; // Maximum height
minH: number; // Minimum height
};
config?: {
icon?: string; // PatternFly icon name (converted to element by ConvertWidgetMapping)
title?: string; // Display title
headerLink?: { title?: string; href?: string };
permissions?: WidgetPermission[]; // Access control
};
}Widgets with config.permissions are filtered based on user capabilities via Chrome's visibilityFunctions. Widgets the user can't access are excluded from the mapping before rendering.
| Type | Stored in backend | Used in frontend |
|---|---|---|
LayoutWithTitle |
Yes | Yes |
TemplateConfig |
Yes | Yes |
ExtendedLayoutItem |
No | Yes (adds widgetType, config, locked) |
ExtendedTemplateConfig |
No | Yes |
The frontend enriches backend data with UI-only fields. When persisting, strip back to LayoutWithTitle fields only (i, x, y, w, h, maxH, minH, title).
- All API functions live in
src/api/dashboard-templates.ts - Use native
fetch(no axios) - Errors throw
DashboardTemplatesErrorwith HTTP status - Template saves are debounced at 1500ms — don't call
patchDashboardTemplatedirectly in event handlers; use the hook'ssaveTemplatecallback
- Use SCSS files co-located with components
- Follow PatternFly spacing/layout utilities where possible
- SCSS prefix for this app:
.widgetLayout, .landing(fromfec.config.jssassPrefix) - Avoid inline styles — use SCSS classes or PatternFly utility classes