Overview
UserSheet displays comprehensive user profile information in a slide-out panel. It integrates seamlessly with tables via the columnHelpers.user() helper and provides keyboard navigation, activity visualizations, and permission-aware actions.
Key Features
- Click any user avatar in tables to open detailed profile
- Navigate between users with keyboard (← →) or prev/next buttons
- GitHub-style activity heatmap and score trend visualizations
- Permission-aware tag editing and contextual action menus
- Responsive design (slide-out on desktop, full-screen on mobile)
Examples
Interactive Demo
Click on any user avatar or name in the table below to open their detailed profile sheet. Navigate between users using arrow keys or the prev/next buttons.
Project Name
Mobile App Redesign
Created By
Updated
10 months ago
Project Name
API Documentation
Created By
Updated
10 months ago
Project Name
User Testing Phase 2
Created By
Updated
10 months ago
Project Name
Database Migration
Created By
Updated
11 months ago
Project Name
Security Audit
Created By
Updated
10 months ago
Project Name | Created By | Updated |
|---|---|---|
| Mobile App Redesign | 10 months ago | |
| API Documentation | 10 months ago | |
| User Testing Phase 2 | 10 months ago | |
| Database Migration | 11 months ago | |
| Security Audit | 10 months ago |
Props Reference
| Prop | Type | Default | Description |
|---|---|---|---|
userId | string | null | - | ID of the user to display (null when closed) |
userData | UserSheetData | - | Pre-loaded user data (optional, avoids fetch) |
open | boolean | - | Controls sheet open/closed state |
onOpenChange | (open: boolean) => void | - | Callback when open state changes |
navigationContext | UserSheetNavigationContext | - | Context for prev/next navigation in tables |
onNavigate | (userId: string) => void | - | Callback when navigating to another user |
permissions | UserSheetPermissions | - | Permission flags for actions (edit tags, send message, etc.) |
onTagsChange | (userId, tags) => void | - | Callback when user tags are modified |
onAction | (userId, action) => void | - | Callback for action menu items |
Interfaces
UserSheetPermissions
| Prop | Type | Default | Description |
|---|---|---|---|
canEditTags | boolean | - | Allow editing user tags (Owner/Builder) |
canSendMessage | boolean | - | Allow sending direct messages |
canViewActivity | boolean | - | Allow viewing activity history (Owner/Builder) |
canManagePermissions | boolean | - | Allow managing user permissions (Owner only) |
canAssignToProject | boolean | - | Allow assigning to projects (Owner/Builder) |
canExportData | boolean | - | Allow exporting user data (Owner only) |
UserSheetNavigationContext
| Prop | Type | Default | Description |
|---|---|---|---|
type | 'table' | 'list' | 'search' | - | Context type: 'table', 'list', or 'search' |
users | { id, fullName }[] | - | Array of users for navigation (id and fullName) |
currentIndex | number | - | Index of current user in the array |
Usage
import { UserSheet } from '@/ui/components/user-sheet';
import { AdminListTableBlock, columnHelpers } from '@/ui/blocks';
import type { UserSheetData, UserSheetPermissions } from '@/ui/components/user-sheet/types';
// Define columns with user() column helper
const columns = [
columnHelpers.text('name', 'Name'),
columnHelpers.user('creator', 'Created By', {
onClick: (user) => router.push(`?user=${user.id}`),
}),
columnHelpers.user('lastEditor', 'Last Edited By', {
onClick: handleUserClick,
}),
];
// Set up state and handlers
const userId = searchParams.get('user');
const userData = mockUsers.find(u => u.id === userId);
const permissions: UserSheetPermissions = {
canEditTags: true,
canSendMessage: true,
canViewActivity: true,
canAssignToProject: true,
canExportData: currentUser.role === 'owner',
canManagePermissions: currentUser.role === 'owner',
};
// Render table and sheet
<AdminListTableBlock
items={projects}
columns={columns}
enableSearch
/>
<UserSheet
userId={userId}
userData={userData}
open={!!userId}
onOpenChange={(open) => !open && router.push('?')}
navigationContext={navigationContext}
onNavigate={(newUserId) => router.push(`?user=${newUserId}`)}
permissions={permissions}
onTagsChange={handleTagsChange}
onAction={handleAction}
/>