feat: WCAG AA accessibility, code splitting, responsive ads layout

- Add React.lazy code splitting for all 15 tool pages
- Fix WCAG AA contrast issues (304 text color fixes)
- Add ARIA labels and aria-expanded to navigation buttons
- Add aria-live for error announcements in tools
- Implement responsive ad layout:
  - Desktop (≥1280px): Right sidebar with 3 ad units
  - Tablet (1024-1279px): Bottom section with 3 horizontal units
  - Mobile (<1024px): Fixed bottom banner
- Add TabletAdSection component for tablet ad placement
- Integrate Onidel affiliate partnership
- Update all Adsterra domains to solutionbiologyisle.com
- Add release notes for 2026-02-18 updates
This commit is contained in:
dwindown
2026-02-18 18:57:31 +07:00
parent 9dc3285adb
commit 3a475e9df2
41 changed files with 391 additions and 318 deletions

View File

@@ -1863,7 +1863,7 @@ const TableEditor = () => {
className={`flex items-center gap-1 sm:gap-2 px-3 sm:px-4 py-3 text-sm font-medium transition-colors whitespace-nowrap ${
activeTab === "create"
? "bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300 border-b-2 border-blue-500"
: "text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200"
: "text-gray-600 dark:text-gray-600 hover:text-gray-900 dark:hover:text-gray-200"
}`}
>
<Plus className="h-4 w-4" />
@@ -1874,7 +1874,7 @@ const TableEditor = () => {
className={`flex items-center gap-1 sm:gap-2 px-3 sm:px-4 py-3 text-sm font-medium transition-colors whitespace-nowrap ${
activeTab === "url"
? "bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300 border-b-2 border-blue-500"
: "text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200"
: "text-gray-600 dark:text-gray-600 hover:text-gray-900 dark:hover:text-gray-200"
}`}
>
<Globe className="h-4 w-4" />
@@ -1885,7 +1885,7 @@ const TableEditor = () => {
className={`flex items-center gap-1 sm:gap-2 px-3 sm:px-4 py-3 text-sm font-medium transition-colors whitespace-nowrap ${
activeTab === "paste"
? "bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300 border-b-2 border-blue-500"
: "text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200"
: "text-gray-600 dark:text-gray-600 hover:text-gray-900 dark:hover:text-gray-200"
}`}
>
<FileText className="h-4 w-4" />
@@ -1896,7 +1896,7 @@ const TableEditor = () => {
className={`flex items-center gap-1 sm:gap-2 px-3 sm:px-4 py-3 text-sm font-medium transition-colors whitespace-nowrap ${
activeTab === "upload"
? "bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300 border-b-2 border-blue-500"
: "text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200"
: "text-gray-600 dark:text-gray-600 hover:text-gray-900 dark:hover:text-gray-200"
}`}
>
<Upload className="h-4 w-4" />
@@ -1914,7 +1914,7 @@ const TableEditor = () => {
<h3 className="text-lg font-medium text-gray-900 dark:text-gray-100 mb-2">
Start Building Your Table
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4">
<p className="text-sm text-gray-600 dark:text-gray-600 mb-4">
Choose how you'd like to begin working with your data
</p>
</div>
@@ -1932,11 +1932,11 @@ const TableEditor = () => {
}}
className="flex flex-col items-center p-6 border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg hover:border-blue-500 dark:hover:border-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/20 transition-colors group"
>
<Plus className="h-8 w-8 text-gray-400 group-hover:text-blue-500 dark:group-hover:text-blue-400 mb-2" />
<Plus className="h-8 w-8 text-gray-600 group-hover:text-blue-500 dark:group-hover:text-blue-400 mb-2" />
<span className="font-medium text-gray-900 dark:text-gray-100 group-hover:text-blue-600 dark:group-hover:text-blue-400">
Start Empty
</span>
<span className="text-xs text-gray-500 dark:text-gray-400 text-center mt-1">
<span className="text-xs text-gray-600 dark:text-gray-600 text-center mt-1">
Create a blank table with basic columns
</span>
</button>
@@ -2005,11 +2005,11 @@ const TableEditor = () => {
}}
className="flex flex-col items-center p-6 border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg hover:border-green-500 dark:hover:border-green-400 hover:bg-green-50 dark:hover:bg-green-900/20 transition-colors group"
>
<FileText className="h-8 w-8 text-gray-400 group-hover:text-green-500 dark:group-hover:text-green-400 mb-2" />
<FileText className="h-8 w-8 text-gray-600 group-hover:text-green-500 dark:group-hover:text-green-400 mb-2" />
<span className="font-medium text-gray-900 dark:text-gray-100 group-hover:text-green-600 dark:group-hover:text-green-400">
Load Sample
</span>
<span className="text-xs text-gray-500 dark:text-gray-400 text-center mt-1">
<span className="text-xs text-gray-600 dark:text-gray-600 text-center mt-1">
Start with example data to explore features
</span>
</button>
@@ -2055,7 +2055,7 @@ const TableEditor = () => {
{url && !isLoading && (
<button
onClick={() => setUrl("")}
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300 transition-colors"
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-600 hover:text-gray-600 dark:text-gray-600 dark:hover:text-gray-300 transition-colors"
>
<X className="h-4 w-4" />
</button>
@@ -2069,7 +2069,7 @@ const TableEditor = () => {
{isLoading ? "Fetching..." : "Fetch Data"}
</button>
</div>
<label className="flex items-center text-sm text-gray-600 dark:text-gray-400">
<label className="flex items-center text-sm text-gray-600 dark:text-gray-600">
<input
type="checkbox"
checked={useFirstRowAsHeader}
@@ -2118,7 +2118,7 @@ const TableEditor = () => {
</div>
)}
<div className="flex items-center justify-between flex-shrink-0">
<label className="flex items-center text-sm text-gray-600 dark:text-gray-400">
<label className="flex items-center text-sm text-gray-600 dark:text-gray-600">
<input
type="checkbox"
checked={useFirstRowAsHeader}
@@ -2161,7 +2161,7 @@ const TableEditor = () => {
onChange={handleFileUpload}
className="tool-input"
/>
<label className="flex items-center text-sm text-gray-600 dark:text-gray-400">
<label className="flex items-center text-sm text-gray-600 dark:text-gray-600">
<input
type="checkbox"
checked={useFirstRowAsHeader}
@@ -2204,7 +2204,7 @@ const TableEditor = () => {
{availableTables.length > 1 ? "Multi-Table Database" : "Table Editor"}
</h3>
{availableTables.length === 1 && (
<p className="text-sm text-gray-600 dark:text-gray-400">
<p className="text-sm text-gray-600 dark:text-gray-600">
{data.length} rows, {columns.length} columns
</p>
)}
@@ -2219,7 +2219,7 @@ const TableEditor = () => {
className={`flex items-center gap-2 px-3 py-2 text-sm font-medium transition-colors ${
isTableFullscreen
? "bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300"
: "text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700"
: "text-gray-600 dark:text-gray-600 hover:text-gray-900 dark:hover:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700"
}`}
>
{isTableFullscreen ? (
@@ -2233,7 +2233,7 @@ const TableEditor = () => {
</button>
<button
onClick={clearData}
className="flex items-center gap-2 px-3 py-2 text-sm font-medium transition-colors border-l border-gray-200 dark:border-gray-700 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700"
className="flex items-center gap-2 px-3 py-2 text-sm font-medium transition-colors border-l border-gray-200 dark:border-gray-700 text-gray-600 dark:text-gray-600 hover:text-gray-900 dark:hover:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700"
>
<AlertTriangle className="h-4 w-4" />
<span className="hidden sm:inline">Clear All</span>
@@ -2246,7 +2246,7 @@ const TableEditor = () => {
{availableTables.length > 1 && (
<div className="px-4 py-3 flex flex-col sm:flex-row items-start sm:items-center gap-3 sm:gap-4 border-b border-gray-200 dark:border-gray-700 justify-between">
<div className="flex items-center gap-2 w-full sm:max-w-1/2">
<span className="text-sm text-gray-600 dark:text-gray-400 whitespace-nowrap hidden sm:inline">
<span className="text-sm text-gray-600 dark:text-gray-600 whitespace-nowrap hidden sm:inline">
Current Table:
</span>
<select
@@ -2267,7 +2267,7 @@ const TableEditor = () => {
})}
</select>
</div>
<p className="text-sm text-gray-600 dark:text-gray-400">
<p className="text-sm text-gray-600 dark:text-gray-600">
{data.length} rows, {columns.length} columns
</p>
</div>
@@ -2279,7 +2279,7 @@ const TableEditor = () => {
<div className="px-4 py-3 flex flex-col sm:flex-row items-start sm:items-center gap-3 sm:gap-4 border-b border-gray-200 dark:border-gray-700">
{/* Search Bar */}
<div className="relative w-full">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-600" />
<input
type="text"
value={searchTerm}
@@ -2319,7 +2319,7 @@ const TableEditor = () => {
{/* Freeze Columns Control */}
<div className="flex items-center gap-2">
<span className="text-xs sm:text-sm text-gray-600 dark:text-gray-400 whitespace-nowrap">
<span className="text-xs sm:text-sm text-gray-600 dark:text-gray-600 whitespace-nowrap">
Freeze:
</span>
<select
@@ -2354,7 +2354,7 @@ const TableEditor = () => {
<thead className="bg-gray-50 dark:bg-gray-700 sticky top-[-1px] z-10">
<tr>
<th
className={`px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 tracking-wider border-r border-gray-200 dark:border-gray-600 ${
className={`px-4 py-3 text-left text-xs font-medium text-gray-600 dark:text-gray-300 tracking-wider border-r border-gray-200 dark:border-gray-600 ${
frozenColumns > 0
? "sticky left-0 z-20 bg-blue-50 dark:!bg-blue-900"
: ""
@@ -2390,7 +2390,7 @@ const TableEditor = () => {
return (
<th
key={column.id}
className={`relative px-4 py-3 text-left text-sm font-medium text-gray-500 dark:text-gray-300 tracking-wider hover:bg-gray-100 dark:hover:bg-gray-600 border-r border-gray-200 dark:border-gray-600 ${
className={`relative px-4 py-3 text-left text-sm font-medium text-gray-600 dark:text-gray-300 tracking-wider hover:bg-gray-100 dark:hover:bg-gray-600 border-r border-gray-200 dark:border-gray-600 ${
isFrozen
? "sticky z-20 bg-blue-50 dark:!bg-blue-900"
: ""
@@ -2450,7 +2450,7 @@ const TableEditor = () => {
className={`h-4 w-4 flex-shrink-0 ${
sortConfig.key === column.id
? "text-blue-600 dark:text-blue-400"
: "text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
: "text-gray-600 hover:text-gray-600 dark:hover:text-gray-300"
}`}
/>
</button>
@@ -2471,7 +2471,7 @@ const TableEditor = () => {
<th className="px-4 py-3 text-center border-l-2 border-dashed border-gray-300 dark:border-gray-600 hover:bg-blue-50 dark:hover:bg-blue-900/20 w-[60px]">
<button
onClick={addColumn}
className="flex items-center justify-center text-gray-500 hover:text-blue-600 p-2 rounded-lg transition-colors group"
className="flex items-center justify-center text-gray-600 hover:text-blue-600 p-2 rounded-lg transition-colors group"
title="Add new column"
>
<Plus className="h-4 w-4 group-hover:scale-110 transition-transform" />
@@ -2632,7 +2632,7 @@ const TableEditor = () => {
>
<span className="truncate block w-full">
{cellValue || (
<span className="text-gray-400 dark:text-gray-500 italic text-sm">
<span className="text-gray-600 dark:text-gray-600 italic text-sm">
Click to edit
</span>
)}
@@ -2662,7 +2662,7 @@ const TableEditor = () => {
>
<button
onClick={addRow}
className="flex items-center justify-center gap-2 text-gray-500 hover:text-blue-600 px-3 py-2 rounded-lg transition-colors group whitespace-nowrap sticky left-4"
className="flex items-center justify-center gap-2 text-gray-600 hover:text-blue-600 px-3 py-2 rounded-lg transition-colors group whitespace-nowrap sticky left-4"
title="Add new row"
>
<Plus className="h-4 w-4 group-hover:scale-110 transition-transform" />
@@ -2736,7 +2736,7 @@ const TableEditor = () => {
Export Results
{exportExpanded ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
</h3>
<div className="text-sm text-gray-600 dark:text-gray-400">
<div className="text-sm text-gray-600 dark:text-gray-600">
{availableTables.length > 1 ? (
<span>
Database: {originalFileName || "Multi-table"} (
@@ -2763,7 +2763,7 @@ const TableEditor = () => {
className={`flex items-center gap-1 sm:gap-2 px-3 sm:px-4 py-3 text-sm font-medium transition-colors whitespace-nowrap ${
exportTab === "json"
? "bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300 border-b-2 border-blue-500"
: "text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200"
: "text-gray-600 dark:text-gray-600 hover:text-gray-900 dark:hover:text-gray-200"
}`}
>
<Braces className="h-4 w-4" />
@@ -2774,7 +2774,7 @@ const TableEditor = () => {
className={`flex items-center gap-1 sm:gap-2 px-3 sm:px-4 py-3 text-sm font-medium transition-colors whitespace-nowrap ${
exportTab === "csv"
? "bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300 border-b-2 border-blue-500"
: "text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200"
: "text-gray-600 dark:text-gray-600 hover:text-gray-900 dark:hover:text-gray-200"
}`}
>
<FileText className="h-4 w-4" />
@@ -2785,7 +2785,7 @@ const TableEditor = () => {
className={`flex items-center gap-1 sm:gap-2 px-3 sm:px-4 py-3 text-sm font-medium transition-colors whitespace-nowrap ${
exportTab === "tsv"
? "bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300 border-b-2 border-blue-500"
: "text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200"
: "text-gray-600 dark:text-gray-600 hover:text-gray-900 dark:hover:text-gray-200"
}`}
>
<FileText className="h-4 w-4" />
@@ -2796,7 +2796,7 @@ const TableEditor = () => {
className={`flex items-center gap-1 sm:gap-2 px-3 sm:px-4 py-3 text-sm font-medium transition-colors whitespace-nowrap ${
exportTab === "sql"
? "bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300 border-b-2 border-blue-500"
: "text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200"
: "text-gray-600 dark:text-gray-600 hover:text-gray-900 dark:hover:text-gray-200"
}`}
>
<Database className="h-4 w-4" />
@@ -3318,7 +3318,7 @@ const ClearConfirmationModal = ({
<h4 className="text-sm font-medium text-gray-900 dark:text-gray-100 mb-2">
This will permanently delete:
</h4>
<ul className="text-sm text-gray-600 dark:text-gray-400 space-y-1">
<ul className="text-sm text-gray-600 dark:text-gray-600 space-y-1">
{tableCount > 1 ? (
<>
<li> {tableCount} tables</li>
@@ -3632,7 +3632,7 @@ const ObjectEditorModal = ({ modal, onClose, onApply }) => {
const renderVisualEditor = () => {
if (!isValid) {
return (
<div className="h-full flex items-center justify-center text-gray-500 dark:text-gray-400 p-6">
<div className="h-full flex items-center justify-center text-gray-600 dark:text-gray-600 p-6">
<div className="text-center">
<Code className="h-12 w-12 mx-auto mb-4 opacity-50" />
<p>Invalid or unparseable data</p>
@@ -3666,7 +3666,7 @@ const ObjectEditorModal = ({ modal, onClose, onApply }) => {
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
Object Editor
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400">
<p className="text-sm text-gray-600 dark:text-gray-600">
Row {modal.rowIndex} Column: {modal.columnName} Format:{" "}
{modal.format.type.replace("_", " ")}
</p>
@@ -3681,7 +3681,7 @@ const ObjectEditorModal = ({ modal, onClose, onApply }) => {
{isValid &&
structuredData &&
typeof structuredData === "object" && (
<span className="text-gray-600 dark:text-gray-400">
<span className="text-gray-600 dark:text-gray-600">
{" • "}{Object.keys(structuredData).length} properties
</span>
)}
@@ -3689,7 +3689,7 @@ const ObjectEditorModal = ({ modal, onClose, onApply }) => {
</div>
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 self-start"
className="text-gray-600 hover:text-gray-600 dark:hover:text-gray-300 self-start"
>
<X className="h-6 w-6" />
</button>
@@ -3704,7 +3704,7 @@ const ObjectEditorModal = ({ modal, onClose, onApply }) => {
className={`px-3 py-2 text-sm font-medium rounded-md transition-colors ${
viewMode === "visual"
? "bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300"
: "text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-200"
: "text-gray-600 hover:text-gray-900 dark:text-gray-600 dark:hover:text-gray-200"
}`}
>
<Edit3 className="h-4 w-4 inline mr-2" />
@@ -3715,7 +3715,7 @@ const ObjectEditorModal = ({ modal, onClose, onApply }) => {
className={`px-3 py-2 text-sm font-medium rounded-md transition-colors ${
viewMode === "raw"
? "bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300"
: "text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-200"
: "text-gray-600 hover:text-gray-900 dark:text-gray-600 dark:hover:text-gray-200"
}`}
>
<Code className="h-4 w-4 inline mr-2" />
@@ -3845,7 +3845,7 @@ const InputChangeConfirmationModal = ({
<h4 className="text-sm font-medium text-gray-900 dark:text-gray-100 mb-2">
This will permanently delete:
</h4>
<ul className="text-sm text-gray-600 dark:text-gray-400 space-y-1">
<ul className="text-sm text-gray-600 dark:text-gray-600 space-y-1">
{tableCount > 1 ? (
<>
<li> {tableCount} imported tables</li>