main.jsx: galería de servicios y revisión en bloque
Composición completa y nativa de Gallery/Card/Checkbox/Progress con un alcance de selección derivado y confirmación local.
import React, { useState } from "react";
import { createRoot } from "react-dom/client";
import {
Alert,
Button,
Card,
CardBody,
CardTitle,
Checkbox,
Gallery,
GalleryItem,
Label,
Progress,
SearchInput,
Switch,
Title,
} from "@patternfly/react-core";
import "@patternfly/react-core/dist/styles/base.css";
import "./patternfly.css";
const services = [
{
id: "api",
name: "Public API",
region: "eu-west",
replicas: 6,
load: 62,
status: "Healthy",
},
{
id: "jobs",
name: "Background jobs",
region: "eu-west",
replicas: 3,
load: 81,
status: "Review capacity",
},
{
id: "search",
name: "Search index",
region: "us-east",
replicas: 4,
load: 44,
status: "Healthy",
},
{
id: "events",
name: "Event stream",
region: "us-east",
replicas: 8,
load: 72,
status: "Healthy",
},
];
function App() {
const [query, setQuery] = useState(""),
[selected, setSelected] = useState([]),
[review, setReview] = useState(false),
[notice, setNotice] = useState(""),
[dark, setDark] = useState(false);
const visible = services.filter((x) =>
x.name.toLowerCase().includes(query.toLowerCase()),
);
const picked = services.filter((x) => selected.includes(x.id));
const allVisible =
visible.length > 0 && visible.every((x) => selected.includes(x.id));
const someVisible = visible.some((x) => selected.includes(x.id));
function choose(id, checked) {
setSelected((v) =>
checked ? [...new Set([...v, id])] : v.filter((x) => x !== id),
);
setReview(false);
}
function selectVisible(checked) {
setSelected((v) =>
checked
? [...new Set([...v, ...visible.map((x) => x.id)])]
: v.filter((id) => !visible.some((x) => x.id === id)),
);
setReview(false);
}
return (
<main className="pf-example">
<header>
<div>
<span className="overline">PATTERNFLY · SERVICE OPERATIONS</span>
<Title headingLevel="h1" size="2xl">
Plan a maintenance review
</Title>
<p>
Select services and inspect the combined scope before creating a
local plan.
</p>
</div>
<Switch
id="dark-theme"
label="Dark theme"
isChecked={dark}
onChange={(_, checked) => {
setDark(checked);
document.documentElement.classList.toggle(
"pf-v6-theme-dark",
checked,
);
}}
/>
</header>
<div className="service-toolbar">
<SearchInput
aria-label="Find a service"
placeholder="Find a service"
value={query}
onChange={(_, value) => setQuery(value)}
onClear={() => setQuery("")}
/>
<Checkbox
id="select-visible"
label="Select visible"
isChecked={allVisible ? true : someVisible ? null : false}
isDisabled={!visible.length}
onChange={(_, checked) => selectVisible(checked)}
/>
<span aria-live="polite">{selected.length} selected</span>
<Button isDisabled={!selected.length} onClick={() => setReview(true)}>
Review selection
</Button>
</div>
<div className="service-layout">
<Gallery hasGutter minWidths={{ default: "230px" }}>
{visible.map((s) => (
<GalleryItem key={s.id}>
<Card>
<CardTitle>
<Checkbox
id={"select-" + s.id}
label={s.name}
isChecked={selected.includes(s.id)}
onChange={(_, checked) => choose(s.id, checked)}
/>
</CardTitle>
<CardBody>
<div className="service-meta">
<Label color={s.status === "Healthy" ? "green" : "orange"}>
{s.status}
</Label>
<span>{s.region}</span>
</div>
<Progress
value={s.load}
title="Sample utilization"
measureLocation="outside"
/>
<dl>
<dt>Replicas</dt>
<dd>{s.replicas}</dd>
<dt>Service ID</dt>
<dd>
<code>{s.id}</code>
</dd>
</dl>
</CardBody>
</Card>
</GalleryItem>
))}
</Gallery>
<aside>
<Card>
<CardTitle>Maintenance scope</CardTitle>
<CardBody>
<p>
{picked.length} services ·{" "}
{picked.reduce((n, s) => n + s.replicas, 0)} replicas
</p>
{picked.length ? (
<ul>
{picked.map((s) => (
<li key={s.id}>
{s.name}
<span>{s.region}</span>
</li>
))}
</ul>
) : (
<p className="muted">Select a service to inspect its scope.</p>
)}
{review && (
<div className="review">
<Alert
isInline
variant="info"
title="Review the selected services"
>
This is a fictional plan. No workload will be restarted.
</Alert>
<Button
onClick={() => {
setNotice(
`Local plan includes ${picked.length} services and ${picked.reduce((n, s) => n + s.replicas, 0)} replicas. No operation was scheduled.`,
);
setReview(false);
}}
>
Confirm local plan
</Button>
</div>
)}
</CardBody>
</Card>
</aside>
</div>
{!visible.length && (
<Alert isInline variant="info" title="No services match">
<Button variant="link" onClick={() => setQuery("")}>
Clear search
</Button>
</Alert>
)}
{notice && (
<Alert isInline variant="success" title="Local plan recorded">
{notice}
</Alert>
)}
<footer>
Native PatternFly 6.6.1 · service data and capacity figures are
fictional, memory-only examples.
</footer>
</main>
);
}
createRoot(document.getElementById("root")).render(<App />);
- Guárdalo como src/main.jsx. Checkbox onChange recibe el evento y el valor checked; isChecked puede ser null cuando el conjunto es mixto.


