Skip to content

Commit 5451afb

Browse files
skearnesclaude
andauthored
Browse: clamp descriptions and add a dataset detail card; drop Submitted column (#201)
* Clamp browse descriptions by width instead of char count The browse table cut descriptions at a fixed 75 characters, which truncated well before the column ran out of room. Drop the JS truncation and clamp the description cell to three lines with a CSS ellipsis, so it fills the available width and adapts to the layout. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Stop caching index.html so deploys take effect immediately nginx served index.html with no Cache-Control, so browsers heuristically cached it and kept loading the previous build's hashed bundle until a hard refresh. Mark index.html no-cache (always revalidate) and the content-hashed /assets/ immutable so new deploys are picked up on the next navigation without serving stale assets. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Add an expand button to view full browse descriptions When a description is clamped, show a small expand button that opens the full text in a floating modal. The button only appears when the text is actually clipped (detected via ResizeObserver), and the modal overlay means the table layout never reflows. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Revert "Stop caching index.html so deploys take effect immediately" This reverts commit f822cd2. * Keep the browse table within its width The browse grid used `1fr` tracks, whose implicit `min-width: auto` let a long description (or dataset ID) widen its column and push the Size and Submitted columns off the right edge. Use `minmax(0, 1fr)` so the flexible columns shrink to their share, let cells shrink below content width, and wrap long unbreakable tokens. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Show dataset details in a card modal; drop the Submitted column The standalone "Submitted" column didn't earn its width, so remove it and surface the submission date (along with the dataset ID and size) in the description modal, which now reads as a dataset "card": - The description cell's expand button is always shown, so the card is always reachable (not just when the text is clamped). - The modal header shows the dataset name with the ID (monospace) beneath it; the body shows submitted date, reaction count, and full description. - Size the modal to its content (capped at 80vh) so short descriptions don't leave empty space. FloatingModal gains an optional `className` (for the content sizing) and its `title` now accepts a ReactNode (for the name + ID header). Datasets are still ordered newest-first by submission date server-side; only the visible column is removed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Format the browse Size column with thousands separators, right-aligned Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Apply Prettier formatting to MainBrowse Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent b4f4387 commit 5451afb

3 files changed

Lines changed: 159 additions & 13 deletions

File tree

app/src/components/FloatingModal.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,18 @@ import React, { type ReactNode } from 'react';
1818
import './FloatingModal.scss';
1919

2020
interface FloatingModalProps {
21-
title: string;
21+
title: ReactNode;
2222
children: ReactNode;
2323
onCloseModal: () => void;
24+
// Optional extra class on the modal container, for per-use sizing overrides.
25+
className?: string;
2426
}
2527

2628
const FloatingModal: React.FC<FloatingModalProps> = ({
2729
title,
2830
children,
2931
onCloseModal,
32+
className,
3033
}) => {
3134
return (
3235
<div className="modal-main">
@@ -35,7 +38,7 @@ const FloatingModal: React.FC<FloatingModalProps> = ({
3538
onClick={onCloseModal}
3639
/>
3740
<div className="modal-holder">
38-
<div className="modal-container">
41+
<div className={`modal-container${className ? ` ${className}` : ''}`}>
3942
<div className="header">
4043
<div className="modal-title">{title}</div>
4144
<div

app/src/views/browse/MainBrowse.scss

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,91 @@
2121
padding: 1rem 0;
2222

2323
.table-container {
24-
grid-template-columns: 1fr 1fr 1fr auto auto;
24+
// minmax(0, 1fr) (rather than 1fr) lets the flexible columns shrink to
25+
// their share instead of being widened by long content like descriptions
26+
// or dataset IDs, which would otherwise overflow the table.
27+
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr) auto;
28+
}
29+
30+
// Allow cells to shrink below their content's intrinsic width, and wrap long
31+
// unbreakable tokens (e.g. dataset IDs) instead of overflowing.
32+
.column {
33+
min-width: 0;
34+
overflow-wrap: anywhere;
35+
}
36+
37+
.column.size {
38+
text-align: right;
39+
}
40+
41+
.description-cell {
42+
display: flex;
43+
align-items: flex-start;
44+
gap: 0.5rem;
45+
}
46+
47+
// Clamp long descriptions to a few lines (with an ellipsis) based on the
48+
// available column width, rather than a fixed character count.
49+
.description-text {
50+
flex: 1;
51+
min-width: 0;
52+
display: -webkit-box;
53+
-webkit-line-clamp: 3;
54+
line-clamp: 3;
55+
-webkit-box-orient: vertical;
56+
overflow: hidden;
57+
}
58+
59+
.expand-button {
60+
flex: none;
61+
display: inline-flex;
62+
padding: 0.15rem;
63+
border: none;
64+
background: none;
65+
color: vars.$linkblue;
66+
cursor: pointer;
67+
opacity: 0.6;
68+
69+
&:hover {
70+
color: vars.$hoverblue;
71+
opacity: 1;
72+
}
2573
}
2674

2775
.loading {
2876
margin-top: 30vh;
2977
}
3078
}
79+
80+
// Description modal: size to its content (capped) instead of the shared modal's
81+
// fixed height, so short descriptions don't leave empty space below.
82+
.modal-main .modal-container.description-modal {
83+
height: auto;
84+
max-height: 80vh;
85+
display: flex;
86+
flex-direction: column;
87+
88+
.body {
89+
height: auto;
90+
flex: 1;
91+
min-height: 0;
92+
}
93+
}
94+
95+
.modal-dataset-id {
96+
font-family: monospace;
97+
font-size: 1rem;
98+
font-weight: 400;
99+
margin-top: 0.25rem;
100+
}
101+
102+
.modal-meta {
103+
display: flex;
104+
flex-wrap: wrap;
105+
gap: 0.25rem 1.5rem;
106+
margin: 0.5rem 0 1rem;
107+
}
108+
109+
.modal-description {
110+
white-space: pre-wrap;
111+
}

app/src/views/browse/MainBrowse.tsx

Lines changed: 72 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import React, { useState, useEffect } from 'react';
1818
import { Link } from 'react-router-dom';
1919
import EntityTable from '../../components/EntityTable';
20+
import FloatingModal from '../../components/FloatingModal';
2021
import LoadingSpinner from '../../components/LoadingSpinner';
2122
import './MainBrowse.scss';
2223

@@ -28,6 +29,68 @@ interface Dataset {
2829
submitted_at?: string | null;
2930
}
3031

32+
// Description cell: the text is clamped to a few lines, and an always-present
33+
// button opens a floating "card" modal with the dataset's full details. The
34+
// modal overlay means the table layout never reflows.
35+
const DescriptionCell: React.FC<{
36+
datasetId: string;
37+
name: string;
38+
description?: string;
39+
submittedAt?: string | null;
40+
numReactions: number;
41+
}> = ({ datasetId, name, description, submittedAt, numReactions }) => {
42+
const [expanded, setExpanded] = useState(false);
43+
44+
return (
45+
<div className="column description-cell">
46+
<div className="description-text">{description}</div>
47+
<button
48+
type="button"
49+
className="expand-button"
50+
aria-label="Show dataset details"
51+
title="Show dataset details"
52+
onClick={() => setExpanded(true)}
53+
>
54+
<svg
55+
viewBox="0 0 16 16"
56+
width="14"
57+
height="14"
58+
fill="none"
59+
stroke="currentColor"
60+
strokeWidth="1.5"
61+
strokeLinecap="round"
62+
strokeLinejoin="round"
63+
aria-hidden="true"
64+
>
65+
<path d="M6 2H2v4M10 2h4v4M6 14H2v-4M10 14h4v-4" />
66+
</svg>
67+
</button>
68+
{expanded && (
69+
<FloatingModal
70+
title={
71+
<>
72+
{name}
73+
<div className="modal-dataset-id">{datasetId}</div>
74+
</>
75+
}
76+
className="description-modal"
77+
onCloseModal={() => setExpanded(false)}
78+
>
79+
<div className="modal-meta">
80+
<span>
81+
<strong>Submitted:</strong> {submittedAt ?? '—'}
82+
</span>
83+
<span>
84+
<strong>Reactions:</strong> {numReactions.toLocaleString()}
85+
</span>
86+
</div>
87+
<div className="modal-description">{description}</div>
88+
</FloatingModal>
89+
)}
90+
</div>
91+
);
92+
};
93+
3194
const MainBrowse: React.FC = () => {
3295
const [loading, setLoading] = useState(true);
3396
const [tableData, setTableData] = useState<Dataset[]>([]);
@@ -45,11 +108,6 @@ const MainBrowse: React.FC = () => {
45108
});
46109
}, []);
47110

48-
const truncateDescription = (description?: string) => {
49-
if (!description) return '';
50-
return description.length > 75 ? description.substr(0, 75) + '...' : description;
51-
};
52-
53111
if (loading) {
54112
return (
55113
<div id="browse-main">
@@ -81,17 +139,21 @@ const MainBrowse: React.FC = () => {
81139
<div className="column label">Dataset ID</div>
82140
<div className="column label">Name</div>
83141
<div className="column label">Description</div>
84-
<div className="column label">Size</div>
85-
<div className="column label">Submitted</div>
142+
<div className="column label size">Size</div>
86143
{entities.map(row => (
87144
<React.Fragment key={row.dataset_id}>
88145
<div className="column">
89146
<Link to={`/dataset/${row.dataset_id}`}>{row.dataset_id}</Link>
90147
</div>
91148
<div className="column">{row.name}</div>
92-
<div className="column">{truncateDescription(row.description)}</div>
93-
<div className="column">{row.num_reactions}</div>
94-
<div className="column">{row.submitted_at ?? '—'}</div>
149+
<DescriptionCell
150+
datasetId={row.dataset_id}
151+
name={row.name}
152+
description={row.description}
153+
submittedAt={row.submitted_at}
154+
numReactions={row.num_reactions}
155+
/>
156+
<div className="column size">{row.num_reactions.toLocaleString()}</div>
95157
</React.Fragment>
96158
))}
97159
</div>

0 commit comments

Comments
 (0)