Package Exports
- @juniorxsound/react-three-components
Readme
@juniorxsound/react-three-components
A personal set of reusable components for React Three Fiber 🪄
This library is a work in progress - if you find any issues, please report them here. Please proceed with caution if you use this library in production.
Installation
npm install @juniorxsound/react-three-componentsPeer Dependencies
This library requires the following peer dependencies:
npm install react react-dom three @react-three/fiber @react-spring/web @use-gesture/reactComponents
CircularCarousel
A 3D carousel that arranges items in a circle and rotates around an axis.
import { Canvas } from "@react-three/fiber";
import {
CircularCarousel,
useCarouselContext,
} from "@juniorxsound/react-three-components";
function Item({ index }: { index: number }) {
const { activeIndex } = useCarouselContext();
const isActive = index === activeIndex;
return (
<mesh>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color={isActive ? "hotpink" : "gray"} />
</mesh>
);
}
function App() {
return (
<Canvas>
<CircularCarousel radius={3} onIndexChange={(i) => console.log(i)}>
<Item index={0} />
<Item index={1} />
<Item index={2} />
<Item index={3} />
</CircularCarousel>
</Canvas>
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
children |
ReactNode |
required | Carousel items |
radius |
number |
3 |
Distance from center to items |
axis |
"x" | "y" | "z" |
"y" |
Rotation axis |
index |
number |
- | Controlled active index |
defaultIndex |
number |
0 |
Initial index (uncontrolled) |
onIndexChange |
(index: number) => void |
- | Called when index changes |
dragEnabled |
boolean |
true |
Enable drag gestures |
dragSensitivity |
number |
auto | Drag sensitivity |
dragAxis |
"x" | "y" |
"x" |
Drag gesture axis |
dragConfig |
DragConfig |
- | Additional drag options |
Ref Methods
const ref = useRef<CircularCarouselRef>(null);
ref.current.next(); // Go to next item
ref.current.prev(); // Go to previous item
ref.current.goTo(2); // Go to specific indexWith Navigation Triggers
<CircularCarousel>
<Item index={0} />
<Item index={1} />
<Item index={2} />
<CircularCarousel.PrevTrigger position={[-2, 0, 0]}>
<mesh>
<boxGeometry />
<meshBasicMaterial color="blue" />
</mesh>
</CircularCarousel.PrevTrigger>
<CircularCarousel.NextTrigger position={[2, 0, 0]}>
<mesh>
<boxGeometry />
<meshBasicMaterial color="red" />
</mesh>
</CircularCarousel.NextTrigger>
</CircularCarousel>LinearCarousel
A carousel that slides items linearly (horizontally or vertically).
import { Canvas } from "@react-three/fiber";
import {
LinearCarousel,
useLinearCarouselContext,
} from "@juniorxsound/react-three-components";
function Item({ index }: { index: number }) {
const { activeIndex } = useLinearCarouselContext();
const isActive = index === activeIndex;
return (
<mesh scale={isActive ? 1.2 : 1}>
<planeGeometry args={[2, 1.5]} />
<meshBasicMaterial color={isActive ? "hotpink" : "gray"} />
</mesh>
);
}
function App() {
return (
<Canvas>
<LinearCarousel gap={0.5} direction="horizontal">
<Item index={0} />
<Item index={1} />
<Item index={2} />
<Item index={3} />
</LinearCarousel>
</Canvas>
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
children |
ReactNode |
required | Carousel items |
gap |
number |
0.2 |
Space between items |
direction |
"horizontal" | "vertical" |
"horizontal" |
Slide direction |
index |
number |
- | Controlled active index |
defaultIndex |
number |
0 |
Initial index (uncontrolled) |
onIndexChange |
(index: number) => void |
- | Called when index changes |
dragEnabled |
boolean |
true |
Enable drag gestures |
dragSensitivity |
number |
150 |
Drag sensitivity |
dragAxis |
"x" | "y" |
auto | Drag axis (derived from direction) |
dragConfig |
DragConfig |
- | Additional drag options |
Ref Methods
const ref = useRef<LinearCarouselRef>(null);
ref.current.next(); // Go to next item (bounded)
ref.current.prev(); // Go to previous item (bounded)
ref.current.goTo(2); // Go to specific indexNote: LinearCarousel is bounded (doesn't wrap around), unlike CircularCarousel which loops infinitely.
With Navigation Triggers
<LinearCarousel>
<Item index={0} />
<Item index={1} />
<Item index={2} />
<LinearCarousel.PrevTrigger position={[-3, 0, 0]}>
<PrevButton />
</LinearCarousel.PrevTrigger>
<LinearCarousel.NextTrigger position={[3, 0, 0]}>
<NextButton />
</LinearCarousel.NextTrigger>
</LinearCarousel>useGLTFMaterialVariants
Parse and assign KHR_materials_variants on an already-loaded glTF. Load the model with useGLTF (Drei), useLoader(GLTFLoader, url), or your own loader; then pass the result. The hook applies the first variant initially (or pass { variant: "name" } to avoid flashing). Returns variants, activeVariant, and setVariant. Suspends until the variant is applied—wrap in a <Suspense> boundary.
import { Suspense } from "react";
import { Canvas } from "@react-three/fiber";
import { useGLTF } from "@react-three/drei";
import { useGLTFMaterialVariants } from "@juniorxsound/react-three-components";
function Shoe() {
const gltf = useGLTF("/MaterialsVariantsShoe.gltf");
const { variants, activeVariant, setVariant } = useGLTFMaterialVariants(
gltf,
{ variant: "midnight" }
);
return (
<group>
<primitive object={gltf.scene} />
{/* Use setVariant(name) to switch variants */}
</group>
);
}
function App() {
return (
<Canvas>
<Suspense fallback={null}>
<Shoe />
</Suspense>
</Canvas>
);
}| Return | Type | Description |
|---|---|---|
variants |
string[] |
Variant names from the extension. |
activeVariant |
string | null |
Currently active variant name. |
setVariant |
(name: string) => void |
Switch to a variant by name. |
Context Hooks
Access carousel state from any child component:
// For CircularCarousel
import { useCarouselContext } from "@juniorxsound/react-three-components";
const { activeIndex, count, next, prev, goTo } = useCarouselContext();
// For LinearCarousel
import { useLinearCarouselContext } from "@juniorxsound/react-three-components";
const { activeIndex, count, next, prev, goTo } = useLinearCarouselContext();DragConfig
Fine-tune drag behavior:
<CircularCarousel
dragConfig={{
axis: "x", // Constrain to axis
threshold: 10, // Pixels before drag starts
rubberband: 0.2, // Elastic effect at bounds
touchAction: "pan-y", // CSS touch-action
pointer: { touch: true }, // Pointer options
}}
>Contributing
Development
nvm use # Switch to Node 24 (uses .nvmrc)
npm install # Install dependencies
npm run dev # Start Storybook dev server
npm run test # Run tests in watch mode
npm run lint # Run ESLint
npm run build # Build the libraryReleasing
This package uses npm trusted publishers for secure, token-free publishing.
npm run release:patch # 0.1.0 → 0.1.1
npm run release:minor # 0.1.0 → 0.2.0
npm run release:major # 0.1.0 → 1.0.0Then create a GitHub Release from the tag - this triggers automatic npm publish with provenance.
License
MIT