Skip to content

Commit

Permalink
feat: make turntable completely controllable from the parent element, c…
Browse files Browse the repository at this point in the history
…loses #24

BREAKING CHANGE: The library now requires the `useReactImageTurntable`
hook to be called and its props passed.
  • Loading branch information
nerdyman committed Nov 26, 2023
1 parent 74c944f commit f43b532
Show file tree
Hide file tree
Showing 11 changed files with 170 additions and 136 deletions.
2 changes: 1 addition & 1 deletion example/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<title>React Image Turntable &ndash; Example</title>
</head>
<body class="container">
<div id="root" class="container" role="main"></div>
<div id="root" class="root" role="main"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
2 changes: 1 addition & 1 deletion example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"pepjs": "^0.5.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-image-turntable": "latest"
"react-image-turntable": "workspace:^"
},
"devDependencies": {
"@types/react": "^18.2.37",
Expand Down
62 changes: 42 additions & 20 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { useState } from 'react';
import { ReactImageTurntable } from 'react-image-turntable';
import type { ReactImageTurntableProps } from 'react-image-turntable';
import { ReactImageTurntable, useReactImageTurntable } from 'react-image-turntable';

export const images = [
'https://static2.zerolight.com/web/df3a45687480167bb4451d79c02f67bd/img/2d-explorer/bmw/P0C1G__S022B__FX3A9/P0C1G__S022B__FX3A9__Spin_01.jpg?v=1',
Expand Down Expand Up @@ -41,27 +40,50 @@ export const images = [
'https://static2.zerolight.com/web/df3a45687480167bb4451d79c02f67bd/img/2d-explorer/bmw/P0C1G__S022B__FX3A9/P0C1G__S022B__FX3A9__Spin_36.jpg?v=1',
];

function App(props: Partial<ReactImageTurntableProps>) {
const [rotationDisabled, setRotationDisabled] = useState(false);
function App() {
const [currentImages, setCurrentImages] = useState(images);
// Call the hook with the props you want.
const turntableProps = useReactImageTurntable({ images: currentImages });

const handleKeyDown = (ev: React.KeyboardEvent<HTMLDivElement>) => {
if (rotationDisabled) return;
return (
<div className="main">
{/* Render the component with the props from the hook. */}
<ReactImageTurntable id="turntable" {...turntableProps} />

if (ev.key === 'ArrowLeft' || ev.key === 'ArrowRight') {
setRotationDisabled(true);
}
};
{/* Demo controls, ignore. */}
<div className="main__toolbar main-toolbar">
<label className="main-toolbar__item">
<span>Index</span>
<input
style={{ maxWidth: '4.25ch', textAlign: 'center' }}
type="number"
className="output"
value={turntableProps.activeImageIndex}
onChange={(ev) => turntableProps.setActiveImageIndex(ev.target.valueAsNumber)}
/>
<button
onClick={() => turntableProps.setActiveImageIndex(turntableProps.activeImageIndex - 1)}
>
-
</button>
<button
onClick={() => turntableProps.setActiveImageIndex(turntableProps.activeImageIndex + 1)}
>
+
</button>
</label>

return (
<ReactImageTurntable
images={images}
autoRotate={{ disabled: rotationDisabled, interval: 200 }}
onPointerDown={() => setRotationDisabled(true)}
onPointerUp={() => setRotationDisabled(false)}
onKeyDown={handleKeyDown}
onKeyUp={() => setRotationDisabled(false)}
{...props}
/>
<button
onClick={() =>
setCurrentImages((prev) =>
prev.length == images.length ? images.slice(0, 12) : images,
)
}
>
Toggle cut images
</button>
</div>
</div>
);
}

Expand Down
39 changes: 0 additions & 39 deletions example/src/Test.tsx

This file was deleted.

38 changes: 37 additions & 1 deletion example/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@ body {
}

body {
display: flex;
align-items: center;
justify-content: center;
font-family: system-ui, sans-serif;
margin: 0;
background-color: #fff;
color: #111;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
Expand All @@ -22,10 +27,41 @@ pre {
font-family: monospace;
}

.container {
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}

input[type='number'] {
-moz-appearance: textfield;
}

.root {
display: flex;
align-items: center;
justify-content: center;
flex-grow: 1;
flex-direction: column;
}

.main__toolbar {
flex-shrink: 0;
flex-grow: 0;
}

.main-toolbar {
display: flex;
align-items: center;
gap: 1rem;
padding: 0.5rem;
background-color: #eee;
color: #111;
border-radius: 0.25rem;
border: 1px solid #ddd;
}

.main-toolbar__item {
display: flex;
gap: 0.125rem;
}
9 changes: 6 additions & 3 deletions example/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ import { createRoot } from 'react-dom/client';

import './index.css';
import App from './App';
import { Test } from './Test';

const isDebug = new URLSearchParams(window.location.search).get('debug') !== null;
const root = createRoot(document.getElementById('root') as HTMLElement);
root.render(<React.StrictMode>{isDebug ? <Test /> : <App />}</React.StrictMode>);

root.render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"version": "3.1.0",
"name": "react-image-turntable",
"description": "Display a set of images as a draggable 360 degree turntable.",
"keywords": [
Expand All @@ -9,7 +10,6 @@
"rotator",
"slider"
],
"version": "3.1.0",
"repository": {
"type": "git",
"url": "git+https://github.com/nerdyman/react-image-turntable.git"
Expand Down
32 changes: 7 additions & 25 deletions src/ReactImageTurntable.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import React, { useEffect } from 'react';
import type { CSSProperties, FC, MouseEvent } from 'react';

import { useTurntableState } from './hooks';
import type { ReactImageTurntableFullProps } from './types';
import type { ReactImageTurntableRootProps } from './types';

/** Base `className` for images. */
export const CLASS_NAME_IMG = '__react-image-turntable-img';
Expand All @@ -11,10 +9,6 @@ export const CLASS_NAME_IMG_PRIMARY = `${CLASS_NAME_IMG}--primary`;
/** `className` of subsequent images. */
export const CLASS_NAME_IMG_SECONDARY = `${CLASS_NAME_IMG}--secondary`;

const imgBaseStyle = {
maxWidth: '100%',
};

/**
* Firefox desktop tries to drag the image on `mousedown` + `mousemove` so we need to prevent it
* then mimic the default behavior of the browser.
Expand All @@ -23,39 +17,27 @@ const handleImgDragStart = (ev: MouseEvent<HTMLImageElement>) => {
ev.preventDefault();
};

export const ReactImageTurntable: FC<ReactImageTurntableFullProps> = ({
export const ReactImageTurntable: FC<ReactImageTurntableRootProps> = ({
activeImageIndex,
images,
initialImageIndex = 0,
style,
tabIndex = 0,
movementSensitivity = 20,
onIndexChange,
autoRotate = { disabled: false },
turntableRef,
setActiveImageIndex: __setActiveImageIndex,
...props
}) => {
const { ref, activeImageIndex } = useTurntableState({
initialImageIndex,
imagesCount: images.length - 1,
movementSensitivity,
autoRotate,
});

const rootStyle: CSSProperties = {
position: 'relative',
touchAction: 'none',
userSelect: 'none',
...style,
};

useEffect(() => {
if (onIndexChange) onIndexChange(activeImageIndex);
}, [activeImageIndex, onIndexChange]);

return (
<div
aria-label="Image turntable"
{...props}
ref={ref}
ref={turntableRef}
role="slider"
aria-valuemin={1}
aria-valuemax={images.length}
Expand All @@ -75,14 +57,14 @@ export const ReactImageTurntable: FC<ReactImageTurntableFullProps> = ({
draggable={false}
onDragStart={handleImgDragStart}
style={{
...imgBaseStyle,
position: index === 0 ? undefined : 'absolute',
opacity: index === activeImageIndex ? 1 : 0,
left: 0,
top: 0,
right: 0,
bottom: 0,
width: '100%',
maxWidth: '100%',
height: '100%',
objectFit: 'cover',
}}
Expand Down
7 changes: 6 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
export * from './ReactImageTurntable';
export type { ReactImageTurntableProps, ReactImageTurntableFullProps } from './types';
export * from './useReactImageTurntable';
export type {
UseReactImageTurntableProps,
ReactImageTurntableRootProps,
UseReactImageTurntableReturn,
} from './types';
34 changes: 22 additions & 12 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,32 @@
import type { HtmlHTMLAttributes } from 'react';
import type { HtmlHTMLAttributes, RefObject } from 'react';

export interface ReactImageTurntableProps {
export interface UseReactImageTurntableProps {
/** The array index of the active image. */
initialImageIndex?: number;
/** Properties to automatically rotate the turntable. */
autoRotate?: {
/** Enable or disable the autorotation of the turntable. */
disabled?: boolean;
/** The speed (in ms) at which the turntable rotates. */
interval?: number;
};
/** List of image `src` attributes. */
images: string[];
/** Index of image to show first. */
initialImageIndex?: number;
/** The amount a "drag" has to move before an image changes to next or previous. */
movementSensitivity?: number;
/** Callback that triggers whenever the active index changes. */
onIndexChange?: (index: number) => void;
autoRotate?: {
/** Automatically rotate the turntable. */
disabled: boolean;
/** The speed (in ms) at which the turntable rotates. */
interval?: number;
};
}

export interface UseReactImageTurntableReturn extends Pick<UseReactImageTurntableProps, 'images'> {
/** The array index of the active image. */
activeImageIndex: number;
/** Function to programatically set the active image index. */
setActiveImageIndex: (index: number) => void;
/** The ref to the root turntable element. */
turntableRef: RefObject<HTMLDivElement>;
}

/** Base props *and* all available HTML element props. */
export type ReactImageTurntableFullProps = HtmlHTMLAttributes<HTMLDivElement> &
ReactImageTurntableProps;
export type ReactImageTurntableRootProps = HtmlHTMLAttributes<HTMLDivElement> &
UseReactImageTurntableReturn;
Loading

0 comments on commit f43b532

Please sign in to comment.