Skip to content

Update image handling #14564

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
76137dd
Add image lightbox with Radix UI Dialog and improved image handling
cursoragent Aug 5, 2025
222fbb2
Refactor DocImage components and improve type ordering
cursoragent Aug 5, 2025
1d4c770
[getsentry/action-github-commit] Auto commit
getsantry[bot] Aug 5, 2025
970049b
remove alt text overlay
jaffrepaul Aug 6, 2025
2e98bdc
fix default browser behavior
jaffrepaul Aug 6, 2025
041b40d
more specific classes
jaffrepaul Aug 6, 2025
b5470a6
fix external image bug, update to pass all props, & ensure fallback s…
jaffrepaul Aug 6, 2025
12ca95c
refactor to fix event handling & propagation issues
jaffrepaul Aug 6, 2025
a636625
[getsentry/action-github-commit] Auto commit
getsantry[bot] Aug 6, 2025
11227e4
remove anchors
jaffrepaul Aug 6, 2025
528b31a
prevent browser context menu from downloading images instead of openi…
jaffrepaul Aug 6, 2025
0bd0bae
fix window parameter & click behavior cursor bug
jaffrepaul Aug 7, 2025
37fd8ce
[getsentry/action-github-commit] Auto commit
getsantry[bot] Aug 7, 2025
19db25b
cleanup
jaffrepaul Aug 7, 2025
7b781f4
add basic a11y
jaffrepaul Aug 7, 2025
106d776
[getsentry/action-github-commit] Auto commit
getsantry[bot] Aug 7, 2025
50455a3
update button styles
jaffrepaul Aug 7, 2025
6c6b8a1
refactor to simplify logic
jaffrepaul Aug 7, 2025
19e598e
prop and types improvements
jaffrepaul Aug 7, 2025
c74e563
[getsentry/action-github-commit] Auto commit
getsantry[bot] Aug 7, 2025
9bffc3c
remove DocImageClient to simplify and scope styles
jaffrepaul Aug 8, 2025
4065b0c
port image logic to imageLightbox
jaffrepaul Aug 8, 2025
c68596c
dry things up
jaffrepaul Aug 8, 2025
30c260b
fix alt prop bug
jaffrepaul Aug 8, 2025
5a7c1be
[getsentry/action-github-commit] Auto commit
getsantry[bot] Aug 8, 2025
7ff34e8
fix type narrowing issue
jaffrepaul Aug 8, 2025
19b1e92
extend next config for external images
jaffrepaul Aug 8, 2025
02a849e
[getsentry/action-github-commit] Auto commit
getsantry[bot] Aug 8, 2025
ca1f744
gpt5 update 💪
jaffrepaul Aug 8, 2025
d6081b0
[getsentry/action-github-commit] Auto commit
getsantry[bot] Aug 8, 2025
896228e
fix image edgecase bug
jaffrepaul Aug 8, 2025
052325c
add PR feedback
jaffrepaul Aug 8, 2025
0770228
better abstraction refactor & cleanup
jaffrepaul Aug 8, 2025
4be0b7e
Update package.json
jaffrepaul Aug 8, 2025
689e57f
bugbot smash
jaffrepaul Aug 8, 2025
5dc9bcd
delegate basic events to radix, keep modifier logic
jaffrepaul Aug 8, 2025
b2fecbf
bugbot fix
jaffrepaul Aug 9, 2025
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
57 changes: 57 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,60 @@ body {
content: "Step " counter(onboarding-step) ": ";
font-weight: inherit;
}

/* Lightbox animations */
@keyframes dialog-content-show {
from {
opacity: 0;
transform: translate(-50%, -48%) scale(0.96);
}
to {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
}
}

@keyframes dialog-content-hide {
from {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
}
to {
opacity: 0;
transform: translate(-50%, -48%) scale(0.96);
}
}

@keyframes dialog-overlay-show {
from {
opacity: 0;
}
to {
opacity: 1;
}
}

@keyframes dialog-overlay-hide {
from {
opacity: 1;
}
to {
opacity: 0;
}
}

[data-state="open"] {
animation: dialog-content-show 200ms cubic-bezier(0.16, 1, 0.3, 1);
}

[data-state="closed"] {
animation: dialog-content-hide 200ms cubic-bezier(0.16, 1, 0.3, 1);
}

.dialog-overlay[data-state="open"] {
animation: dialog-overlay-show 200ms cubic-bezier(0.16, 1, 0.3, 1);
}

.dialog-overlay[data-state="closed"] {
animation: dialog-overlay-hide 200ms cubic-bezier(0.16, 1, 0.3, 1);
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"@prettier/plugin-xml": "^3.3.1",
"@radix-ui/colors": "^3.0.0",
"@radix-ui/react-collapsible": "^1.1.1",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-icons": "^1.3.2",
"@radix-ui/react-tabs": "^1.1.1",
Expand Down
26 changes: 11 additions & 15 deletions src/components/docImage.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import path from 'path';

import Image from 'next/image';

import {serverContext} from 'sentry-docs/serverContext';

import {DocImageClient} from './docImageClient';

export default function DocImage({
src,
...props
Expand Down Expand Up @@ -40,18 +40,14 @@ export default function DocImage({
.map(s => parseInt(s, 10));

return (
<a href={imgPath} target="_blank" rel="noreferrer">
<Image
{...props}
src={src}
width={width}
height={height}
style={{
width: '100%',
height: 'auto',
}}
alt={props.alt ?? ''}
/>
</a>
<DocImageClient
src={src}
imgPath={imgPath}
width={width}
height={height}
alt={props.alt ?? ''}
style={props.style}
className={props.className}
/>
);
}
63 changes: 63 additions & 0 deletions src/components/docImageClient.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
'use client';

import Image from 'next/image';

import {ImageLightbox} from './imageLightbox';

interface DocImageClientProps {
alt: string;
height: number;
imgPath: string;
src: string;
width: number;
className?: string;
style?: React.CSSProperties;
}

export function DocImageClient({
src,
imgPath,
width,
height,
alt,
style,
className,
}: DocImageClientProps) {
const handleContextMenu = (e: React.MouseEvent) => {
e.preventDefault(); // Prevent default context menu
// Allow right-click to open in new tab
const link = document.createElement('a');
link.href = imgPath;
link.target = '_blank';
link.rel = 'noreferrer';
link.click();
};

const handleClick = (e: React.MouseEvent) => {
// If Ctrl/Cmd+click, open in new tab instead of lightbox
if (e.ctrlKey || e.metaKey) {
e.preventDefault();
e.stopPropagation();
window.open(imgPath, '_blank', 'noreferrer');
}
};

return (
<div onContextMenu={handleContextMenu} onClick={handleClick}>
<ImageLightbox src={src} alt={alt} width={width} height={height}>
<Image
src={src}
width={width}
height={height}
style={{
width: '100%',
height: 'auto',
...style,
}}
className={className}
alt={alt}
/>
</ImageLightbox>
</div>
);
}
56 changes: 56 additions & 0 deletions src/components/imageLightbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
'use client';

import {useState} from 'react';
import {X} from 'react-feather';
import * as Dialog from '@radix-ui/react-dialog';
import Image from 'next/image';

interface ImageLightboxProps {
alt: string;
children: React.ReactNode;
height: number;
src: string;
width: number;
}

export function ImageLightbox({src, alt, width, height, children}: ImageLightboxProps) {
const [open, setOpen] = useState(false);

return (
<Dialog.Root open={open} onOpenChange={setOpen}>
<Dialog.Trigger asChild>
<button className="cursor-pointer border-none bg-transparent p-0 block w-full">
{children}
</button>
</Dialog.Trigger>

<Dialog.Portal>
<Dialog.Overlay className="dialog-overlay fixed inset-0 bg-black/80 backdrop-blur-sm z-50" />

<Dialog.Content className="fixed left-[50%] top-[50%] z-50 max-h-[90vh] max-w-[90vw] translate-x-[-50%] translate-y-[-50%]">
{/* Close button */}
<Dialog.Close className="absolute right-4 top-4 z-10 rounded-sm bg-black/50 p-2 text-white opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</Dialog.Close>

{/* Image container */}
<div className="relative flex items-center justify-center">
<Image
src={src}
alt={alt}
width={width}
height={height}
className="max-h-[90vh] max-w-[90vw] object-contain"
style={{
width: 'auto',
height: 'auto',
}}
priority
/>
</div>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
}
Loading