Docker config
This commit is contained in:
41
app/components/SampleGuideline/ContentSection.jsx
Normal file
41
app/components/SampleGuideline/ContentSection.jsx
Normal file
@ -0,0 +1,41 @@
|
||||
// ContentSection.jsx - Simple with Left Sidebar
|
||||
'use client';
|
||||
import React, { useState } from 'react';
|
||||
import Sidebar from './Sidebar';
|
||||
import GeneralGuidelines from './GeneralGuidelines';
|
||||
import SearchSampleRequirements from './SearchSampleRequirements';
|
||||
|
||||
const ContentSection = () => {
|
||||
const [activeSection, setActiveSection] = useState('general-content');
|
||||
|
||||
const renderContent = () => {
|
||||
const sections = {
|
||||
'general-content': <GeneralGuidelines />,
|
||||
'search-content': <SearchSampleRequirements />
|
||||
};
|
||||
return sections[activeSection] || <GeneralGuidelines />;
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="pt-4 pb-8">
|
||||
<div className="container mx-auto max-w-none px-4">
|
||||
<div className="grid xl:grid-cols-[280px_1fr] gap-8">
|
||||
{/* LEFT SIDEBAR */}
|
||||
<div className="xl:sticky xl:top-8">
|
||||
<Sidebar
|
||||
activeSection={activeSection}
|
||||
onSectionChange={setActiveSection}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* RIGHT CONTENT */}
|
||||
<div className="bg-white rounded-lg">
|
||||
{renderContent()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default ContentSection;
|
||||
54
app/components/SampleGuideline/GeneralGuidelines.jsx
Normal file
54
app/components/SampleGuideline/GeneralGuidelines.jsx
Normal file
@ -0,0 +1,54 @@
|
||||
import React from 'react';
|
||||
|
||||
const GeneralGuidelines = () => {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="mb-6">
|
||||
<h3 className="text-2xl font-semibold text-gray-600 mb-4">General Guidelines</h3>
|
||||
</div>
|
||||
|
||||
<div className="prose max-w-none">
|
||||
<ul className="space-y-4 text-gray-700 leading-relaxed pl-5">
|
||||
<li className="list-disc">
|
||||
Please complete the Sample Initiation Form (SIF), ensuring that the
|
||||
sample names on the form match the labels on the sample tubes. We also
|
||||
request that you send an electronic copy of the form and any required QC
|
||||
data via email.
|
||||
</li>
|
||||
<li className="list-disc">
|
||||
Each tube should be labeled on the lid with a maximum of 4-6
|
||||
alphanumeric characters (e.g., 4B0001). Use a black permanent marker to
|
||||
write sample names on the top and side of each tube. Avoid writing
|
||||
directly on the tube wall or cover with an oil pen.
|
||||
</li>
|
||||
<li className="list-disc">
|
||||
DNA can be submitted in DNase-free water, Elution Buffer, or 10mM Tris
|
||||
pH 8.0. DNA samples should have an OD260/280 ratio as close to 1.8~2.0
|
||||
as possible. All DNA should be RNase-treated and free from degradation
|
||||
or contamination. Ship with ice packs. The total amount of DNA required
|
||||
depends on the specific application.
|
||||
</li>
|
||||
<li className="list-disc">
|
||||
RNA can be submitted in RNase-free water, RNA Stabilization Reagent, or
|
||||
10mM Tris pH 8.0. All total RNA samples should be DNA-free, with an OD
|
||||
A260/A280 ratio ≥ 1.8, A260/230 ratio ≥ 1.8, and a RIN ≥ 6. Ship with
|
||||
dry ice. The total amount of RNA required depends on the specific
|
||||
application. For Long Read Sequencing, RNA samples should have a RIN ≥
|
||||
8.
|
||||
</li>
|
||||
<li className="list-disc">
|
||||
The listed concentrations should be determined by fluorometry (e.g.,
|
||||
PicoGreen/Qubit/RiboGreen). If using spectrophotometry (e.g., Nanodrop),
|
||||
increase concentrations by approximately twofold.
|
||||
</li>
|
||||
<li className="list-disc">
|
||||
The quality inspection method for the sizes and concentrations of the
|
||||
Ready To Run Library is Qubit and Agilent Bioanalyzer.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default GeneralGuidelines;
|
||||
23
app/components/SampleGuideline/IntroSection.jsx
Normal file
23
app/components/SampleGuideline/IntroSection.jsx
Normal file
@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
|
||||
const IntroSection = () => {
|
||||
return (
|
||||
<section className="bg-white mx-4 md:mx-8 mt-4 mb-4 py-4">
|
||||
<div className="container mx-auto max-w-none px-4">
|
||||
<div className="text-gray-600 max-w-none leading-relaxed text-center">
|
||||
<div className="text-base text-justify">
|
||||
<p className="m-0">
|
||||
We humbly offer a wide range of services, including genomics, transcriptomics,
|
||||
metagenomics, epigenomics, single-cell sequencing, genotyping, microarray,
|
||||
bioinformatics, and more. To help us deliver the best results for you, we request you to
|
||||
review our sample requirements and follow the instructions for packaging, labeling, and
|
||||
shipping your samples.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default IntroSection;
|
||||
41
app/components/SampleGuideline/PageTitle.jsx
Normal file
41
app/components/SampleGuideline/PageTitle.jsx
Normal file
@ -0,0 +1,41 @@
|
||||
import React from 'react';
|
||||
|
||||
const PageTitle = () => {
|
||||
return (
|
||||
<section
|
||||
className="relative bg-cover bg-center py-4 sm:py-6 h-auto sm:h-32 md:h-40 lg:h-24 min-h-[120px] sm:min-h-[140px]"
|
||||
style={{ backgroundImage: "url('images/bredcrumb.jpg')" }}
|
||||
>
|
||||
{/* Breadcrumb */}
|
||||
<div className="relative z-10 mb-2 sm:mb-1 pt-2 sm:pt-0 sm:-mt-3 lg:-mt-3">
|
||||
<div className="container mx-auto max-w-none px-4">
|
||||
<nav className="flex flex-wrap items-center gap-1 sm:gap-2 text-xs sm:text-sm lg:text-sm">
|
||||
<a href="/" className="text-white hover:text-yellow-400 underline whitespace-nowrap">Home</a>
|
||||
<span className="text-white flex-shrink-0">
|
||||
<svg className="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clipRule="evenodd" />
|
||||
</svg>
|
||||
</span>
|
||||
<a href="/sample-submission-guideline" className="text-white hover:text-yellow-400 underline whitespace-nowrap">Knowledge Hub</a>
|
||||
<span className="text-white flex-shrink-0">
|
||||
<svg className="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clipRule="evenodd" />
|
||||
</svg>
|
||||
</span>
|
||||
<span className="text-white">Sample Submission Guideline</span>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Page Title */}
|
||||
<div className="relative z-10 text-center pb-2 sm:pb-0 sm:-mt-2 lg:mt-4">
|
||||
<h1 className="text-lg sm:text-2xl md:text-3xl lg:text-4xl xl:text-4xl font-bold text-white mb-2 px-4 leading-tight">
|
||||
Sample Submission Guideline
|
||||
</h1>
|
||||
<div className="w-12 sm:w-14 md:w-16 lg:w-16 h-1 bg-yellow-400 mx-auto"></div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default PageTitle;
|
||||
15
app/components/SampleGuideline/SampleGuidelinePage.jsx
Normal file
15
app/components/SampleGuideline/SampleGuidelinePage.jsx
Normal file
@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
import PageTitle from './PageTitle';
|
||||
import IntroSection from './IntroSection';
|
||||
import ContentSection from './ContentSection';
|
||||
|
||||
const SampleGuidelinePage = () => {
|
||||
return (
|
||||
<div className="page-content">
|
||||
<PageTitle />
|
||||
<IntroSection />
|
||||
<ContentSection />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default SampleGuidelinePage;
|
||||
154
app/components/SampleGuideline/SearchFilters.jsx
Normal file
154
app/components/SampleGuideline/SearchFilters.jsx
Normal file
@ -0,0 +1,154 @@
|
||||
'use client';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { sequencingData } from './sequencingData';
|
||||
import TooltipIcon from './TooltipIcon';
|
||||
|
||||
const SearchFilters = ({ filters, onFilterChange, onSearch, onReset, isSearchEnabled }) => {
|
||||
const [availableOptions, setAvailableOptions] = useState({
|
||||
nucleicAcid: [],
|
||||
category: [],
|
||||
application: [],
|
||||
sampleType: []
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
updateAvailableOptions();
|
||||
}, [filters]);
|
||||
|
||||
const updateAvailableOptions = () => {
|
||||
// Get nucleic acid options
|
||||
const nucleicAcids = [...new Set(sequencingData.map(item => item.nucleicAcid))];
|
||||
|
||||
// Filter data based on current selections
|
||||
let filteredData = sequencingData;
|
||||
if (filters.nucleicAcid) {
|
||||
filteredData = filteredData.filter(item => item.nucleicAcid === filters.nucleicAcid);
|
||||
}
|
||||
|
||||
const categories = [...new Set(filteredData.map(item => item.category))];
|
||||
|
||||
// Further filter for applications
|
||||
if (filters.category) {
|
||||
filteredData = filteredData.filter(item => item.category === filters.category);
|
||||
}
|
||||
|
||||
const applications = [...new Set(filteredData.map(item => item.application))];
|
||||
|
||||
// Further filter for sample types
|
||||
if (filters.application) {
|
||||
filteredData = filteredData.filter(item => item.application === filters.application);
|
||||
}
|
||||
|
||||
const sampleTypes = [...new Set(filteredData.map(item => item.sampleType))];
|
||||
|
||||
setAvailableOptions({
|
||||
nucleicAcid: nucleicAcids.sort(),
|
||||
category: categories.sort(),
|
||||
application: applications.sort(),
|
||||
sampleType: sampleTypes.sort()
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-white p-6 rounded-lg shadow-sm border">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{/* Nucleic Acid */}
|
||||
<div className="space-y-2">
|
||||
<label className="flex items-center text-sm font-semibold text-gray-700">
|
||||
Nucleic Acid
|
||||
</label>
|
||||
<select
|
||||
value={filters.nucleicAcid}
|
||||
onChange={(e) => onFilterChange('nucleicAcid', e.target.value)}
|
||||
style={{ color: '#555555' }}
|
||||
className="w-full p-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
>
|
||||
<option value="" style={{ color: '#555555' }}>Select</option>
|
||||
{availableOptions.nucleicAcid.map(option => (
|
||||
<option key={option} value={option} style={{ color: '#555555' }}>{option}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Category */}
|
||||
<div className="space-y-2">
|
||||
<label className="flex items-center text-sm font-semibold text-gray-700">
|
||||
Category
|
||||
</label>
|
||||
<select
|
||||
value={filters.category}
|
||||
onChange={(e) => onFilterChange('category', e.target.value)}
|
||||
disabled={!filters.nucleicAcid}
|
||||
style={{ color: '#555555' }}
|
||||
className="w-full p-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:bg-gray-100 disabled:cursor-not-allowed"
|
||||
>
|
||||
<option value="" style={{ color: '#555555' }}>{filters.nucleicAcid ? 'Select' : 'Select Previous Option First'}</option>
|
||||
{availableOptions.category.map(option => (
|
||||
<option key={option} value={option} style={{ color: '#555555' }}>{option}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Applications */}
|
||||
<div className="space-y-2">
|
||||
<label className="flex items-center text-sm font-semibold text-gray-700">
|
||||
Applications
|
||||
<TooltipIcon text="Lists all the applications for the sample." />
|
||||
</label>
|
||||
<select
|
||||
value={filters.application}
|
||||
onChange={(e) => onFilterChange('application', e.target.value)}
|
||||
disabled={!filters.category}
|
||||
style={{ color: '#555555' }}
|
||||
className="w-full p-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:bg-gray-100 disabled:cursor-not-allowed"
|
||||
>
|
||||
<option value="" style={{ color: '#555555' }}>{filters.category ? 'Select' : 'Select Previous Option First'}</option>
|
||||
{availableOptions.application.map(option => (
|
||||
<option key={option} value={option} style={{ color: '#555555' }}>{option}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Sample Type */}
|
||||
<div className="space-y-2">
|
||||
<label className="flex items-center text-sm font-semibold text-gray-700">
|
||||
Sample Type
|
||||
<TooltipIcon text="Describes the nature of the sample. Common sample types are listed, though some categories might not be included." />
|
||||
</label>
|
||||
<select
|
||||
value={filters.sampleType}
|
||||
onChange={(e) => onFilterChange('sampleType', e.target.value)}
|
||||
disabled={!filters.application}
|
||||
style={{ color: '#555555' }}
|
||||
className="w-full p-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:bg-gray-100 disabled:cursor-not-allowed"
|
||||
>
|
||||
<option value="" style={{ color: '#555555' }}>{filters.application ? 'Select' : 'Select Previous Option First'}</option>
|
||||
{availableOptions.sampleType.map(option => (
|
||||
<option key={option} value={option} style={{ color: '#555555' }}>{option}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Buttons */}
|
||||
<div className="flex justify-end items-center space-x-4 mt-6">
|
||||
<button
|
||||
onClick={onReset}
|
||||
className="px-4 py-2 text-gray-600 hover:text-gray-800 transition-colors"
|
||||
title="Reset filters"
|
||||
>
|
||||
✖
|
||||
</button>
|
||||
<button
|
||||
onClick={onSearch}
|
||||
disabled={!isSearchEnabled}
|
||||
className="px-6 py-2 bg-teal-600 text-white font-semibold rounded-md hover:bg-teal-700 disabled:bg-gray-300 disabled:cursor-not-allowed transition-colors"
|
||||
>
|
||||
Search
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SearchFilters;
|
||||
72
app/components/SampleGuideline/SearchResults.jsx
Normal file
72
app/components/SampleGuideline/SearchResults.jsx
Normal file
@ -0,0 +1,72 @@
|
||||
import React from 'react';
|
||||
import TooltipIcon from './TooltipIcon';
|
||||
|
||||
const SearchResults = ({ results }) => {
|
||||
if (results.length === 0) {
|
||||
return (
|
||||
<div className="mt-6">
|
||||
<h2 className="text-2xl font-semibold text-teal-600 mb-4">Search Result</h2>
|
||||
<div className="text-center py-8 text-gray-600 bg-gray-50 rounded-lg">
|
||||
Please Select Valid Information
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mt-6 space-y-4">
|
||||
<h2 className="text-2xl font-semibold text-teal-600 mb-4">Search Result</h2>
|
||||
|
||||
{results.map((item, index) => (
|
||||
<div key={index} className="bg-teal-50 p-6 rounded-lg shadow-sm">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<ResultItem
|
||||
title="Recommended Quantity"
|
||||
value={item.recommendedQuantity}
|
||||
tooltip="Indicates the suggested amount of sample for optimal results."
|
||||
/>
|
||||
<ResultItem
|
||||
title="Minimum Quantity"
|
||||
value={item.minimumQuantity}
|
||||
tooltip="Specifies the minimum amount of sample that can be processed."
|
||||
/>
|
||||
<ResultItem
|
||||
title="Minimum Concentration"
|
||||
value={item.minimumConcentration}
|
||||
tooltip="States the lowest concentration of nucleic acid required for processing."
|
||||
/>
|
||||
<ResultItem
|
||||
title="Platform"
|
||||
value={item.platform}
|
||||
tooltip="Identifies the sequencing or analysis platform. This category is applicable only for Ready To Run Library (RTRL)."
|
||||
/>
|
||||
<ResultItem
|
||||
title="Data Amount"
|
||||
value={item.dataAmount}
|
||||
tooltip="Provides an estimate of the data output. This category is applicable only for Ready To Run Library (RTRL)."
|
||||
/>
|
||||
<ResultItem
|
||||
title="Volume Requirement"
|
||||
value={item.volumeRequirement}
|
||||
tooltip="Specifies the necessary volume of the sample for submission. This category is applicable only for Ready To Run Library (RTRL)."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ResultItem = ({ title, value, tooltip }) => (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center text-gray-600 text-sm">
|
||||
{title}
|
||||
<TooltipIcon text={tooltip} />
|
||||
</div>
|
||||
<div className="font-semibold text-gray-900 text-base">
|
||||
{value || 'N/A'}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default SearchResults;
|
||||
88
app/components/SampleGuideline/SearchSampleRequirements.jsx
Normal file
88
app/components/SampleGuideline/SearchSampleRequirements.jsx
Normal file
@ -0,0 +1,88 @@
|
||||
'use client';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { sequencingData } from './sequencingData';
|
||||
import SearchFilters from './SearchFilters';
|
||||
import SearchResults from './SearchResults';
|
||||
|
||||
const SearchSampleRequirements = () => {
|
||||
const [filters, setFilters] = useState({
|
||||
nucleicAcid: '',
|
||||
category: '',
|
||||
application: '',
|
||||
sampleType: ''
|
||||
});
|
||||
|
||||
const [filteredResults, setFilteredResults] = useState([]);
|
||||
const [showResults, setShowResults] = useState(false);
|
||||
|
||||
const handleFilterChange = (filterName, value) => {
|
||||
setFilters(prev => {
|
||||
const newFilters = { ...prev, [filterName]: value };
|
||||
|
||||
// Reset subsequent filters when a parent filter changes
|
||||
const filterOrder = ['nucleicAcid', 'category', 'application', 'sampleType'];
|
||||
const currentIndex = filterOrder.indexOf(filterName);
|
||||
|
||||
for (let i = currentIndex + 1; i < filterOrder.length; i++) {
|
||||
newFilters[filterOrder[i]] = '';
|
||||
}
|
||||
|
||||
return newFilters;
|
||||
});
|
||||
};
|
||||
|
||||
const handleSearch = () => {
|
||||
let results = sequencingData;
|
||||
|
||||
if (filters.nucleicAcid) {
|
||||
results = results.filter(item => item.nucleicAcid === filters.nucleicAcid);
|
||||
}
|
||||
if (filters.category) {
|
||||
results = results.filter(item => item.category === filters.category);
|
||||
}
|
||||
if (filters.application) {
|
||||
results = results.filter(item => item.application === filters.application);
|
||||
}
|
||||
if (filters.sampleType) {
|
||||
results = results.filter(item => item.sampleType === filters.sampleType);
|
||||
}
|
||||
|
||||
setFilteredResults(results);
|
||||
setShowResults(true);
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setFilters({
|
||||
nucleicAcid: '',
|
||||
category: '',
|
||||
application: '',
|
||||
sampleType: ''
|
||||
});
|
||||
setFilteredResults([]);
|
||||
setShowResults(false);
|
||||
};
|
||||
|
||||
const isSearchEnabled = filters.nucleicAcid && filters.category && filters.application && filters.sampleType;
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="mb-6">
|
||||
<h3 className="text-2xl font-semibold text-gray-600">Search Sample Requirements</h3>
|
||||
</div>
|
||||
|
||||
<SearchFilters
|
||||
filters={filters}
|
||||
onFilterChange={handleFilterChange}
|
||||
onSearch={handleSearch}
|
||||
onReset={handleReset}
|
||||
isSearchEnabled={isSearchEnabled}
|
||||
/>
|
||||
|
||||
{showResults && (
|
||||
<SearchResults results={filteredResults} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SearchSampleRequirements;
|
||||
43
app/components/SampleGuideline/Sidebar.jsx
Normal file
43
app/components/SampleGuideline/Sidebar.jsx
Normal file
@ -0,0 +1,43 @@
|
||||
import React from 'react';
|
||||
|
||||
const Sidebar = ({ activeSection, onSectionChange }) => {
|
||||
const menuItems = [
|
||||
{ id: 'general-content', label: '1. General Guidelines' },
|
||||
{ id: 'search-content', label: '2. Search Sample Requirements' }
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
{menuItems.map((item) => (
|
||||
<button
|
||||
key={item.id}
|
||||
onClick={() => onSectionChange(item.id)}
|
||||
className={`w-full text-left px-6 py-4 text-base font-medium rounded-full transition-all duration-300 border-2 whitespace-nowrap ${
|
||||
activeSection === item.id
|
||||
? 'text-white shadow-lg border-transparent'
|
||||
: 'bg-white text-gray-600 border-transparent hover:border-2'
|
||||
}`}
|
||||
style={
|
||||
activeSection === item.id
|
||||
? { backgroundColor: '#ffa72a' }
|
||||
: {}
|
||||
}
|
||||
onMouseEnter={(e) => {
|
||||
if (activeSection !== item.id) {
|
||||
e.target.style.borderColor = '#ffa72a';
|
||||
}
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
if (activeSection !== item.id) {
|
||||
e.target.style.borderColor = 'transparent';
|
||||
}
|
||||
}}
|
||||
>
|
||||
{item.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Sidebar;
|
||||
27
app/components/SampleGuideline/TooltipIcon.jsx
Normal file
27
app/components/SampleGuideline/TooltipIcon.jsx
Normal file
@ -0,0 +1,27 @@
|
||||
'use client';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
const TooltipIcon = ({ text }) => {
|
||||
const [showTooltip, setShowTooltip] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="relative inline-block ml-2">
|
||||
<span
|
||||
className="inline-flex items-center justify-center w-4 h-4 text-xs font-bold text-teal-600 border-2 border-teal-600 rounded-full cursor-pointer hover:bg-teal-600 hover:text-white transition-all duration-200"
|
||||
onMouseEnter={() => setShowTooltip(true)}
|
||||
onMouseLeave={() => setShowTooltip(false)}
|
||||
>
|
||||
i
|
||||
</span>
|
||||
|
||||
{showTooltip && (
|
||||
<div className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-3 py-2 text-xs text-white bg-gray-800 rounded-md shadow-lg z-50 w-56 text-left">
|
||||
{text}
|
||||
<div className="absolute top-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-l-4 border-r-4 border-t-4 border-transparent border-t-gray-800"></div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TooltipIcon;
|
||||
1382
app/components/SampleGuideline/sequencingData.js
Normal file
1382
app/components/SampleGuideline/sequencingData.js
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user