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
10 changes: 6 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,16 @@
},
"dependencies": {
"@fluffylabs/migrate-selection": "^1.0.0",
"@fluffylabs/shared-ui": "^0.1.0",
"@fluffylabs/shared-ui": "^0.1.1",
"@fluffylabs/synctex-store": "^1.0.0",
"@radix-ui/react-dropdown-menu": "^2.1.15",
"@radix-ui/react-popover": "^1.1.14",
"@types/jspdf": "^1.3.3",
"class-variance-authority": "^0.7.1",
"dompurify": "^3.2.0",
"jspdf": "^3.0.1",
"katex": "^0.16.22",
"lucide-react": "^0.487.0",
"pdfjs-dist": "^4.5.136",
"react": "^19.1.0",
"react-dom": "^19.1.0",
Expand Down
5 changes: 4 additions & 1 deletion src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { Header as FluffyHeader } from "@fluffylabs/shared-ui";
import type React from "react";
import { Version } from "../Version";
import toolLogoUrl from "./../../assets/tool-logo.svg";

export const Header: React.FC = () => {
return <FluffyHeader toolNameSrc={toolLogoUrl} ghRepoName="graypaper-reader" keepNameWhenSmall />;
return (
<FluffyHeader toolNameSrc={toolLogoUrl} ghRepoName="graypaper-reader" keepNameWhenSmall endSlot={<Version />} />
);
};
2 changes: 0 additions & 2 deletions src/components/Sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { Outline } from "../Outline/Outline";
import { Search } from "../Search/Search";
import { Selection } from "../Selection/Selection";
import { Tabs } from "../Tabs/Tabs";
import { Version } from "../Version/Version";

export function Sidebar() {
const [tab, setTab] = useState(loadActiveTab());
Expand Down Expand Up @@ -52,7 +51,6 @@ export function Sidebar() {
<div className="content">
<Selection activeTab={tab} switchTab={setTab} />
<Tabs tabs={tabs} activeTab={tab} switchTab={setTab} alwaysRender />
<Version />
</div>
</div>
);
Expand Down
25 changes: 0 additions & 25 deletions src/components/Version/Version.css

This file was deleted.

96 changes: 67 additions & 29 deletions src/components/Version/Version.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { Button } from "@fluffylabs/shared-ui";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuTrigger,
} from "@fluffylabs/shared-ui";
import { ChevronDown } from "lucide-react";
import { useCallback, useContext, useRef } from "react";
import { Tooltip } from "react-tooltip";
import "./Version.css";
import { type ChangeEventHandler, useCallback, useContext } from "react";
import { CodeSyncContext, type ICodeSyncContext } from "../CodeSyncProvider/CodeSyncProvider";
import { type ILocationContext, LocationContext } from "../LocationProvider/LocationProvider";
import { type IMetadataContext, type IVersionInfo, MetadataContext } from "../MetadataProvider/MetadataProvider";
Expand All @@ -11,10 +19,12 @@ export function Version() {
const { migrateSelection } = useContext(CodeSyncContext) as ICodeSyncContext;
const versions = Object.values(metadata.versions).filter(({ legacy }) => !legacy);
const currentVersionHash = metadata.versions[locationParams.version].hash;
const currentVersion = metadata.versions[locationParams.version];
const dropdownContentRef = useRef<HTMLDivElement>(null);
const currentItemRef = useRef<HTMLDivElement>(null);

const handleChange = useCallback<ChangeEventHandler<HTMLSelectElement>>(
(e) => {
const newVersion = e.target.value;
const handleVersionSelect = useCallback(
(newVersion: string) => {
const { selectionStart, selectionEnd, version } = locationParams;
if (!selectionStart || !selectionEnd) {
setLocationParams({ version: newVersion });
Expand All @@ -32,41 +42,69 @@ export function Version() {
[setLocationParams, locationParams, migrateSelection],
);

const getCurrentVersionLabel = () => {
const date = new Date(currentVersion.date);
const isLatest = currentVersion.hash === metadata.latest;
let label = isLatest ? "Latest" : "v";
if (currentVersion.name) {
label += `: ${currentVersion.name}`;
}
return `${label} ${shortHash(currentVersion.hash)} (${date.toLocaleDateString()})`;
};

const handleOpenChange = (open: boolean) => {
if (open) {
requestAnimationFrame(() => {
if (currentItemRef.current && dropdownContentRef.current) {
currentItemRef.current.scrollIntoView({ block: "center", behavior: "instant" });
}
});
}
};
Comment on lines +55 to +63
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix invalid scrollIntoView option: "instant" is not supported

ScrollIntoViewOptions.behavior only accepts "auto" or "smooth". "instant" will cause a TS error and is ignored by browsers.

Apply this diff:

-          currentItemRef.current.scrollIntoView({ block: "center", behavior: "instant" });
+          currentItemRef.current.scrollIntoView({ block: "center", behavior: "auto" });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleOpenChange = (open: boolean) => {
if (open) {
requestAnimationFrame(() => {
if (currentItemRef.current && dropdownContentRef.current) {
currentItemRef.current.scrollIntoView({ block: "center", behavior: "instant" });
}
});
}
};
const handleOpenChange = (open: boolean) => {
if (open) {
requestAnimationFrame(() => {
if (currentItemRef.current && dropdownContentRef.current) {
currentItemRef.current.scrollIntoView({ block: "center", behavior: "auto" });
}
});
}
};
🤖 Prompt for AI Agents
In src/components/Version/Version.tsx around lines 55 to 63, the call to
scrollIntoView uses an invalid behavior value "instant" which causes a
TypeScript error and is ignored by browsers; replace "instant" with a valid
option ("auto" or "smooth") — for example use { block: "center", behavior:
"auto" } — and ensure the argument matches the ScrollIntoViewOptions type (no
casts needed) so the code compiles and browsers perform the expected scroll.


return (
<div className="version">
<div className="flex items-center justify-end gap-2 mx-4">
{currentVersionHash !== metadata.latest && (
<span
data-tooltip-id="version"
data-tooltip-content="The current version is not the latest."
data-tooltip-content="The current version is not the latest"
data-tooltip-place="top"
className="icon"
className="text-amber-500 text-2xl mt-[-2px]"
>
</span>
)}
<select onChange={handleChange} value={locationParams.version}>
{versions.map((v) => (
<Option key={v.hash} id={v.hash} version={v} latest={metadata.latest} />
))}
</select>
<a
data-tooltip-id="version"
data-tooltip-content="Open Gray Paper github commit."
data-tooltip-place="top"
target="_blank"
href={`https://github.com/gavofyork/graypaper/commit/${currentVersionHash}`}
rel="noreferrer"
className="default-link"
>
Github
</a>
<Tooltip id="version" />
<DropdownMenu onOpenChange={handleOpenChange}>
<DropdownMenuTrigger asChild>
<Button variant="outline" forcedColorScheme="dark" className="flex-1 justify-between h-[32px]">
<span className="px-2">{getCurrentVersionLabel()}</span>
<ChevronDown className="ml-2 h-5 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent ref={dropdownContentRef} className="max-h-[60vh] overflow-y-auto" forcedColorScheme="dark">
<DropdownMenuRadioGroup value={currentVersionHash} onValueChange={handleVersionSelect}>
{versions.map((version) => (
<DropdownMenuRadioItem
value={version.hash}
key={version.hash}
ref={version.hash === currentVersionHash ? currentItemRef : null}
>
<VersionOption version={version} latest={metadata.latest} />
</DropdownMenuRadioItem>
))}
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
<Tooltip
id="version"
style={{ backgroundColor: "oklch(76.9% 0.188 70.08)", zIndex: 1, color: "black", fontSize: "10px" }}
/>
</div>
);
}

type OptionProps = { id: string; version: IVersionInfo; latest: string };
function Option({ id, version, latest }: OptionProps) {
type VersionOptionProps = { version: IVersionInfo; latest: string };
function VersionOption({ version, latest }: VersionOptionProps) {
const date = new Date(version.date);
let latestText = "Latest";
let versionText = "v";
Expand All @@ -75,9 +113,9 @@ function Option({ id, version, latest }: OptionProps) {
versionText += `: ${version.name}`;
}
return (
<option value={id}>
<span className="w-full">
{version.hash === latest ? latestText : versionText} {shortHash(version.hash)} ({date.toLocaleDateString()})
</option>
</span>
);
}

Expand Down
1 change: 1 addition & 0 deletions src/components/Version/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Version } from "./Version";
7 changes: 5 additions & 2 deletions src/components/ui/button/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
forcedColorScheme?: "light" | "dark";
}

export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
({ className, variant, size, forcedColorScheme, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return <Comp className={twMerge(buttonVariants({ variant, size, className }))} ref={ref} {...props} />;
return (
<Comp className={twMerge(buttonVariants({ variant, size, className, forcedColorScheme }))} ref={ref} {...props} />
);
},
);
Button.displayName = "Button";
14 changes: 13 additions & 1 deletion src/components/ui/button/variants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const buttonVariants = cva(
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
outlineBrand:
"border border-input border-brand-dark dark:border-brand dark:text-brand hover:bg-accent hover:text-accent-foreground",
"border border-input border-brand-dark dark:border-brand dark:text-brand hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-black hover:text-background dark:hover:bg-brand dark:hover:text-background",
ghost: "hover:bg-accent hover:text-accent-foreground",
Expand All @@ -21,7 +21,19 @@ export const buttonVariants = cva(
lg: "h-11 rounded-md px-8",
icon: "h-4 w-4 -mt-2",
},
forcedColorScheme: {
light: "",
dark: "dark",
},
},
compoundVariants: [
{
forcedColorScheme: "dark",
variant: "outline",
class:
"bg-[var(--card)] border-[var(--border)] text-[var(--title-foreground)] hover:bg-[var(--title)] hover:text-[var(--title-foreground)] focus-visible:ring-[var(--brand)] focus-visible:ring-offset-[var(--card)]",
},
],
defaultVariants: {
variant: "default",
size: "default",
Expand Down