WinslowFan Claude Opus 4.8 commited on
Commit
5ec3025
·
1 Parent(s): 3acde0a

Mobile chain-detail layout: stack in portrait, wrap long content

Browse files

Stack Chain Identifier and chain metadata vertically in portrait; give the
detail cards min-w-0 and wrap long sequences / experimental details / binders
so they no longer overflow their cards on narrow screens.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

frontend/src/components/datasets/protein/ExperimentalConditionsPanel.tsx CHANGED
@@ -4,7 +4,7 @@
4
  */
5
  import React, { useState } from 'react';
6
  import { ChainMetadata } from '../../../types/protein';
7
- import { formatText, formatPH, formatTemperature, truncateText } from '../../../utils/format';
8
 
9
  interface ExperimentalConditionsPanelProps {
10
  metadata: ChainMetadata | null;
@@ -89,12 +89,12 @@ const ExperimentalConditionsPanel: React.FC<ExperimentalConditionsPanelProps> =
89
  <dd className="text-sm font-semibold text-slate-900">{formattedTemp}</dd>
90
  </div>
91
 
92
- <div className="flex justify-between items-center py-2 border-b border-slate-100">
93
  {formattedDetails && (
94
- <div className="experimental-item experimental-details">
95
  <dt className="text-xs font-medium text-slate-500 uppercase tracking-wider">Details</dt>
96
  <dd className="text-sm font-semibold text-slate-900">
97
- <div className="details-content">
98
  {showFullDetails ? formattedDetails : detailsPreview}
99
  </div>
100
  {hasLongDetails && (
 
4
  */
5
  import React, { useState } from 'react';
6
  import { ChainMetadata } from '../../../types/protein';
7
+ import { formatText, formatPH, formatTemperature, truncateText } from '../../../utils/format';
8
 
9
  interface ExperimentalConditionsPanelProps {
10
  metadata: ChainMetadata | null;
 
89
  <dd className="text-sm font-semibold text-slate-900">{formattedTemp}</dd>
90
  </div>
91
 
92
+ <div className="py-2 border-b border-slate-100">
93
  {formattedDetails && (
94
+ <div className="experimental-item experimental-details min-w-0">
95
  <dt className="text-xs font-medium text-slate-500 uppercase tracking-wider">Details</dt>
96
  <dd className="text-sm font-semibold text-slate-900">
97
+ <div className="details-content wrap-anywhere mt-1">
98
  {showFullDetails ? formattedDetails : detailsPreview}
99
  </div>
100
  {hasLongDetails && (
frontend/src/components/datasets/protein/ProteinDashboard.tsx CHANGED
@@ -275,8 +275,8 @@ export function ProteinDashboard() {
275
  <main className="space-y-6">
276
  {hasActiveChainSearch && activeChainMetadata ? (
277
  <>
278
- {/* First Row: Search + Chain Information (left-right 3:7 split) */}
279
- <div className="grid gap-6 items-stretch" style={{ gridTemplateColumns: 'minmax(0, 3fr) minmax(0, 7fr)' }}>
280
  <section className="min-w-0 h-full border border-gray-200 rounded-2xl p-6 bg-white shadow-sm hover:border-slate-900 hover:shadow-lg hover:-translate-y-0.5 transition-all duration-300">
281
  <FilterPanel
282
  filterOptions={filterOptions}
@@ -304,18 +304,18 @@ export function ProteinDashboard() {
304
 
305
  {/* Second Row: Sequence, Experimental Conditions, Binders (3 columns, equal height) */}
306
  <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
307
- <div className="border border-gray-200 rounded-2xl p-6 bg-white shadow-sm hover:border-slate-900 hover:shadow-lg hover:-translate-y-0.5 transition-all duration-300">
308
  <ErrorBoundary componentName="Sequence Panel">
309
  <SequencePanel metadata={activeChainMetadata} />
310
  </ErrorBoundary>
311
  </div>
312
- <div className="border border-gray-200 rounded-2xl p-6 bg-white shadow-sm hover:border-slate-900 hover:shadow-lg hover:-translate-y-0.5 transition-all duration-300">
313
  <ErrorBoundary componentName="Experimental Conditions">
314
  <ExperimentalConditionsPanel metadata={activeChainMetadata} />
315
  </ErrorBoundary>
316
  </div>
317
  {activeChainMetadata.nonpolymer_entities && activeChainMetadata.nonpolymer_entities.length > 0 && (
318
- <div className="border border-gray-200 rounded-2xl p-6 bg-white shadow-sm hover:border-slate-900 hover:shadow-lg hover:-translate-y-0.5 transition-all duration-300">
319
  <ErrorBoundary componentName="Binders Panel">
320
  <BindersPanel metadata={activeChainMetadata} />
321
  </ErrorBoundary>
 
275
  <main className="space-y-6">
276
  {hasActiveChainSearch && activeChainMetadata ? (
277
  <>
278
+ {/* First Row: Search + Chain Information (3:7 in landscape, stacked in portrait) */}
279
+ <div className="grid gap-6 items-stretch chain-detail-grid">
280
  <section className="min-w-0 h-full border border-gray-200 rounded-2xl p-6 bg-white shadow-sm hover:border-slate-900 hover:shadow-lg hover:-translate-y-0.5 transition-all duration-300">
281
  <FilterPanel
282
  filterOptions={filterOptions}
 
304
 
305
  {/* Second Row: Sequence, Experimental Conditions, Binders (3 columns, equal height) */}
306
  <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
307
+ <div className="min-w-0 border border-gray-200 rounded-2xl p-6 bg-white shadow-sm hover:border-slate-900 hover:shadow-lg hover:-translate-y-0.5 transition-all duration-300">
308
  <ErrorBoundary componentName="Sequence Panel">
309
  <SequencePanel metadata={activeChainMetadata} />
310
  </ErrorBoundary>
311
  </div>
312
+ <div className="min-w-0 border border-gray-200 rounded-2xl p-6 bg-white shadow-sm hover:border-slate-900 hover:shadow-lg hover:-translate-y-0.5 transition-all duration-300">
313
  <ErrorBoundary componentName="Experimental Conditions">
314
  <ExperimentalConditionsPanel metadata={activeChainMetadata} />
315
  </ErrorBoundary>
316
  </div>
317
  {activeChainMetadata.nonpolymer_entities && activeChainMetadata.nonpolymer_entities.length > 0 && (
318
+ <div className="min-w-0 border border-gray-200 rounded-2xl p-6 bg-white shadow-sm hover:border-slate-900 hover:shadow-lg hover:-translate-y-0.5 transition-all duration-300">
319
  <ErrorBoundary componentName="Binders Panel">
320
  <BindersPanel metadata={activeChainMetadata} />
321
  </ErrorBoundary>
frontend/src/components/datasets/protein/SequencePanel.tsx CHANGED
@@ -111,11 +111,11 @@ const SequencePanel: React.FC<SequencePanelProps> = ({ metadata, isLoading }) =>
111
  </div>
112
 
113
  {/* Sequence Display */}
114
- <div className="bg-slate-50 border border-slate-200 rounded-lg p-4 font-mono text-xs max-h-80 overflow-y-auto">
115
  {displayLines.map((line, idx) => (
116
  <div key={idx} className="flex mb-1 leading-relaxed">
117
- <span className="text-gray-400 mr-4 min-w-[50px] text-right select-none">{idx * 60 + 1}</span>
118
- <span className="text-gray-800 tracking-wide break-all">{line}</span>
119
  </div>
120
  ))}
121
  </div>
 
111
  </div>
112
 
113
  {/* Sequence Display */}
114
+ <div className="bg-slate-50 border border-slate-200 rounded-lg p-4 font-mono text-xs max-h-80 overflow-y-auto overflow-x-hidden">
115
  {displayLines.map((line, idx) => (
116
  <div key={idx} className="flex mb-1 leading-relaxed">
117
+ <span className="text-gray-400 mr-4 min-w-[50px] shrink-0 text-right select-none">{idx * 60 + 1}</span>
118
+ <span className="text-gray-800 tracking-wide wrap-anywhere">{line}</span>
119
  </div>
120
  ))}
121
  </div>
frontend/src/index.css CHANGED
@@ -3063,3 +3063,22 @@ body {
3063
  display: none;
3064
  }
3065
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3063
  display: none;
3064
  }
3065
  }
3066
+
3067
+ /* Chain detail top row: Chain Identifier + Chain metadata side by side
3068
+ (3:7) in landscape, stacked vertically in portrait (W < H). */
3069
+ .chain-detail-grid {
3070
+ grid-template-columns: minmax(0, 3fr) minmax(0, 7fr);
3071
+ }
3072
+ @media (orientation: portrait) {
3073
+ .chain-detail-grid {
3074
+ grid-template-columns: minmax(0, 1fr);
3075
+ }
3076
+ }
3077
+
3078
+ /* Let long unbroken content (amino-acid sequences, experimental details,
3079
+ SMILES, binder names) wrap instead of overflowing its card. */
3080
+ .wrap-anywhere {
3081
+ min-width: 0;
3082
+ overflow-wrap: anywhere;
3083
+ word-break: break-word;
3084
+ }