Files
design-system/docs/superpowers/specs/2026-03-18-admin-pages-design.md

250 lines
11 KiB
Markdown
Raw Normal View History

# Admin Pages + New Components Design
**Date:** 2026-03-18
**Scope:** 3 new design system components + 3 admin example pages
## Overview
Transfer the admin section from cameleer3-server UI to the design system project as example pages. Add three new reusable components to the design system that are needed by these pages and useful generally.
## New Design System Components
### 1. MultiSelect (composite)
Dropdown trigger that opens a positioned panel with searchable checkbox list and "Apply" action.
**Props:**
```typescript
interface MultiSelectOption {
value: string
label: string
}
interface MultiSelectProps {
options: MultiSelectOption[]
value: string[]
onChange: (value: string[]) => void
placeholder?: string // default: "Select..."
searchable?: boolean // default: true
disabled?: boolean
className?: string
}
```
**Behavior:**
- Click trigger → panel opens below with search input + checkbox list + "Apply (N)" footer
- Search filters options by label (case-insensitive)
- Checkboxes toggle selection; changes are local until "Apply" is clicked
- Apply calls `onChange` with selected values and closes panel
- Click outside or Escape closes without applying (discards pending changes)
- Trigger shows count: "2 selected" or placeholder when empty
- Arrow keys navigate checkbox list, Space toggles focused item, Tab moves between search/list/apply
- Panel has max-height with scroll for long option lists
**Accessibility:**
- Trigger: `role="combobox"`, `aria-expanded`, `aria-haspopup="listbox"`
- Option list: `role="listbox"`, options have `role="option"` with `aria-selected`
- Search input: `aria-label="Filter options"`
**Implementation:**
- New directory: `src/design-system/composites/MultiSelect/`
- Manages its own open/close state and positioning (does NOT wrap Popover — needs controlled close behavior to distinguish apply vs. discard)
- Uses portal for the dropdown panel to avoid overflow clipping
- CSS Modules with design tokens
### 2. ConfirmDialog (composite)
Modal dialog requiring the user to type a confirmation string before a destructive action proceeds.
**Props:**
```typescript
interface ConfirmDialogProps {
open: boolean
onClose: () => void
onConfirm: () => void
title?: string // default: "Confirm Deletion"
message: string // e.g., "Delete user 'alice'? This cannot be undone."
confirmText: string // text the user must type to enable confirm button (must be non-empty)
confirmLabel?: string // default: "Delete"
cancelLabel?: string // default: "Cancel"
variant?: 'danger' | 'warning' | 'info' // default: 'danger'
loading?: boolean // default: false — disables buttons, shows pending state
className?: string
}
```
**Behavior:**
- Built on Modal (size="sm")
- Shows title, message, text input with label "Type '{confirmText}' to confirm"
- Confirm button disabled until input matches `confirmText` exactly
- Input clears on open
- Enter submits when enabled; Escape closes
- Confirm button uses danger/warning/info variant styling (matches AlertDialog pattern)
- When `loading` is true, both buttons are disabled
**Implementation:**
- New directory: `src/design-system/composites/ConfirmDialog/`
- Reuses Modal internally (same pattern as AlertDialog)
- Auto-focuses input on open
### 3. InlineEdit (primitive)
Click-to-edit text field that toggles between display and edit modes.
**Props:**
```typescript
interface InlineEditProps {
value: string
onSave: (value: string) => void
placeholder?: string // shown when value is empty in display mode
disabled?: boolean
className?: string
}
```
**Behavior:**
- **Display mode:** Shows value as text with subtle edit icon (pencil). Clicking text or icon enters edit mode.
- **Edit mode:** Input field with current value. Enter saves. Escape cancels (reverts to original value). Blur cancels (same as Escape — prevents accidental saves when clicking away).
- If saved value is empty and placeholder exists, display mode shows placeholder in muted style.
- No save/cancel buttons — Enter saves, Escape/blur cancels (lightweight inline pattern).
**Implementation:**
- New directory: `src/design-system/primitives/InlineEdit/`
- No forwardRef — the component manages its own input internally (the input only exists in edit mode, so a forwarded ref would be null in display mode)
- Manages internal editing state with useState
## Admin Pages
### Route Structure
```
/admin → redirects to /admin/rbac
/admin/audit → AuditLog page
/admin/oidc → OidcConfig page
/admin/rbac → UserManagement page (tabs: Users | Groups | Roles)
```
All pages use the standard AppShell + Sidebar + TopBar layout with breadcrumbs.
### Router Integration
Update `App.tsx` to replace the single `/admin` route with nested routes:
```tsx
<Route path="/admin" element={<Navigate to="/admin/rbac" replace />} />
<Route path="/admin/audit" element={<AuditLog />} />
<Route path="/admin/oidc" element={<OidcConfig />} />
<Route path="/admin/rbac" element={<UserManagement />} />
```
### Sidebar Integration
Keep the existing single "Admin" bottom link in the Sidebar. The admin pages handle their own sub-navigation internally via a secondary nav bar at the top of each admin page (links to Audit Log, OIDC, User Management). This avoids cluttering the main sidebar with admin sub-entries.
### Barrel Export Updates
- Add `MultiSelect` and `MultiSelectOption` type to `src/design-system/composites/index.ts`
- Add `ConfirmDialog` and `ConfirmDialogProps` type to `src/design-system/composites/index.ts`
- Add `InlineEdit` and `InlineEditProps` type to `src/design-system/primitives/index.ts`
### Page: Audit Log (`src/pages/Admin/AuditLog/`)
**Layout:** Full-width content area (no split pane).
**Sections:**
1. **Header** — "Audit Log" title + event count badge
2. **Filter bar** — DateRangePicker (from/to), Input (user), Select (category: INFRA/AUTH/USER_MGMT/CONFIG), Input (search)
3. **Data table** — DataTable with columns: Timestamp (monospace), User, Category (Badge), Action, Target, Result (Badge with success/error color)
4. **Expandable rows** — Clicking a row reveals detail section with IP address, user agent, and JSON detail (CodeBlock)
5. **Pagination** — Pagination component below table
**Mock data:** ~30 audit events with varied categories, actions, results.
### Page: OIDC Config (`src/pages/Admin/OidcConfig/`)
**Layout:** Single-column form layout, max-width constrained.
**Sections:**
1. **Header** — "OIDC Configuration" + Save/Test/Delete buttons
2. **Behavior** — Two Toggle fields (Enabled, Auto Sign-Up) wrapped in FormField
3. **Provider Settings** — FormField-wrapped Inputs for Issuer URI, Client ID, Client Secret (type=password)
4. **Claim Mapping** — FormField-wrapped Inputs for Roles Claim, Display Name Claim with hint text
5. **Default Roles** — Tag list (removable) + Input + Button to add new roles
6. **Delete** — Button (danger) that opens ConfirmDialog
**Mock data:** Pre-filled form state with sample OIDC config.
### Page: User Management (`src/pages/Admin/UserManagement/`)
**Layout:** Tabs component at top (Users | Groups | Roles). Each tab has a CSS grid split-pane (roughly 52/48).
#### Users Tab
- **Left pane:** Input search + "Add user" Button + scrollable user list. Each item: Avatar + name + provider Badge + meta (email, group path) + Tag list (roles: amber, groups: green, inherited: dashed Badge).
- **Inline create form:** Appears at top of list when "Add user" clicked. Input fields for username, display, email, password. Cancel/Create buttons.
- **Right pane (detail):** Large Avatar + InlineEdit (display name) + email + Delete Button. Metadata fields (Status, ID as MonoText, Created). SectionHeader "Group membership" + Tag list (removable) + MultiSelect to add groups. SectionHeader "Effective roles" + Tag list (direct: solid, inherited: dashed with source label) + MultiSelect to add roles.
- **Delete:** ConfirmDialog (type username to confirm).
#### Groups Tab
- **Left pane:** Same pattern — search + "Add group" + group list. Each item: Avatar (square) + name + meta (parent, child count, member count) + role Tags.
- **Inline create form:** Name input + parent Select (top-level or existing group).
- **Right pane:** Avatar + InlineEdit (name) + hierarchy label. Metadata (ID, Parent — editable via Select). SectionHeader "Members" + Tag list. SectionHeader "Child groups" + Tag list. SectionHeader "Assigned roles" + removable Tags + MultiSelect. SectionHeader "Hierarchy" with indented tree display.
- **Delete:** ConfirmDialog.
#### Roles Tab
- **Left pane:** Search + "Add role" + role list. Each item: Avatar (square) + name + lock icon if system + meta (description, assignment count) + Tags.
- **Inline create form:** Name, Description, Scope inputs.
- **Right pane:** Avatar + role name (non-editable for system roles) + description. Metadata (ID, Scope, Type). SectionHeader "Assigned to groups" (read-only list). SectionHeader "Assigned to users (direct)" (read-only). SectionHeader "Effective principals" with inherited entries in dashed style.
- **Delete:** ConfirmDialog (only for non-system roles).
**Mock data:** ~8 users, ~4 groups (with nesting), ~6 roles (including system roles ADMIN, USER). Realistic role/group assignments with inheritance.
### Inventory Updates
Add demos for all three new components:
- **MultiSelect** → CompositesSection: Demo showing multi-select with sample options, displaying selected count
- **InlineEdit** → PrimitivesSection: Demo showing display/edit toggle with a sample name
- **ConfirmDialog** → CompositesSection: Demo with a "Delete item" button that opens the dialog
## File Structure
```
src/design-system/composites/MultiSelect/
MultiSelect.tsx
MultiSelect.module.css
MultiSelect.test.tsx
src/design-system/primitives/InlineEdit/
InlineEdit.tsx
InlineEdit.module.css
InlineEdit.test.tsx
src/design-system/composites/ConfirmDialog/
ConfirmDialog.tsx
ConfirmDialog.module.css
ConfirmDialog.test.tsx
src/pages/Admin/
AuditLog/
AuditLog.tsx
AuditLog.module.css
auditMocks.ts
OidcConfig/
OidcConfig.tsx
OidcConfig.module.css
UserManagement/
UserManagement.tsx
UserManagement.module.css
UsersTab.tsx
GroupsTab.tsx
RolesTab.tsx
rbacMocks.ts
```
## Out of Scope
- No backend API integration (static mock data with useState)
- No persistence across page refresh
- No access control / role gating in the example app
- Dashboard tab from RBAC is excluded per user request
- Split pane is page-local CSS, not a design system component