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.