diff --git a/docs/superpowers/specs/2026-03-18-admin-pages-design.md b/docs/superpowers/specs/2026-03-18-admin-pages-design.md index 0796b37..8883577 100644 --- a/docs/superpowers/specs/2026-03-18-admin-pages-design.md +++ b/docs/superpowers/specs/2026-03-18-admin-pages-design.md @@ -9,9 +9,9 @@ Transfer the admin section from cameleer3-server UI to the design system project ## New Design System Components -### 1. MultiSelect (primitive) +### 1. MultiSelect (composite) -Dropdown trigger that opens a popover with searchable checkbox list and "Apply" action. +Dropdown trigger that opens a positioned panel with searchable checkbox list and "Apply" action. **Props:** ```typescript @@ -32,18 +32,25 @@ interface MultiSelectProps { ``` **Behavior:** -- Click trigger → popover opens below with search input + checkbox list + "Apply (N)" footer +- 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 popover -- Click outside or Escape closes without applying +- 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/primitives/MultiSelect/` -- Uses Popover composite for positioning +- 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 -- No forwardRef needed (not a native form control) ### 2. ConfirmDialog (composite) @@ -57,10 +64,11 @@ interface ConfirmDialogProps { 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 + 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' // default: 'danger' + variant?: 'danger' | 'warning' | 'info' // default: 'danger' + loading?: boolean // default: false — disables buttons, shows pending state className?: string } ``` @@ -71,7 +79,8 @@ interface ConfirmDialogProps { - Confirm button disabled until input matches `confirmText` exactly - Input clears on open - Enter submits when enabled; Escape closes -- Confirm button uses danger/warning variant styling +- 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/` @@ -95,14 +104,14 @@ interface InlineEditProps { **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 or blur saves. Escape cancels (reverts to original value). +- **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 cancels (lightweight inline pattern). +- No save/cancel buttons — Enter saves, Escape/blur cancels (lightweight inline pattern). **Implementation:** - New directory: `src/design-system/primitives/InlineEdit/` -- Uses `forwardRef` (wraps an input) -- Manages internal editing state +- 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 @@ -117,11 +126,25 @@ interface InlineEditProps { 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 -Add admin links to the Sidebar bottom links. The existing Sidebar component already supports `bottomLinks` — add entries for Audit Log, OIDC, and User Management. +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. -Update the mock sidebar data in `src/mocks/sidebar.ts` to include admin navigation, and update the Sidebar usage across all example pages. +### 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/`) @@ -178,14 +201,14 @@ Update the mock sidebar data in `src/mocks/sidebar.ts` to include admin navigati Add demos for all three new components: -- **MultiSelect** → PrimitivesSection: Demo showing multi-select with sample options, displaying selected count +- **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/primitives/MultiSelect/ +src/design-system/composites/MultiSelect/ MultiSelect.tsx MultiSelect.module.css MultiSelect.test.tsx