Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@tailwindcss/vite": "^4.0.9",
"axios": "^1.11.0",
"classnames": "^2.5.1",
"flowbite-react": "^0.12.7",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-icons": "^5.5.0",
Expand Down
5 changes: 4 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ function App() {
/>
<Route
path="*"
element={<NotFoundPage />}
element={
<Layout>
<NotFoundPage />
</Layout>}
/>
</Routes>
</BrowserRouter>
Expand Down
16 changes: 12 additions & 4 deletions src/components/ui/common-table.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import React from "react";
import React, { ReactElement } from "react";
import classNames from "classnames";
import { Hint } from "./hint";

export interface Column {
name: string;
renderCell?: (value: any) => React.ReactNode;
hint?: ReactElement;
}

interface CommonTableProps {
Expand Down Expand Up @@ -33,7 +35,7 @@ export const CommonTable: React.FC<CommonTableProps> = ({
}

if (value === undefined || value === null) {
return <span className="text-gray-400 italic">NULL</span>;
return <div />;
}

if (React.isValidElement(value)) {
Expand All @@ -44,7 +46,7 @@ export const CommonTable: React.FC<CommonTableProps> = ({
};

return (
<div className={classNames("w-full", className)}>
<div className={classNames("w-full z-0", className)}>
{children && (
<div className={classNames("mb-1 p-4 bg-gray-50 rounded-sm", headerClassName)}>
{children}
Expand All @@ -63,7 +65,13 @@ export const CommonTable: React.FC<CommonTableProps> = ({
columnHeaderClassName
)}
>
{column.name}
{column.hint ? (
<Hint hintContent={column.hint}>
<span>{column.name}</span>
</Hint>
) : (
column.name
)}
</th>
))}
</tr>
Expand Down
38 changes: 38 additions & 0 deletions src/components/ui/copy-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { ReactElement, useState } from "react";
import { Button } from "./button";
import { MdCheck, MdContentCopy } from "react-icons/md";

interface CopyButtonProps {
children: ReactElement;
textToCopy: string;
}

export const CopyButton: React.FC<CopyButtonProps> = ({ children, textToCopy }) => {
const [copied, setCopied] = useState(false);

const handleCopy = async () => {
try {
await navigator.clipboard.writeText(textToCopy);
setCopied(true);
setTimeout(() => setCopied(false), 1000);
} catch (err) {
console.error('Failed to copy text: ', err);
}
};

return (
<div className="font-mono group relative flex items-center justify-between">
<div>{children}</div>
<Button
onClick={handleCopy}
className="opacity-0 group-hover:opacity-100 transition-opacity"
>
{copied ? (
<MdCheck className="text-gray-400" />
) : (
<MdContentCopy className="text-gray-400" />
)}
</Button>
</div>
);
};
93 changes: 38 additions & 55 deletions src/components/ui/footer.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { useState } from "react";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import { Link as ReactDomLink } from "react-router-dom";
import { Button } from "./button";
import { Link } from "./link";
import { MdKeyboardArrowDown, MdKeyboardArrowUp } from "react-icons/md";

const footerContent = `
Information: https://hyperleda.github.io/

Old version: http://leda.univ-lyon1.fr/
`;
const footerContent = <div className="prose prose-invert prose-a:no-underline">
<div>Information: <Link href="https://hyperleda.github.io/" /></div>
<div>Old version: <Link href="http://leda.univ-lyon1.fr/" /></div>
</div>

export function Footer() {
const [isCollapsed, setIsCollapsed] = useState(true);
Expand All @@ -18,56 +18,39 @@ export function Footer() {

return (
<>
{isCollapsed ? (
<Button
onClick={toggleCollapse}
className="fixed bottom-4 right-4 z-10 transition-colors"
aria-label="Expand footer"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="text-white"
>
<polyline points="18 15 12 9 6 15"></polyline>
</svg>
</Button>
) : (
<footer className="fixed bottom-0 left-0 right-0 py-3 border-t bg-[#2a2a2a] border-gray-800 z-10 shadow-lg backdrop-blur-sm bg-opacity-95 mx-8 mb-3 rounded transition-all">
<div className="flex justify-between items-start">
<div className="max-w-4xl w-full mx-auto prose prose-invert leading-none prose-sm">
<ReactMarkdown remarkPlugins={[remarkGfm]}>
{footerContent}
</ReactMarkdown>
<Button
onClick={toggleCollapse}
className={`fixed bottom-4 right-4 z-10 transition-all duration-100 ease-in-out ${isCollapsed ? 'opacity-100 translate-y-0' : 'opacity-0 -translate-y-2 pointer-events-none'
}`}
aria-label="Expand footer"
>
<MdKeyboardArrowUp />
</Button>

<footer
className={`fixed bottom-0 border-1 left-0 right-0 py-3 z-10 shadow-lg backdrop-blur-sm bg-opacity-99 mx-10 mb-3 rounded transition-all duration-300 ease-in-out ${isCollapsed
? 'opacity-0 translate-y-full pointer-events-none'
: 'opacity-100 translate-y-0'
}`}
>
<div className="flex justify-between items-start">
<div className="max-w-4xl w-full mx-auto leading-none prose-sm">
<div className="flex items-center gap-4">
<ReactDomLink to="/">
<img
src="/logo.png"
alt="HyperLeda Logo"
className="h-8"
/>
</ReactDomLink>
{footerContent}
</div>
<Button
onClick={toggleCollapse}
className="text-gray-400 hover:text-white transition-colors mr-3"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</Button>
</div>
</footer>
)}
<Button onClick={toggleCollapse} className="mr-3">
<MdKeyboardArrowDown />
</Button>
</div>
</footer>
</>
);
}
22 changes: 22 additions & 0 deletions src/components/ui/hint.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Tooltip } from "flowbite-react";
import { ReactElement } from "react";
import { MdHelpOutline } from "react-icons/md";

interface HintProps {
children: ReactElement;
hintContent: ReactElement;
className?: string;
}

export const Hint: React.FC<HintProps> = ({ children, hintContent, className = "" }) => {
return (
<div className={`flex items-center justify-center gap-2 ${className}`}>
<div>{children}</div>
<div>
<Tooltip content={hintContent} arrow={false} placement="top" className="bg-gray-600 px-2 border-1 max-w-xl">
<MdHelpOutline />
</Tooltip>
</div>
</div >
);
};
13 changes: 13 additions & 0 deletions src/components/ui/link.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React, { ReactElement } from "react";

interface LinkProps {
children?: ReactElement | string;
href: string;
}

export const Link: React.FC<LinkProps> = ({ children, href }) => {
const content = children ?? href
return <a target="_blank" rel="noopener noreferrer" href={href} className="text-green-500 hover:text-green-600 transition-colors">
{content}
</a>
}
19 changes: 1 addition & 18 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,6 @@

font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}

body {
Expand All @@ -41,10 +30,4 @@ h1 {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}
}
47 changes: 32 additions & 15 deletions src/pages/Home.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,36 @@
import { useNavigate } from "react-router-dom";
import { SearchBar } from "../components/ui/searchbar";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import { ReactElement } from "react";
import { Link } from "../components/ui/link";

const homePageHint = `
Examples:
- Search by name: [name:IC1445](/query?q=name:IC1445)
- Search by PGC number: [pgc:112642](/query?q=pgc:112642)

The search conditions can be concatenated with AND or OR operators. For example:
- Search by name and PGC number: [name:IC1445 and pgc:112642](/query?q=name:IC1445%20AND%20pgc:112642)
- Search by name or PGC number: [name:IC4445 or pgc:87422](/query?q=name:IC1445%20OR%20pgc:112642)
`;
const homePageHint: ReactElement = (
<div>
<div>Examples:</div>
<ul>
<li>
Search by name: <Link href="/query?q=name:IC1445">name:IC1445</Link>
</li>
<li>
Search by PGC number: <Link href="/query?q=pgc:112642">pgc:112642</Link>
</li>
</ul>
<div>
The search conditions can be concatenated with AND or OR operators. For example:
</div>
<ul>
<li>
Search by name and PGC number: <Link href="/query?q=name:IC1445%20AND%20pgc:112642">
name:IC1445 and pgc:112642
</Link>
</li>
<li>
Search by name or PGC number: <Link href="/query?q=name:IC1445%20OR%20pgc:112642">
name:IC4445 or pgc:87422
</Link>
</li>
</ul>
</div>
);

export const HomePage: React.FC = () => {
const navigate = useNavigate();
Expand All @@ -23,10 +42,8 @@ export const HomePage: React.FC = () => {
return (
<div className="p-4">
<SearchBar onSearch={handleSearch} logoSize="large" />
<div className="max-w-4xl mx-auto mt-8 prose prose-invert leading-none">
<ReactMarkdown remarkPlugins={[remarkGfm]}>
{homePageHint}
</ReactMarkdown>
<div className="max-w-4xl mx-auto mt-8 prose prose-invert leading-none prose-a:no-underline">
{homePageHint}
</div>
</div>
);
Expand Down
Loading