Skip to content

feat(Gallery): add inline view and controlled active item index#397

Merged
kseniya57 merged 1 commit into
gravity-ui:mainfrom
artemnikotin:feat/gallery-contained-and-controlled-index
Jul 13, 2026
Merged

feat(Gallery): add inline view and controlled active item index#397
kseniya57 merged 1 commit into
gravity-ui:mainfrom
artemnikotin:feat/gallery-contained-and-controlled-index

Conversation

@artemnikotin

@artemnikotin artemnikotin commented Jul 6, 2026

Copy link
Copy Markdown
Contributor
  • add view prop ('modal' default | 'inline') to render the gallery in place instead of a modal
  • add controlled activeItemIndex / onActiveItemIndexChange,
    backed by a local useConditionallyControlledState
  • add GalleryItem.id to keep item previews reconciled by identity

I hereby agree to the terms of the CLA available at: link.

@artemnikotin artemnikotin force-pushed the feat/gallery-contained-and-controlled-index branch from 6ceafa5 to cfe3760 Compare July 7, 2026 07:31
@artemnikotin artemnikotin marked this pull request as ready for review July 7, 2026 07:34
@artemnikotin artemnikotin requested a review from kseniya57 as a code owner July 7, 2026 07:34
@artemnikotin artemnikotin force-pushed the feat/gallery-contained-and-controlled-index branch from cfe3760 to 0e794b9 Compare July 8, 2026 11:27
@artemnikotin artemnikotin force-pushed the feat/gallery-contained-and-controlled-index branch from 0e794b9 to 5e50e2c Compare July 8, 2026 11:39
Comment thread src/components/Gallery/README.md Outdated
| open | `Boolean` | | | | `modal` only. The modal opened state |
| onOpenChange | `(open: boolean) => void` | | | | `modal` only. The modal toggle handler |
| container | `HTMLElement` | | | | `modal` only. The modal container |
| onClose | `() => void` | | | | `inline` only. Called on the header close/back button; the button is hidden when omitted |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

onOpenChange and onClose: when using the component, it will seem that these props do the same thing

I think that for inline mode, onClose and rendering an additional header with a close button are unnecessary

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agree, removed.

Comment thread src/components/Gallery/README.md Outdated

```tsx
{
open && (

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please remove this flag from the example, the goal here is to show that the gallery can be rendered inline, not to show how its conditional rendering can be implemented

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch, I forgot to remove it after the previous version.

Comment thread src/components/Gallery/Gallery.scss Outdated
}

&_view_inline {
position: absolute;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think it's a good idea to set such position rules; they should be set by the user for a specific use case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed, styling the inline gallery's container is the consumer's responsibility. Updated.

Comment thread src/components/Gallery/Gallery.tsx Outdated

export type GalleryProps = GalleryModalProps | GalleryInlineProps;

function resolveViewProps(props: GalleryProps) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Let's move this to Gallery/utils/resolveViewProps, so this file will contain only the component

But in general, this function seems to be unnecessary, because the props type already excludes the passing of unnecessary properties, and instead of isInline, it is better to use view === inline directly, as this constant does not make readability better, it is equivalent to

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You're right, removed the unnecessary helpers.

Comment thread src/components/Gallery/Gallery.tsx Outdated
* - `modal` — open the gallery in a viewport-sized modal overlay (default).
* - `inline` — render the gallery in place, filling its parent container.
*/
export type GalleryView = 'modal' | 'inline';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Let's move this to Gallery/types.ts

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

Comment thread src/components/Gallery/Gallery.tsx Outdated
}, [onOpenChange]);

const handleClose = React.useCallback(() => {
if (isInline) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is superfluous when we remove onClose.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yep, done

@gravity-ui-bot

Copy link
Copy Markdown
Contributor

Preview is ready.

@artemnikotin artemnikotin force-pushed the feat/gallery-contained-and-controlled-index branch 2 times, most recently from ca3c517 to 9423eab Compare July 9, 2026 13:33
@@ -0,0 +1,31 @@
import * as React from 'react';

// Copied from `@gravity-ui/uikit`'s private `useStateWithCallback` hook:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think we don't need this information

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I see, replaced with hook description.


import {useStateWithCallback} from '../useStateWithCallback';

// Copied from `@gravity-ui/uikit`'s private `useConditionallyControlledState` hook:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think we don't need this information

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I see, replaced with hook description.

Comment thread src/components/Gallery/Gallery.tsx Outdated
open,
onOpenChange,
container,
} = props;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Let's return immediate props destructuring

export const Gallery = ({ ... }: GalleryProps) => {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

Comment thread src/components/Gallery/Gallery.tsx Outdated
container,
} = props;

const inlineRef = React.useRef<HTMLDivElement>(null);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Let's rename this to something like inlineGalleryContainerRef

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sure, done

Comment thread src/components/Gallery/README.md Outdated
- **Swipe Gestures**: Mobile swipe navigation (automatically disabled during zoom interaction)
- **Fullscreen Mode**: Toggle fullscreen view
- **Custom Actions**: Add custom action buttons for each gallery item
- **Contained Mode**: Render the gallery inside its `container` bounds instead of over the whole viewport

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe Inline view will be clearer

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agree, done

container?: undefined;
};

export type GalleryProps = GalleryModalProps | GalleryInlineProps;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Seems like this will be a braking change since only modal view properties could be used (as in the GalleryProvider) , there will be an error through the new union type

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It's not a breaking change - existing modal usage keeps compiling:

  • GalleryModalProps keeps view?: 'modal' optional, so old call sites with no view (<Gallery open onOpenChange container>) still resolve to the modal branch.
  • GalleryInlineProps declares open?/onOpenChange?/container? as undefined rather than dropping them, so those keys still exist on both members - reads and keyof GalleryProps are unchanged, so property access / Pick / Omit all behave as before.
  • GalleryProvider is unaffected since it references Pick<GalleryModalProps, ...> directly, not the union.

The only thing the union newly forbids is combining view="inline" with open/onOpenChange/container - a new invalid combo that didn't exist before, so it can't break existing code.
Verified with npm run typecheck.

@artemnikotin artemnikotin force-pushed the feat/gallery-contained-and-controlled-index branch from 9423eab to 408497d Compare July 10, 2026 12:20
@artemnikotin artemnikotin changed the title feat(Gallery): add contained mode and controlled active item index feat(Gallery): add inline view and controlled active item index Jul 10, 2026
}

&_view_inline {
width: 100%;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe this also should be controlled by the user

  - add `view` prop ('modal' default | 'inline') to render the gallery in place instead of a modal
  - add controlled `activeItemIndex` / `onActiveItemIndexChange`,
    backed by a local `useConditionallyControlledState`
  - add `GalleryItem.id` to keep item previews reconciled by identity
@artemnikotin artemnikotin force-pushed the feat/gallery-contained-and-controlled-index branch from 408497d to 1753e0b Compare July 13, 2026 12:23
@kseniya57 kseniya57 merged commit 2a04394 into gravity-ui:main Jul 13, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants