docs: address spec review feedback for admin pages design
- Move MultiSelect to composites (depends on portal, not a primitive) - MultiSelect manages own positioning instead of wrapping Popover - Add loading prop and info variant to ConfirmDialog - Drop forwardRef from InlineEdit (input conditionally exists) - Change InlineEdit blur to cancel (not save) - Add router integration, barrel export, and accessibility details - Add sidebar integration strategy (admin sub-nav, not sidebar clutter) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,9 +9,9 @@ Transfer the admin section from cameleer3-server UI to the design system project
|
|||||||
|
|
||||||
## New Design System Components
|
## 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:**
|
**Props:**
|
||||||
```typescript
|
```typescript
|
||||||
@@ -32,18 +32,25 @@ interface MultiSelectProps {
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Behavior:**
|
**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)
|
- Search filters options by label (case-insensitive)
|
||||||
- Checkboxes toggle selection; changes are local until "Apply" is clicked
|
- Checkboxes toggle selection; changes are local until "Apply" is clicked
|
||||||
- Apply calls `onChange` with selected values and closes popover
|
- Apply calls `onChange` with selected values and closes panel
|
||||||
- Click outside or Escape closes without applying
|
- Click outside or Escape closes without applying (discards pending changes)
|
||||||
- Trigger shows count: "2 selected" or placeholder when empty
|
- 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:**
|
**Implementation:**
|
||||||
- New directory: `src/design-system/primitives/MultiSelect/`
|
- New directory: `src/design-system/composites/MultiSelect/`
|
||||||
- Uses Popover composite for positioning
|
- 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
|
- CSS Modules with design tokens
|
||||||
- No forwardRef needed (not a native form control)
|
|
||||||
|
|
||||||
### 2. ConfirmDialog (composite)
|
### 2. ConfirmDialog (composite)
|
||||||
|
|
||||||
@@ -57,10 +64,11 @@ interface ConfirmDialogProps {
|
|||||||
onConfirm: () => void
|
onConfirm: () => void
|
||||||
title?: string // default: "Confirm Deletion"
|
title?: string // default: "Confirm Deletion"
|
||||||
message: string // e.g., "Delete user 'alice'? This cannot be undone."
|
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"
|
confirmLabel?: string // default: "Delete"
|
||||||
cancelLabel?: string // default: "Cancel"
|
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
|
className?: string
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -71,7 +79,8 @@ interface ConfirmDialogProps {
|
|||||||
- Confirm button disabled until input matches `confirmText` exactly
|
- Confirm button disabled until input matches `confirmText` exactly
|
||||||
- Input clears on open
|
- Input clears on open
|
||||||
- Enter submits when enabled; Escape closes
|
- 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:**
|
**Implementation:**
|
||||||
- New directory: `src/design-system/composites/ConfirmDialog/`
|
- New directory: `src/design-system/composites/ConfirmDialog/`
|
||||||
@@ -95,14 +104,14 @@ interface InlineEditProps {
|
|||||||
|
|
||||||
**Behavior:**
|
**Behavior:**
|
||||||
- **Display mode:** Shows value as text with subtle edit icon (pencil). Clicking text or icon enters edit mode.
|
- **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.
|
- 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:**
|
**Implementation:**
|
||||||
- New directory: `src/design-system/primitives/InlineEdit/`
|
- New directory: `src/design-system/primitives/InlineEdit/`
|
||||||
- Uses `forwardRef` (wraps an input)
|
- 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
|
- Manages internal editing state with useState
|
||||||
|
|
||||||
## Admin Pages
|
## Admin Pages
|
||||||
|
|
||||||
@@ -117,11 +126,25 @@ interface InlineEditProps {
|
|||||||
|
|
||||||
All pages use the standard AppShell + Sidebar + TopBar layout with breadcrumbs.
|
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
|
### 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/`)
|
### 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:
|
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
|
- **InlineEdit** → PrimitivesSection: Demo showing display/edit toggle with a sample name
|
||||||
- **ConfirmDialog** → CompositesSection: Demo with a "Delete item" button that opens the dialog
|
- **ConfirmDialog** → CompositesSection: Demo with a "Delete item" button that opens the dialog
|
||||||
|
|
||||||
## File Structure
|
## File Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
src/design-system/primitives/MultiSelect/
|
src/design-system/composites/MultiSelect/
|
||||||
MultiSelect.tsx
|
MultiSelect.tsx
|
||||||
MultiSelect.module.css
|
MultiSelect.module.css
|
||||||
MultiSelect.test.tsx
|
MultiSelect.test.tsx
|
||||||
|
|||||||
Reference in New Issue
Block a user