284 lines
8.7 KiB
Markdown
284 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
|