# 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 } /> } /> } /> } /> ``` ### 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