Skip to main content

apicp-ui

Build or change UI in portals/api-control-plane — pages, components, forms, dialogs, listings, app shell/navigation, theming, and MUI→Oxygen migration. Covers the Oxygen UI (@wso2/oxygen-ui) component/theming API plus this app's own rules for data access (hooks only), i18n (react-intl), routing/scope gating, and tests. Use for any .tsx/.ts work under portals/api-control-plane/src, and whenever asked how a page, listing, form, sidebar item, or theme override should be built here.

Quellinformationen

Repository
wso2/api-platform
Letzte Quellaktivität
17. September 2026 um 06:37
Erkannte Sprache von SKILL.md
Englisch
Sterne
76
Forks
122

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
apicp-ui
description
Build or change UI in portals/api-control-plane — pages, components, forms, dialogs, listings, app shell/navigation, theming, and MUI→Oxygen migration. Covers the Oxygen UI (@wso2/oxygen-ui) component/theming API plus this app's own rules for data access (hooks only), i18n (react-intl), routing/scope gating, and tests. Use for any .tsx/.ts work under portals/api-control-plane/src, and whenever asked how a page, listing, form, sidebar item, or theme override should be built here.
# UI development — api-control-plane One skill for all UI work in `portals/api-control-plane`. It replaces the four generated `oxygen-*` skills and `.claude/oxygen-ui/*.md`: everything load-bearing from those is condensed below, and the full vendor reference is still on disk (see *Where the truth lives*). ## Where the truth lives Verify before you invent. In priority order: | Question | Source | | --- | --- | | Does this component/prop/sub-component exist? | `node_modules/@wso2/oxygen-ui/dist/**/*.d.ts` — the compiled types, ground truth | | Full Oxygen component/pattern/theming reference | `node_modules/@wso2/oxygen-ui/.ai/{components,patterns,theming,migration}.md` (shipped with the package; identical to the docs previously copied into `.claude/oxygen-ui/`) | | Which icon names exist | `node_modules/@wso2/oxygen-ui-icons-react/dist/index.d.ts` → 8 brand icons + `export * from "lucide-react"` | | Data-access layer rules | `src/api/README.md` | | i18n rules | `src/i18n/README.md` | | Test conventions | `src/test/README.md` | | Layer bans, i18n lint rules | `eslint.config.js` — the restrictions are documented inline there | | Online / upstream | <https://github.com/wso2/oxygen-ui> (`packages/oxygen-ui/.ai/*.md`, `src/components/**`). No hosted docs site exists as of v0.13.1 — fetch the repo, not a guessed URL. | Vendor docs lag the package. Two known errors in them — **do not copy**: - `<Grid item xs={12}>` → this app is on `@mui/material` v9 Grid v2: `<Grid size={{ xs: 12, md: 4 }}>`, no `item`. - `HomeIcon`/`TrashIcon` naming → this codebase uses bare lucide names (`Home`, `Trash2`, `Plus`). Both aliases resolve; stay with the bare form. - They also omit components that do exist (`AppBreadcrumbs`, `PageTitle.Actions`, `PageTitle.BackButton`, `ColorSchemeSVG`). `npx @wso2/oxygen-ui init --claude` / `update --claude` regenerates `.claude/oxygen-ui/` and the `oxygen-*` skills. If someone runs it, delete the regenerated files again — this skill is the project's single entry point, and `node_modules/@wso2/oxygen-ui/.ai/` already carries the reference. ## Non-negotiables 1. **Every source file starts with the Apache-2.0 header** (`Copyright (c) 2026, WSO2 LLC.` block — copy it from any neighbouring file). 2. **All components come from `@wso2/oxygen-ui`**, never `@mui/material`. All icons from `@wso2/oxygen-ui-icons-react`, never `lucide-react`/`@mui/icons-material`. 3. **Backend access goes through a hook in `src/api/resources/<resource>`** — nothing else. ESLint blocks endpoints, queries, query keys, transport, generated types and legacy clients. 4. **No user-facing literal strings.** `<FormattedMessage>` for JSX, `intl.formatMessage` for string props. IDs are `apiControlPlane.<area>.<Module>.<slug>`. 5. **No colour, radius, blur, shadow or border literals.** Theme tokens (`bgcolor: 'background.paper'`, `color: 'text.secondary'`, `spacing` numbers) or a recipe from `src/theme/receipes.ts`. Local `sx` is for layout only. 6. **Scoped pages wrap their body in `ScopeGate`** (see *Routing, scope and navigation*). 7. **Never “resolve” a rule from `.claude/rules/*` with a `// TODO`.** Those rules (XSS/output encoding, dependency management, error handling) apply to this portal's `.ts`/`.tsx` too. Verification loop, in order: `npm run typecheck` → `npm run lint` → `npm run test` → `npm run i18n` (commit `src/i18n/messages/`, never `src/i18n/compiled/`). ## Project map ``` src/ api/ resources/<name>/{endpoints,queries,hooks}.ts + core/ (http, scope, spec, queryKeys) components/ app-wide: StateViews (Loading/Empty/Error), ConfirmDialog, Notifications, ErrorBoundary, AppLoader, ComingSoon, cards/*, common/* contexts/auth/ AuthProvider + AuthStateContext hooks/ cross-cutting hooks (ProductActivation, useDocumentTitle) i18n/ I18nProvider, useLocale, useFormatters, formats.ts, messages/ (source catalogs) navigation/ navigationRegistry.tsx (the sidebar), navigationTypes, useNavigationItems, usePageTitle (browser tab title) pages/ auth/ LoginPage, AuthCallbackPage appShell/ AppLayout, AppHeader, AppSidebar, *QuickSelector appShellPages/<feature>/ the actual pages (+ components/, utils/ per feature) routes/ paths.ts (route builders), AppRoutes.tsx, ProtectedRoute scope/ ConsoleScopeProvider/Context, ScopeGate, consoleRouteParams slots/ Slot + Hideable extension primitives theme/ receipes.ts (shared style recipes) test/ renderWithProviders, MSW server + toolkit, mock scope/auth extensions.tsx extension registration; hostPort.tsx the value handed to extensions ``` Provider order (`App.tsx`, outermost first): `I18nProvider` → `OxygenUIThemeProvider` → `NotificationProvider` → `AppQueryProvider` → `ApiClientProvider` → `ErrorBoundary` → `BrowserRouter` → `AuthProvider` → `ExtensionsProvider` → `AppRoutes`. Inside a protected route: `ConsoleScopeProvider` → `AppLayout`. Don't add a provider without a reason that names its position. --- ## Oxygen UI essentials ### Imports ```tsx import { Box, Button, Card, PageTitle, Stack, Typography } from '@wso2/oxygen-ui'; import { Plus, Search, Trash2 } from '@wso2/oxygen-ui-icons-react'; // lucide names, size={18} import { DataGrid, DatePickers, TreeView, AdapterDateFns } from '@wso2/oxygen-ui'; // MUI X namespaces ``` `@wso2/oxygen-ui` re-exports **all** of `@mui/material` plus Oxygen's own components, `styled`, `alpha`, `useTheme`, `Theme`. MUI X ships as namespaces: `<DataGrid.DataGrid …>`, `<DatePickers.DatePicker …>`, `<TreeView.SimpleTreeView>`. Charts live in `@wso2/oxygen-ui-charts-react`, which is **not installed here** — adding it is a dependency change (`.claude/rules/js-dependency-management.md`). ### Oxygen's own components (beyond MUI) | Component | Sub-components / notes | | --- | --- | | `AppShell` | `.Navbar` `.Sidebar` `.Main` `.Footer` `.NotificationPanel`; props `initialCollapsed`, `collapseOnSelectOnMobile`. Wired once in `AppLayout` — pages never touch it. | | `Header` | `.Toggle` `.Brand` `.BrandLogo` `.BrandTitle` `.Switchers` `.Actions` `.Spacer`; `minimal` hides switchers | | `Sidebar` | `.Nav` `.Category` `.CategoryLabel` `.Item` `.ItemIcon` `.ItemLabel` `.ItemBadge` `.Footer` `.User*`; props `collapsed`, `activeItem`, `expandedMenus`, `onSelect`, `onToggleExpand`. An `Item` with nested children toggles instead of navigating. | | `Footer` | `.Copyright` `.Version` `.Divider` (left) · `.Link` (right) | | `UserMenu` | `.Trigger` `.Header` `.Item` `.Logout` `.Divider` | | `NotificationPanel` | `.Header{,Icon,Title,Badge,Close}` `.Tabs` `.Actions` `.List` `.Item{,Avatar,Title,Message,Timestamp,Action}` `.EmptyState` | | `PageTitle` | `.Header` `.SubHeader` `.Avatar` `.Link` `.Actions` `.BackButton` — the standard page heading | | `PageContent` | padding/max-width wrapper; `fullWidth`. Already applied by `AppLayout`, so a page starts at its own content. | | `AppBreadcrumbs` | `items: BreadcrumbItem[]`; rendered by `AppLayout` from route scope | | `ListingTable` | `.Provider` `.Container` `.Toolbar` `.Head` `.Body` `.Footer` `.Row` `.Cell` `.SortLabel` `.RowActions` `.CellIcon` `.EmptyState` `.DensityControl`; `variant='table'\|'card'`, `density`, `striped`, `bordered`. Prefer over raw MUI `Table`. | | `Form` | `.Section` `.Header` `.Subheader` `.Body` `.Stack` `.ElementWrapper` `.Wizard` `.CardButton` `.Card{Header,Content,Actions,Media}` | | `ComplexSelect` | `.MenuItem` + `.MenuItem.Icon` / `.MenuItem.Text` — icon+text options inside a `Select` | | `SearchBar`, `SearchBarWithAdvancedFilter`, `StatCard`, `CodeBlock`, `ColorSchemeToggle`, `ColorSchemeImage`, `ColorSchemeSVG`, `ThemeSwitcher`, `NotificationBanner`, `ParticleBackground`, `Layout` (`.Navbar` `.Sidebar` `.Content` `.Header`) | single-purpose; check the `.d.ts` for props | Hooks: `useTheme`, `useThemeSwitcher`, `useThemeContent`, `useAppShell`, `useNotifications` (Oxygen's own — not this app's `src/components/Notifications`), `useHeader`, `useSidebar`, `useNotificationPanel`, `useListingTable` / `useListingTableRequired`. Utils: `formatRelativeTime`, `pxToRem`, `alpha`. ### Theming `OxygenUIThemeProvider` is mounted once, directly in `App.tsx`, with a one-entry registry (`AcrylicOrangeTheme`) declared at module scope in that same file — module scope because the provider keys a theme-resolving effect on the array's identity. Don't call the provider anywhere else, and don't wrap it in an app-level provider of your own. It needs nothing else from us: it already renders `<CssBaseline enableColorScheme />` internally, and with no `emotionCache`/`nonce` prop it falls back to `<StyledEngineProvider injectFirst>` — so app styles override Oxygen styles without a hand-rolled Emotion cache. Under a CSP, pass the provider's own `nonce` prop rather than building a cache. Three tiers, in order of preference: 1. **Theme** — global decisions owned by Oxygen (`theme.palette.*`, `theme.typography.*`, `theme.border.*`, `theme.zIndex.*`, `theme.oxygen.*` for blur/gradient/glass/syntax). 2. **Recipes** — `src/theme/receipes.ts`: `hairline(theme)`, `glassSurfaceSx(theme)`, `interactiveCardSx`, `stickyBottomBarSx(theme)`, `overlayBarShadow`. A repeated multi-property treatment goes here, once. 3. **Local `sx`** — layout only: flex, gap, grid, min/max sizing. ```tsx <Card sx={(theme) => ({ ...glassSurfaceSx(theme), display: 'flex', gap: 2 })} /> ``` Dark mode: the theme is CSS-variable based (`--oxygen-*`, `data-color-scheme` attribute), so token usage adapts for free. Read the mode with `useTheme().palette.mode` only when behaviour (not colour) must branch. --- ## Page patterns ### Page skeleton ```tsx export function ThingListPage() { // Gate the whole body, not just the JSX: out of scope the query stays disabled // and `isPending` never clears, so a loading branch would hang forever. return ( <ScopeGate prompt="Things are created and managed at the project level." requires="project" to={routes.things} > <ThingList /> </ScopeGate> ); } function ThingList() { const intl = useIntl(); const thingsQuery = useThings(); const { notify } = useNotifications(); // `isPending`, not `isLoading` — a disabled query has isLoading=false with no // data, which would flash the empty state. if (thingsQuery.isPending) return <LoadingState label="Loading things" />; if (thingsQuery.error) return <ErrorState message="Unable to load things" />; const things = thingsQuery.data?.list ?? []; return ( <> <PageTitle> <PageTitle.Header><FormattedMessage {...messages.title} /></PageTitle.Header> <PageTitle.SubHeader><FormattedMessage {...messages.subtitle} /></PageTitle.SubHeader> <PageTitle.Actions> <Button startIcon={<Plus />} variant="contained" onClick={create}> <FormattedMessage {...messages.create} /> </Button> </PageTitle.Actions> </PageTitle> {things.length === 0 ? ( <EmptyState title={intl.formatMessage(messages.emptyTitle)} description={intl.formatMessage(messages.emptyBody)} actionLabel={intl.formatMessage(messages.create)} onAction={create} /> ) : ( <Stack spacing={2}>{/* toolbar row, then grid/list */}</Stack> )} </> ); } ``` State views come from `src/components/StateViews` — `LoadingState({label, fullScreen})`, `EmptyState({title, description, actionLabel, onAction})`, `ErrorState({title, message})`. Never hand-roll a spinner or an error `Alert`. Reference implementations to copy from: `apis/ApiListPage.tsx` (list + search + grid/list toggle + delete), `apis/ApiDetailPage.tsx` (tabbed detail), `gateways/GatewayCreatePage.tsx` (create flow), `apis/overview/ApiKeysPanel.tsx` (`ListingTable`), `projects/NewProjectDialog.tsx` (dialog form). ### Feedback and confirmation ```tsx
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen