Aller au contenu principal

Data View Components

Overview

The Dynamic JSON Visualizer provides two distinct ways to view and interact with JSON data: CardView and TableView. These components handle the core data presentation, allowing users to explore, sort, flag, and select JSON objects in different formats optimized for different use cases.

CardView Component

The CardView component displays JSON objects as individual cards in a responsive grid layout. This view is ideal for exploring detailed object properties and handling complex nested data structures.

Grid Layout and Responsiveness

<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{data.map((obj) => (
<div key={obj.__id} className="bg-white dark:bg-gray-800 rounded-lg shadow-md">
{/* Card content */}
</div>
))}
</div>

Responsive grid system:

  • Mobile (default): Single column layout
  • Medium screens (md): Two columns
  • Large screens (lg): Three columns
  • Consistent spacing: 4-unit gap between cards

Card Structure

Each card contains several distinct sections:

1. Image Display (Optional)

{imageField && getValueByPath(obj, imageField) && (
<div className="aspect-video w-full overflow-hidden">
<img
src={String(getValueByPath(obj, imageField))}
alt="Card image"
className="w-full h-full object-cover"
onError={(e) => {
(e.target as HTMLImageElement).style.display = 'none';
}}
/>
</div>
)}

Smart image handling:

  • Conditional display - Only shows if an image field is configured and the object has that field
  • Aspect ratio - Maintains 16:9 aspect ratio (aspect-video)
  • Error handling - Hides broken images automatically
  • Responsive sizing - Images scale with card width

2. Header with Flags

<div className="p-4 border-b border-gray-200 dark:border-gray-700 flex justify-between items-center">
<div className="font-medium text-gray-700 dark:text-gray-300">
Object {data.indexOf(obj) + 1}
</div>
<div className="flex gap-1">
{flagTypes.map(flag => {
const IconComponent = flag.icon && availableIcons[flag.icon as keyof typeof availableIcons]
? availableIcons[flag.icon as keyof typeof availableIcons]
: Flag;

return (
<button
key={flag.id}
className={`p-1 rounded-full transition-colors ${
obj.__flags?.[flag.id]
? 'text-white'
: 'text-gray-400 hover:text-gray-700'
}`}
style={{
backgroundColor: obj.__flags?.[flag.id] ? flag.color : 'transparent',
}}
onClick={(e) => {
e.stopPropagation();
handleFlagToggle(obj.__id || '', flag.id, !!obj.__flags?.[flag.id]);
}}
>
<IconComponent className="h-4 w-4" />
</button>
);
})}
</div>
</div>

Flag system features:

  • Dynamic icons - Uses configured icon or defaults to Flag icon
  • Visual state - Active flags show with their configured color background
  • Interactive - Click to toggle flag state
  • Event isolation - stopPropagation() prevents card selection when clicking flags

3. Property Display with Expansion

{Object.entries(obj).map(([key, value]) => {
if (key.startsWith('__') || !shouldDisplayField(key)) return null;

const isExpandable = isExpandableObject(value);
const isExpanded = expandedKeys[key] || false;

return (
<div key={key} className="border-b border-gray-100 last:border-0 pb-2 last:pb-0">
<div className="flex justify-between items-center">
<div className="font-medium text-gray-700">{key}</div>
{isExpandable && (
<button onClick={() => toggleExpand(key)}>
{isExpanded ? <ChevronUp /> : <ChevronDown />}
</button>
)}
</div>

<div className="mt-1">
{isExpandable ? (
isExpanded ? (
<div className="ml-4 mt-2 border-l-2 border-gray-200 pl-2">
<RenderObjectProperties
obj={value as JSONObject}
parentPath={key}
expandedKeys={expandedKeys}
toggleExpand={toggleExpand}
/>
</div>
) : (
<div className="text-gray-500 text-sm italic">
{Object.keys(value as JSONObject).length} properties
</div>
)
) : (
<div className="text-gray-800 break-all whitespace-pre-wrap">
{renderValue(value)}
</div>
)}
</div>
</div>
);
})}

Property display features:

  • Field filtering - Skips internal fields (__ prefix) and hidden display fields
  • Expandable objects - Nested objects can be expanded/collapsed
  • Visual hierarchy - Indented display with left border for nested content
  • Property count - Shows number of properties when collapsed
  • Text wrapping - Handles long text with proper word breaking

Recursive Object Rendering

The RenderObjectProperties component handles nested object display:

const RenderObjectProperties: React.FC<RenderObjectPropertiesProps> = ({ 
obj,
parentPath,
expandedKeys,
toggleExpand
}) => {
return (
<div className="grid gap-2">
{Object.entries(obj).map(([key, value]) => {
const fullPath = parentPath ? `${parentPath}.${key}` : key;
const isExpandable = isExpandableObject(value);
const isExpanded = expandedKeys[fullPath] || false;

return (
<div key={fullPath}>
{/* Recursive rendering logic */}
</div>
);
})}
</div>
);
};

Recursive features:

  • Path tracking - Builds dot-notation paths for nested properties
  • Independent expansion - Each nested level can be expanded/collapsed independently
  • Unlimited depth - Handles arbitrarily deep object nesting
  • Visual consistency - Same styling patterns at all nesting levels

Selection System

const toggleSelection = (id: string) => {
if (selectedIds.includes(id)) {
onSelectionChange(selectedIds.filter(selectedId => selectedId !== id));
} else {
onSelectionChange([...selectedIds, id]);
}
};

<div
className={`transition-all hover:shadow-lg relative ${
selectedIds.includes(obj.__id || '') ? 'ring-2 ring-blue-500' : ''
}`}
onClick={() => obj.__id && toggleSelection(obj.__id)}
>
<div className="absolute top-2 right-2 z-10">
<input
type="checkbox"
checked={selectedIds.includes(obj.__id || '')}
onChange={(e) => {
e.stopPropagation();
if(obj.__id) toggleSelection(obj.__id);
}}
/>
</div>
</div>

Selection features:

  • Visual feedback - Selected cards show blue ring border
  • Multiple selection methods - Click card or checkbox
  • Event handling - Checkbox clicks don't trigger card selection
  • State management - Selection state managed by parent component

TableView Component

The TableView component displays JSON data in a spreadsheet-like format, ideal for comparing values across multiple objects and handling large datasets efficiently.

Table Structure and Sticky Headers

<div className="overflow-x-auto">
<table className="min-w-full border-collapse">
<thead>
<tr className="bg-gray-100 dark:bg-gray-800">
<th className="sticky left-0 bg-gray-100 dark:bg-gray-800 z-10">
<input type="checkbox" onChange={toggleAllSelection} />
</th>
<th className="sticky left-12 bg-gray-100 dark:bg-gray-800 z-10">
Actions
</th>
{allKeys.map(key => (
<th key={key}>
<button onClick={() => handleSort(key)}>
{key}
{getSortDirection(key) === 'asc' && <ArrowUp />}
{getSortDirection(key) === 'desc' && <ArrowDown />}
{getSortDirection(key) === null && <ArrowUpDown className="opacity-50" />}
</button>
</th>
))}
</tr>
</thead>
</table>
</div>

Table features:

  • Horizontal scrolling - Handles wide tables with many columns
  • Sticky columns - Selection and actions columns remain visible during scroll
  • Sortable headers - Click column headers to sort data
  • Visual sort indicators - Arrows show current sort direction
  • Select all - Master checkbox in header selects/deselects all rows

Dynamic Column Generation

const allKeys = useMemo(() => {
return extractKeys(data).filter(key => !displayFields.includes(key));
}, [data, displayFields]);

Smart column management:

  • Automatic discovery - Extracts all possible keys from the dataset
  • Memoization - Uses useMemo to avoid recalculating on every render
  • Field filtering - Respects display field configuration
  • Nested support - Includes nested object properties with dot notation

Cell Rendering with Expansion

{allKeys.map(key => {
const value = getValueByPath(obj, key);
const isExpandable = isExpandableObject(value);
const cellId = `${obj.__id}-${key}`;
const isExpanded = expandedCells[cellId] || false;

return (
<td key={key} className="px-4 py-3 border-t border-gray-100">
<div className="break-all whitespace-pre-wrap max-w-md">
{isExpandable ? (
<div>
<div className="flex items-center gap-1">
<button onClick={() => toggleExpand(obj.__id || '', key)}>
{isExpanded ? <ChevronUp /> : <ChevronDown />}
</button>
<span className="text-gray-600">
{Object.keys(value as JSONObject).length} properties
</span>
</div>

{isExpanded && (
<div className="mt-2 border-l-2 border-gray-200 pl-2">
<NestedObject
obj={value as JSONObject}
expandedKeys={expandedCells}
toggleExpand={(nestedKey) => {
const nestedCellId = `${obj.__id}-${key}-${nestedKey}`;
setExpandedCells(prev => ({
...prev,
[nestedCellId]: !prev[nestedCellId]
}));
}}
objId={obj.__id || ''}
parentKey={key}
/>
</div>
)}
</div>
) : (
<div className="text-gray-800">
{renderValue(value)}
</div>
)}
</div>
</td>
);
})}

Cell features:

  • Path-based access - Uses getValueByPath for nested property access
  • Individual expansion - Each cell can be expanded independently
  • Unique cell IDs - Combines object ID and key for expansion tracking
  • Constrained width - max-w-md prevents cells from becoming too wide
  • Text handling - Proper word breaking and whitespace preservation

Nested Object Component

const NestedObject: React.FC<NestedObjectProps> = ({ 
obj,
expandedKeys,
toggleExpand,
objId,
parentKey
}) => {
return (
<div className="grid gap-2">
{Object.entries(obj).map(([key, value]) => {
const cellId = `${objId}-${parentKey}-${key}`;
const isExpandable = isExpandableObject(value);
const isExpanded = expandedKeys[cellId] || false;

return (
<div key={cellId}>
{/* Nested object rendering */}
</div>
);
})}
</div>
);
};

Nested object features:

  • Hierarchical IDs - Builds unique IDs for nested expansion tracking
  • Recursive rendering - Handles unlimited nesting depth
  • Independent state - Each nested level maintains its own expansion state
  • Visual hierarchy - Consistent indentation and borders for nested content

Value Rendering System

Both components share a common renderValue function for consistent data display:

const renderValue = (value: JSONValue): React.ReactNode => {
if (value === null) return <span className="text-gray-500">null</span>;
if (value === undefined) return <span className="text-gray-500">undefined</span>;

if (typeof value === 'boolean') {
return (
<span className={value ? 'text-green-600' : 'text-red-600'}>
{String(value)}
</span>
);
}

if (typeof value === 'number') {
return <span className="text-violet-600">{value}</span>;
}

if (Array.isArray(value)) {
return (
<div className="text-gray-800">
{value.map((item, index) => (
<div key={index} className="flex items-center gap-1">
<span>{String(item)}</span>
{index < value.length - 1 && <span className="text-gray-400">,</span>}
</div>
))}
</div>
);
}

return String(value);
};

Type-specific rendering:

  • Null/undefined - Gray italic text
  • Booleans - Green for true, red for false
  • Numbers - Purple/violet color
  • Arrays - Vertical list with comma separators
  • Strings - Default text color
  • Objects - Handled by expansion system, not rendered directly

Sorting Integration

Both components integrate with the global sort system:

const handleSort = (key: string) => {
if (sortConfig?.key === key) {
if (sortConfig.direction === 'asc') {
setSortConfig({ key, direction: 'desc' });
} else {
setSortConfig(null); // Clear sort
}
} else {
setSortConfig({ key, direction: 'asc' });
}
};

Sort behavior:

  • First click - Sort ascending
  • Second click - Sort descending
  • Third click - Clear sort
  • Different field - Start with ascending sort

Icon Configuration System

The iconConfig.ts file centralizes icon management:

export const availableIcons = {
eye: Eye,
bell: Bell,
star: Star,
heart: Heart,
flag: Flag,
bookmark: Bookmark,
alertCircle: AlertCircle,
checkCircle: CheckCircle,
clock: Clock,
mail: Mail
};

Icon system features:

  • Centralized imports - All Lucide React icons imported in one place
  • String-based lookup - Icons referenced by string keys in flag configuration
  • Fallback handling - Defaults to Flag icon if specified icon not found
  • Type safety - TypeScript ensures only valid icon keys are used

Performance Considerations

Memoization

  • Column calculation - useMemo for expensive key extraction
  • Render optimization - Prevents unnecessary recalculations

Event Handling

  • Event delegation - Efficient handling of multiple interactive elements
  • Propagation control - stopPropagation() prevents unwanted event bubbling

Expansion State

  • Granular tracking - Individual expansion state for each expandable element
  • Unique identifiers - Prevents state conflicts between similar elements

Responsive Design

  • CSS Grid - Efficient responsive layouts
  • Sticky positioning - Maintains usability during scroll
  • Overflow handling - Graceful handling of wide content

These data view components provide flexible, performant ways to explore JSON data while maintaining consistent user experience patterns across different viewing modes.