Skip to content

Commit

Permalink
feat(share): add Share plugin (close #105)
Browse files Browse the repository at this point in the history
  • Loading branch information
igordanchenko committed May 15, 2023
1 parent 0e0fcde commit 77e5583
Show file tree
Hide file tree
Showing 12 changed files with 214 additions and 5 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,8 @@ The following plugins are bundled in the package:
- [Download](https://yet-another-react-lightbox.com/plugins/download) - adds download button
- [Fullscreen](https://yet-another-react-lightbox.com/plugins/fullscreen) - adds support for fullscreen mode
- [Inline](https://yet-another-react-lightbox.com/plugins/inline) - transforms the lightbox into an image carousel
- [Slideshow](https://yet-another-react-lightbox.com/plugins/slideshow) - adds slideshow autoplay feature
- [Share](https://yet-another-react-lightbox.com/plugins/share) - adds sharing button
- [Slideshow](https://yet-another-react-lightbox.com/plugins/slideshow) - adds slideshow button
- [Thumbnails](https://yet-another-react-lightbox.com/plugins/thumbnails) - adds thumbnails track
- [Video](https://yet-another-react-lightbox.com/plugins/video) - adds support for video slides
- [Zoom](https://yet-another-react-lightbox.com/plugins/zoom) - adds image zoom feature
Expand Down
3 changes: 2 additions & 1 deletion dev/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Counter from "../src/plugins/counter/index.js";
import Captions from "../src/plugins/captions/index.js";
import Download from "../src/plugins/download/index.js";
import Fullscreen from "../src/plugins/fullscreen/index.js";
import Share from "../src/plugins/share/index.js";
import Slideshow from "../src/plugins/slideshow/index.js";
import Thumbnails from "../src/plugins/thumbnails/index.js";
import Video from "../src/plugins/video/index.js";
Expand All @@ -26,7 +27,7 @@ export default function App() {
open={open}
close={() => setOpen(false)}
slides={slides}
plugins={[Captions, Counter, Download, Fullscreen, Slideshow, Thumbnails, Video, Zoom]}
plugins={[Captions, Counter, Download, Share, Fullscreen, Slideshow, Thumbnails, Video, Zoom]}
/>

<button type="button" className="button" onClick={() => setOpen(true)}>
Expand Down
6 changes: 3 additions & 3 deletions dev/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React from "react";
import ReactDOM from "react-dom/client";
import * as React from "react";
import { createRoot } from "react-dom/client";

import App from "./App.js";
import "./index.css";

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<App />
</React.StrictMode>
Expand Down
7 changes: 7 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@
"types": "./dist/plugins/inline/index.d.ts",
"default": "./dist/plugins/inline/index.js"
},
"./plugins/share": {
"types": "./dist/plugins/share/index.d.ts",
"default": "./dist/plugins/share/index.js"
},
"./plugins/slideshow": {
"types": "./dist/plugins/slideshow/index.d.ts",
"default": "./dist/plugins/slideshow/index.js"
Expand Down Expand Up @@ -108,6 +112,9 @@
"plugins/inline": [
"dist/plugins/inline/index.d.ts"
],
"plugins/share": [
"dist/plugins/share/index.d.ts"
],
"plugins/slideshow": [
"dist/plugins/slideshow/index.d.ts"
],
Expand Down
1 change: 1 addition & 0 deletions src/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const PLUGIN_COUNTER = "counter";
export const PLUGIN_DOWNLOAD = "download";
export const PLUGIN_FULLSCREEN = "fullscreen";
export const PLUGIN_INLINE = "inline";
export const PLUGIN_SHARE = "share";
export const PLUGIN_SLIDESHOW = "slideshow";
export const PLUGIN_THUMBNAILS = "thumbnails";
export const PLUGIN_ZOOM = "zoom";
Expand Down
1 change: 1 addition & 0 deletions src/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export { default as Counter } from "./counter/index.js";
export { default as Download } from "./download/index.js";
export { default as Fullscreen } from "./fullscreen/index.js";
export { default as Inline } from "./inline/index.js";
export { default as Share } from "./share/index.js";
export { default as Slideshow } from "./slideshow/index.js";
export { default as Thumbnails } from "./thumbnails/index.js";
export { default as Video } from "./video/index.js";
Expand Down
13 changes: 13 additions & 0 deletions src/plugins/share/Share.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as React from "react";

import { addToolbarButton, PluginProps } from "../../index.js";
import { resolveShareProps } from "./props.js";
import { ShareButton } from "./ShareButton.js";

export function Share({ augment }: PluginProps) {
augment(({ toolbar, share, ...rest }) => ({
toolbar: addToolbarButton(toolbar, "share", <ShareButton />),
share: resolveShareProps(share),
...rest,
}));
}
56 changes: 56 additions & 0 deletions src/plugins/share/ShareButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import * as React from "react";

import { createIcon, IconButton, isImageSlide, useLightboxProps, useLightboxState } from "../../index.js";
import { resolveShareProps } from "./props.js";
import { isShareSupported } from "./utils.js";

const ShareIcon = createIcon(
"ShareIcon",
<path d="m16 5-1.42 1.42-1.59-1.59V16h-1.98V4.83L9.42 6.42 8 5l4-4 4 4zm4 5v11c0 1.1-.9 2-2 2H6c-1.11 0-2-.9-2-2V10c0-1.11.89-2 2-2h3v2H6v11h12V10h-3V8h3c1.1 0 2 .89 2 2z" />
);

export function ShareButton() {
const { render, on, share: shareProps } = useLightboxProps();
const { share: customShare } = resolveShareProps(shareProps);
const { currentSlide, currentIndex } = useLightboxState();

if (!isShareSupported()) return null;

if (render.buttonShare) {
return <>{render.buttonShare()}</>;
}

const share =
(currentSlide &&
((typeof currentSlide.share === "object" && currentSlide.share) ||
(typeof currentSlide.share === "string" && { url: currentSlide.share }) ||
(isImageSlide(currentSlide) && { url: currentSlide.src }))) ||
undefined;

// slides must be explicitly marked as shareable when custom share function is provided
const canShare = customShare ? Boolean(currentSlide?.share) : share && navigator.canShare(share);

const defaultShare = () => {
if (share) {
navigator.share(share).catch(() => {});
}
};

const handleShare = () => {
if (currentSlide) {
(customShare || defaultShare)({ slide: currentSlide });

on.share?.({ index: currentIndex });
}
};

return (
<IconButton
label="Share"
icon={ShareIcon}
renderIcon={render.iconShare}
disabled={!canShare}
onClick={handleShare}
/>
);
}
55 changes: 55 additions & 0 deletions src/plugins/share/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { PLUGIN_SHARE } from "../../index.js";
import { Share } from "./Share.js";

export { isShareSupported } from "./utils.js";

declare module "../../types.js" {
interface GenericSlide {
/** share url or share props */
share?:
| boolean
| string
| {
/** share url */
url?: string;
/** share text */
text?: string;
/** share title */
title?: string;
};
}

interface LightboxProps {
/** Share plugin settings */
share?: {
/** custom share function */
share?: ({ slide }: ShareFunctionProps) => void;
};
}

interface Render {
/** render custom Share button */
buttonShare?: RenderFunction;
/** render custom Share icon */
iconShare?: RenderFunction;
}

interface Callbacks {
/** a callback called on slide share */
share?: Callback<ShareCallbackProps>;
}

interface ToolbarButtonKeys {
[PLUGIN_SHARE]: null;
}

export interface ShareCallbackProps {
index: number;
}

export interface ShareFunctionProps {
slide: Slide;
}
}

export default Share;
10 changes: 10 additions & 0 deletions src/plugins/share/props.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { LightboxProps } from "../../types.js";

export const defaultShareProps = {
share: undefined,
};

export const resolveShareProps = (share: LightboxProps["share"]) => ({
...defaultShareProps,
...share,
});
3 changes: 3 additions & 0 deletions src/plugins/share/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function isShareSupported() {
return Boolean(navigator.canShare);
}
61 changes: 61 additions & 0 deletions test/plugins/Share.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { render, screen } from "@testing-library/react";
import { vi } from "vitest";

import { clickButton, lightbox } from "../utils.js";
import { Share } from "../../src/plugins/index.js";
import { LightboxExternalProps } from "../../src/index.js";

function renderLightbox(props?: LightboxExternalProps) {
return render(lightbox({ plugins: [Share], ...props }));
}

describe("Share", () => {
beforeAll(() => {
const shareCopy = navigator.share;
const canShareCopy = navigator.canShare;

navigator.share = () => Promise.resolve();
navigator.canShare = () => true;

return () => {
navigator.share = shareCopy;
navigator.canShare = canShareCopy;
};
});

it("renders the share button", () => {
const share = vi.fn();

renderLightbox({ slides: [{ src: "image1" }, { src: "image2" }], on: { share } });

expect(screen.queryByLabelText("Share")).toBeInTheDocument();

clickButton("Share");

expect(share).toHaveBeenCalled();
});

it("doesn't crash with empty slides", () => {
renderLightbox();

expect(screen.queryByRole("presentation")).toBeInTheDocument();
});

it("supports custom share button", () => {
const buttonShare = vi.fn();

renderLightbox({ render: { buttonShare } });

expect(buttonShare).toHaveBeenCalled();
});

it("supports custom share function", () => {
const share = vi.fn();

renderLightbox({ slides: [{ src: "image", share: true }], share: { share } });

clickButton("Share");

expect(share).toHaveBeenCalled();
});
});

0 comments on commit 77e5583

Please sign in to comment.