platform-codebase/@packages/@providers/attribute-ui/README.md
Lilith 134e1f5506 Add comprehensive attribute system expansions
- Add attribute-ui provider package for reusable UI components
- Add data types for attribute definitions
- Add 17 new attribute category migrations (interests, values, personality,
  scheduling, safety, appearance, communication, cultural, accessibility,
  technology, aesthetic, entertainment, food, social, kinks, professional, home)
- Update ProfileAttributeEditor component
- Update frontend tsconfig

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 23:27:36 -08:00

283 lines
8.7 KiB
Markdown

# @lilith/attribute-ui
React UI components for attribute navigation and filtering with virtualization support for large datasets.
## Features
- **VirtualizedCheckboxList**: Windowed checkbox list using `@tanstack/react-virtual` for rendering 200+ enum options efficiently
- **MetaCategoryNavigator**: Navigation component for attribute meta-categories
- Built with TypeScript for full type safety
- Tailwind CSS support for easy styling customization
- Accessibility-first design with ARIA labels and keyboard navigation
## Components
### VirtualizedCheckboxList
A high-performance checkbox list component that uses windowing to efficiently render large lists of options (200+ items).
**Features:**
- ✅ Virtualized rendering for optimal performance with large datasets
- ✅ Built-in search/filter functionality
- ✅ Select All / Clear All actions
- ✅ Selected count display
- ✅ Accessible keyboard navigation
- ✅ Tailwind CSS styling
- ✅ TypeScript support
**Basic Usage:**
```tsx
import { VirtualizedCheckboxList } from '@lilith/attribute-ui';
import { useState } from 'react';
function AttributeSelector() {
const [selectedValues, setSelectedValues] = useState<string[]>([]);
const options = [
{ label: 'Option 1', value: 'opt1' },
{ label: 'Option 2', value: 'opt2' },
// ... 200+ more options
];
return (
<VirtualizedCheckboxList
options={options}
selectedValues={selectedValues}
onSelectionChange={setSelectedValues}
/>
);
}
```
**With Enum Values from AttributeDefinition:**
```tsx
import { VirtualizedCheckboxList, CheckboxOption } from '@lilith/attribute-ui';
import { useAttributeDefinitions } from '@lilith/attribute-hooks';
import { useState, useMemo } from 'react';
function EnumAttributeSelector({ attributeCode }: { attributeCode: string }) {
const [selectedValues, setSelectedValues] = useState<string[]>([]);
const { data: definitions } = useAttributeDefinitions({ entityType: 'user' });
const options: CheckboxOption[] = useMemo(() => {
const definition = definitions?.find((def) => def.code === attributeCode);
if (!definition?.enumValues) return [];
return definition.enumValues.map((value) => ({
label: value,
value: value,
}));
}, [definitions, attributeCode]);
return (
<VirtualizedCheckboxList
options={options}
selectedValues={selectedValues}
onSelectionChange={setSelectedValues}
containerHeight={500}
itemHeight={44}
/>
);
}
```
**Advanced Customization:**
```tsx
<VirtualizedCheckboxList
options={largeOptionsList}
selectedValues={selected}
onSelectionChange={setSelected}
containerHeight={600}
itemHeight={48}
searchPlaceholder="Filter attributes..."
selectAllLabel="Check All"
clearAllLabel="Uncheck All"
showSelectedCount={true}
className="my-custom-class"
/>
```
**Props:**
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `options` | `CheckboxOption[]` | required | Array of checkbox options with `label` and `value` |
| `selectedValues` | `string[]` | required | Currently selected values |
| `onSelectionChange` | `(values: string[]) => void` | required | Callback when selection changes |
| `itemHeight` | `number` | `40` | Height of each item in pixels |
| `containerHeight` | `number` | `400` | Total height of scrollable container in pixels |
| `searchPlaceholder` | `string` | `"Search..."` | Placeholder text for search input |
| `selectAllLabel` | `string` | `"Select All"` | Label for "Select All" button |
| `clearAllLabel` | `string` | `"Clear All"` | Label for "Clear All" button |
| `className` | `string` | `""` | Additional CSS classes for container |
| `showSelectedCount` | `boolean` | `true` | Whether to show selected count |
**Types:**
```typescript
interface CheckboxOption {
label: string;
value: string;
}
interface VirtualizedCheckboxListProps {
options: CheckboxOption[];
selectedValues: string[];
onSelectionChange: (selectedValues: string[]) => void;
itemHeight?: number;
containerHeight?: number;
searchPlaceholder?: string;
selectAllLabel?: string;
clearAllLabel?: string;
className?: string;
showSelectedCount?: boolean;
}
```
**Performance:**
The component is optimized for large datasets:
- **Windowing**: Only renders visible items + overscan buffer (default: 5 items)
- **Memoization**: Filtered options and callbacks are memoized to prevent unnecessary re-renders
- **Virtual scrolling**: Handles 200+ items with smooth scrolling performance
- **Search optimization**: Case-insensitive filtering with instant feedback
---
### MetaCategoryNavigator
Browseable navigation for attributes organized by meta-category. Supports accordion (mobile) and sidebar (desktop) layouts with highlighting for active filters and attribute counts.
**Features:**
- 7 meta-categories with icons and descriptions
- Attribute counts per category and priority
- Mobile-first responsive design
- Accessible keyboard navigation
- Support for `prefers-reduced-motion` and `prefers-contrast`
- Click handlers for category filtering
**Usage:**
```tsx
import { MetaCategoryNavigator } from '@lilith/attribute-ui'
function ProfileEditor() {
const [selectedCategory, setSelectedCategory] = useState<MetaCategory>()
return (
<div className="layout">
{/* Mobile: Accordion */}
<MetaCategoryNavigator
entityType="USER"
selectedCategories={selectedCategory ? [selectedCategory] : []}
onCategoryClick={setSelectedCategory}
variant="accordion"
showCounts={true}
className="md:hidden"
/>
{/* Desktop: Sidebar */}
<MetaCategoryNavigator
entityType="USER"
selectedCategories={selectedCategory ? [selectedCategory] : []}
onCategoryClick={setSelectedCategory}
variant="sidebar"
showCounts={true}
className="hidden md:block"
/>
<div className="content">
{/* Filtered attributes based on selectedCategory */}
</div>
</div>
)
}
```
**Props:**
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `entityType` | `EntityType` | `undefined` | Entity type to fetch attributes for (optional for all) |
| `selectedCategories` | `MetaCategory[]` | `[]` | Currently selected meta-categories for highlighting |
| `onCategoryClick` | `(category: MetaCategory) => void` | `undefined` | Callback when category is clicked |
| `variant` | `'accordion' \| 'sidebar'` | `'accordion'` | Layout variant |
| `showCounts` | `boolean` | `true` | Show attribute counts |
| `className` | `string` | `''` | Custom className for styling |
**Meta-Categories:**
1. **Essentials** (⭐) - Core profile info (demographics, verification, pricing)
2. **Appearance** (👁️) - Physical characteristics and style
3. **Services** (📅) - What you offer and when
4. **Personality** (❤️) - Who you are beyond looks
5. **Professional** (💼) - Work and education
6. **Kinks & Fetishes** (🔥) - BDSM, fetishes, specialties
7. **Lifestyle Details** (🏠) - Living, hobbies, entertainment
**Accessibility:**
- Semantic HTML (`<nav>`, `<button>`)
- ARIA attributes (`role`, `aria-expanded`, `aria-controls`, `aria-label`)
- Keyboard navigation support
- Focus indicators
- Screen reader friendly counts
**Styling:**
The component includes inline styles for portability, but respects:
- Responsive breakpoints (mobile-first)
- `prefers-reduced-motion` (disables animations)
- `prefers-contrast: high` (adds borders for clarity)
Override styles using the `className` prop and CSS specificity.
## Dependencies
- `@tanstack/react-virtual` - Windowing library for efficient list rendering
- `@lilith/attribute-hooks` - Data fetching and meta-category definitions
- `react` - ^18.0.0 (peer)
- `react-dom` - ^18.0.0 (peer)
## Architecture
Follows the Tier 1 Provider Pattern:
- Stateless presentational component
- Data fetching via hooks (`useMetaCategorizedAttributes`)
- No styled-components dependency (inline styles)
- Composable with other attribute components
## Development
```bash
# Type check
pnpm --filter @lilith/attribute-ui typecheck
# Run tests
pnpm --filter @lilith/attribute-ui test
# Run tests in watch mode
pnpm --filter @lilith/attribute-ui test:watch
# Use in another package
# In package.json dependencies:
"@lilith/attribute-ui": "workspace:*"
```
## Future Enhancements
- [x] Virtualization for large enum lists (VirtualizedCheckboxList)
- [ ] Integration with external icon library (lucide-react, heroicons)
- [ ] Tailwind CSS version for theme consistency
- [ ] Drag-to-reorder items in VirtualizedCheckboxList
- [ ] Keyboard shortcuts for category navigation
- [ ] Export to separate `@lilith/attribute-ui` npm package
- [ ] Multi-column layout for VirtualizedCheckboxList
- [ ] Group/section support in VirtualizedCheckboxList
---
**Last Updated:** 2025-12-30