UNPKG

laif-ds

Version:

Design System di Laif con componenti React basati su principi di Atomic Design

343 lines (266 loc) 11.7 kB
# TruncatedCell The TruncatedCell component is designed to display text that may be too long for its container. It automatically detects when text is truncated and provides a popover to show the full content. ## Features - **Flexible Content**: Accepts plain text or rich ReactNode content - **Automatic Truncation Detection**: Uses scrollWidth vs clientWidth comparison to detect if content is truncated - **Responsive**: Re-checks truncation on resize using ResizeObserver for better performance - **Controlled Popover**: Manages popover state programmatically for better UX - **Eye Icon Hint**: Displays an eye icon hint only when content is truncated, with smooth fade-in animation - **Button Visibility Control**: Optional `showButton` prop to control eye button visibility - **Custom Popover Styling**: `popoverClassName` prop for custom popover content styling - **Keyboard Accessibility**: Full keyboard navigation support (Enter/Space to open popover) - **Screen Reader Support**: Proper ARIA labels and roles for accessibility - **Empty State**: Shows "-" with muted styling when no content is provided - **Custom Empty Fallback**: Supports a custom placeholder for empty values - **Customizable Styling**: Supports custom CSS classes for container, cell, and popover - **Dialog Title**: Optional title for the popover header - **Custom Content**: Full JSX support for custom popover content - **Hover Effects**: Smooth underline on content hover and button appearance - **Scrollable Content**: Long content in popover is scrollable with max height - **DX-first API**: Rich children work out of the box, and `text` is only needed when you want explicit popover/accessibility text ## Usage ```tsx import { TruncatedCell } from "laif-ds"; // Basic usage with string content <TruncatedCell text="Short text" /> // Long text that will be truncated <TruncatedCell text="This is a very long text that will be truncated because it exceeds the container width" wrapperClassName="w-56" /> // String children also work without text <TruncatedCell wrapperClassName="w-56"> This is a string content </TruncatedCell> // Rich content works without duplicating text <TruncatedCell wrapperClassName="w-56"> <div className="flex items-center gap-2"> <Avatar src="/avatar.jpg" /> <div className="min-w-0"> <p className="font-semibold">John Doe</p> <p className="text-sm text-gray-600">john@example.com</p> </div> </div> </TruncatedCell> // Provide text when you want explicit popover or accessibility content <TruncatedCell text="John Doe - john@example.com" title="User details" wrapperClassName="w-56" > <UserSummary /> </TruncatedCell> // With custom styling and title <TruncatedCell text="Custom styled truncated text" title="Full Content" wrapperClassName="w-40" className="max-w-40" /> // With custom popover content and styling <TruncatedCell text="Text with custom popover content" title="Custom Content" popoverContent={ <div className="space-y-3"> <p>Custom JSX content here</p> <button>Action Button</button> </div> } popoverClassName="max-w-md" showButton={true} /> // Hide the eye hint <TruncatedCell text="Text without eye button" showButton={false} /> // Empty text <TruncatedCell text="" /> // Custom empty fallback <TruncatedCell text="" emptyFallback="No value" /> ``` ## Props | Prop | Type | Default | Description | | ------------------ | ----------- | ----------- | ---------------------------------------------------------------------------- | | `children` | `ReactNode` | `undefined` | Content to display | | `text` | `string` | `undefined` | Optional text used for truncation detection, accessibility, and popover copy | | `title` | `string` | `undefined` | Optional title for the popover dialog | | `popoverContent` | `ReactNode` | `undefined` | Custom popover content (JSX) | | `wrapperClassName` | `string` | `""` | Preferred CSS classes for the wrapper container | | `className` | `string` | `""` | CSS classes for the cell content | | `popoverClassName` | `string` | `""` | CSS classes for the popover content | | `showButton` | `boolean` | `true` | Flag used to show/hide the eye hint | | `emptyFallback` | `ReactNode` | `"-"` | Custom placeholder rendered when no content is available | ## Behavior ### Truncation Detection - Component monitors the text element's scrollWidth vs clientWidth - If scrollWidth > clientWidth, the text is considered truncated - Detection runs on mount and on resize - When `text` is not provided, the component also extracts `textContent` from the rendered node to power the default popover content ### Visual States 1. **Normal State**: Text is displayed with truncation (CSS `truncate`) 2. **Truncated State**: Text is truncated + eye icon hint appears 3. **Empty State**: Shows "-" when text is empty/undefined 4. **Popover State**: Opens when there is content to inspect, and the eye icon hint appears only when the rendered value is truncated ### Styling - Container: `flex max-w-60 gap-2` + custom classes - Text container: `flex min-w-0 flex-1` - Text element: `w-full min-w-0 truncate whitespace-nowrap` - Popover: `w-96` with `whitespace-pre-wrap` for multiline support ### Content Types The TruncatedCell component supports two main usage patterns: #### 1. String Content (Simple) ```tsx // Direct text prop <TruncatedCell text="Simple text content" /> // String children (text prop optional) <TruncatedCell wrapperClassName="w-56"> String content as children </TruncatedCell> ``` #### 2. ReactNode Content (Advanced) ```tsx // Rich children work without text <TruncatedCell wrapperClassName="w-56"> <div className="flex items-center gap-2"> <Avatar src="/avatar.jpg" /> <div className="min-w-0"> <p className="font-semibold">John Doe</p> <p className="text-sm text-gray-600">john@example.com</p> </div> </div> </TruncatedCell> // Add text when you want explicit popover content <TruncatedCell text="User profile - John Doe" wrapperClassName="w-56"> <UserSummary /> </TruncatedCell> ``` ### Choosing Between `children` and `text` - Use `text` for the simplest plain-text case - Use `children` when you need custom rendering inside the truncated area - Add `text` together with `children` when the rendered content is visual or abbreviated and you want more descriptive text in the popover ```tsx // Plain text <TruncatedCell text="Contract pending signature" /> // Rich UI with automatic text extraction <TruncatedCell wrapperClassName="w-56"> <div>Complex content</div> </TruncatedCell> // Rich UI with explicit popover and a11y text <TruncatedCell text="Complex content description" wrapperClassName="w-56"> <div>Complex content</div> </TruncatedCell> ``` ### Custom Popover Content The `popoverContent` prop allows you to override the default text display with custom JSX content: - **Full Flexibility**: Any React components can be used as popover content - **Backward Compatible**: When not provided, defaults to displaying the text with Typo component - **Title Support**: Works seamlessly with the `title` prop for popover headers - **Custom Styling**: Use `popoverClassName` to style the popover container - **Button Control**: Use `showButton` to control eye hint visibility - **Open Behavior**: Custom popover content can open even when the displayed value itself is not truncated ```tsx <TruncatedCell text="User profile" title="User Details" popoverContent={ <div className="space-y-2"> <div className="flex items-center gap-2"> <Avatar src="/avatar.jpg" /> <div> <p className="font-semibold">John Doe</p> <p className="text-sm text-gray-600">john@example.com</p> </div> </div> <div className="flex gap-2"> <Button size="sm">Edit</Button> <Button size="sm" variant="outline"> View Profile </Button> </div> </div> } popoverClassName="max-w-md" showButton={true} /> ``` ### Button Visibility Control The `showButton` prop gives you control over the eye hint visibility: ```tsx // Show eye hint (default) <TruncatedCell text="Long text that will be truncated" showButton={true} /> // Hide eye hint <TruncatedCell text="Long text without eye hint" showButton={false} /> ``` ### Empty Fallback ```tsx // Default placeholder <TruncatedCell text="" /> // Custom placeholder <TruncatedCell text="" emptyFallback="No value" /> ``` ### Popover Styling The `popoverClassName` prop allows custom styling of the popover container: ```tsx <TruncatedCell text="Custom styled popover" popoverClassName="max-w-md bg-blue-50 border-blue-200" /> ``` ## Technical Details ### Dependencies - React hooks: `useState`, `useEffect`, `useRef`, `useCallback` - Laif-DS components: `Popover`, `PopoverContent`, `PopoverTrigger`, `Typo`, `Icon` - Utility: `cn` for class merging ### Performance Optimizations - **ResizeObserver**: Uses modern ResizeObserver API for better performance than window resize events - **useCallback**: Memoizes truncation check function to prevent unnecessary re-renders - **Controlled State**: Manages popover state programmatically to avoid prop drilling - **Cleanup**: Proper cleanup of observers and event listeners - **Optimized Re-renders**: Only re-checks truncation when content changes ### Event Handling - ResizeObserver for element-specific resize detection - Fallback window resize listener for older browsers - Click on the cell trigger opens the popover when available - Keyboard support (Enter/Space keys) - Hover effects with smooth transitions ### Accessibility Features - **Semantic Trigger**: Uses a real `button` element as the interactive trigger - **Keyboard Navigation**: Full support for Enter and Space keys - **Screen Reader Labels**: Dynamic `aria-label` based on truncation state - **Focus Management**: Native button focus behavior - **Semantic HTML**: Popover content remains accessible even for multiline values ## Accessibility - Text container has cursor pointer to indicate interactivity - Hover state provides visual feedback - Popover provides full text access for screen readers - Eye icon provides a clear visual hint that more content is available ## Examples ### In a Table Cell ```tsx const TableCell = ({ value }: { value: string }) => ( <TruncatedCell text={value} wrapperClassName="w-56" className="py-2" /> ); ``` ### With Maximum Width Constraint ```tsx <TruncatedCell text="Constrained width text that will truncate" wrapperClassName="w-32" /> ``` ### Multiline Text Support ```tsx <TruncatedCell text="Line 1\nLine 2\nLine 3" className="max-w-48" /> ``` ## Comparison with Alternatives ### vs Tooltip - **TruncatedCell**: Supports click-to-inspect content, and visually hints with the eye icon when text is actually truncated - **Tooltip**: Always shows on hover regardless of truncation ### vs CSS text-overflow alone - **TruncatedCell**: Provides access to full content - **CSS only**: Content is inaccessible when truncated ### vs Expandable Text - **TruncatedCell**: Compact, popover-based approach - **Expandable**: Takes more space, inline expansion