Skip to content
7 changes: 5 additions & 2 deletions app/src/components/FloatingModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,18 @@ import React, { type ReactNode } from 'react';
import './FloatingModal.scss';

interface FloatingModalProps {
title: string;
title: ReactNode;
children: ReactNode;
onCloseModal: () => void;
// Optional extra class on the modal container, for per-use sizing overrides.
className?: string;
}

const FloatingModal: React.FC<FloatingModalProps> = ({
title,
children,
onCloseModal,
className,
}) => {
return (
<div className="modal-main">
Expand All @@ -35,7 +38,7 @@ const FloatingModal: React.FC<FloatingModalProps> = ({
onClick={onCloseModal}
/>
<div className="modal-holder">
<div className="modal-container">
<div className={`modal-container${className ? ` ${className}` : ''}`}>
<div className="header">
<div className="modal-title">{title}</div>
<div
Expand Down
83 changes: 82 additions & 1 deletion app/src/views/browse/MainBrowse.scss
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,91 @@
padding: 1rem 0;

.table-container {
grid-template-columns: 1fr 1fr 1fr auto auto;
// minmax(0, 1fr) (rather than 1fr) lets the flexible columns shrink to
// their share instead of being widened by long content like descriptions
// or dataset IDs, which would otherwise overflow the table.
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr) auto;
}

// Allow cells to shrink below their content's intrinsic width, and wrap long
// unbreakable tokens (e.g. dataset IDs) instead of overflowing.
.column {
min-width: 0;
overflow-wrap: anywhere;
}

.column.size {
text-align: right;
}

.description-cell {
display: flex;
align-items: flex-start;
gap: 0.5rem;
}

// Clamp long descriptions to a few lines (with an ellipsis) based on the
// available column width, rather than a fixed character count.
.description-text {
flex: 1;
min-width: 0;
display: -webkit-box;
-webkit-line-clamp: 3;
line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}

.expand-button {
flex: none;
display: inline-flex;
padding: 0.15rem;
border: none;
background: none;
color: vars.$linkblue;
cursor: pointer;
opacity: 0.6;

&:hover {
color: vars.$hoverblue;
opacity: 1;
}
}

.loading {
margin-top: 30vh;
}
}

// Description modal: size to its content (capped) instead of the shared modal's
// fixed height, so short descriptions don't leave empty space below.
.modal-main .modal-container.description-modal {
height: auto;
max-height: 80vh;
display: flex;
flex-direction: column;

.body {
height: auto;
flex: 1;
min-height: 0;
}
}

.modal-dataset-id {
font-family: monospace;
font-size: 1rem;
font-weight: 400;
margin-top: 0.25rem;
}

.modal-meta {
display: flex;
flex-wrap: wrap;
gap: 0.25rem 1.5rem;
margin: 0.5rem 0 1rem;
}

.modal-description {
white-space: pre-wrap;
}
82 changes: 72 additions & 10 deletions app/src/views/browse/MainBrowse.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import EntityTable from '../../components/EntityTable';
import FloatingModal from '../../components/FloatingModal';
import LoadingSpinner from '../../components/LoadingSpinner';
import './MainBrowse.scss';

Expand All @@ -28,6 +29,68 @@ interface Dataset {
submitted_at?: string | null;
}

// Description cell: the text is clamped to a few lines, and an always-present
// button opens a floating "card" modal with the dataset's full details. The
// modal overlay means the table layout never reflows.
const DescriptionCell: React.FC<{
datasetId: string;
name: string;
description?: string;
submittedAt?: string | null;
numReactions: number;
}> = ({ datasetId, name, description, submittedAt, numReactions }) => {
const [expanded, setExpanded] = useState(false);

return (
<div className="column description-cell">
<div className="description-text">{description}</div>
<button
type="button"
className="expand-button"
aria-label="Show dataset details"
title="Show dataset details"
onClick={() => setExpanded(true)}
>
<svg
viewBox="0 0 16 16"
width="14"
height="14"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<path d="M6 2H2v4M10 2h4v4M6 14H2v-4M10 14h4v-4" />
</svg>
</button>
{expanded && (
<FloatingModal
title={
<>
{name}
<div className="modal-dataset-id">{datasetId}</div>
</>
}
className="description-modal"
onCloseModal={() => setExpanded(false)}
>
<div className="modal-meta">
<span>
<strong>Submitted:</strong> {submittedAt ?? '—'}
</span>
<span>
<strong>Reactions:</strong> {numReactions.toLocaleString()}
</span>
</div>
<div className="modal-description">{description}</div>
</FloatingModal>
)}
</div>
);
};

const MainBrowse: React.FC = () => {
const [loading, setLoading] = useState(true);
const [tableData, setTableData] = useState<Dataset[]>([]);
Expand All @@ -45,11 +108,6 @@ const MainBrowse: React.FC = () => {
});
}, []);

const truncateDescription = (description?: string) => {
if (!description) return '';
return description.length > 75 ? description.substr(0, 75) + '...' : description;
};

if (loading) {
return (
<div id="browse-main">
Expand Down Expand Up @@ -81,17 +139,21 @@ const MainBrowse: React.FC = () => {
<div className="column label">Dataset ID</div>
<div className="column label">Name</div>
<div className="column label">Description</div>
<div className="column label">Size</div>
<div className="column label">Submitted</div>
<div className="column label size">Size</div>
{entities.map(row => (
<React.Fragment key={row.dataset_id}>
<div className="column">
<Link to={`/dataset/${row.dataset_id}`}>{row.dataset_id}</Link>
</div>
<div className="column">{row.name}</div>
<div className="column">{truncateDescription(row.description)}</div>
<div className="column">{row.num_reactions}</div>
<div className="column">{row.submitted_at ?? '—'}</div>
<DescriptionCell
datasetId={row.dataset_id}
name={row.name}
description={row.description}
submittedAt={row.submitted_at}
numReactions={row.num_reactions}
/>
<div className="column size">{row.num_reactions.toLocaleString()}</div>
</React.Fragment>
))}
</div>
Expand Down
Loading