Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
reviews:
auto_review:
# Review PRs targeting any branch (not only the default)
base_branches:
- ".*"
18 changes: 18 additions & 0 deletions .cursor/rules/project-conventions.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
description: Core conventions for this Three.js + React project (always apply)
alwaysApply: true
---

# Project Conventions

- **React 19+**: Use functional components (we target React 19.2.4): use `ref` as a normal prop (include `ref?: Ref<T>` in props, destructure `{ ref, ...props }` in the signature and pass it through) and do not use `forwardRef`.
- **Imports**: Use the `~` path alias for app code (e.g. `~/sketched-components/...`). Group: external libs, then internal, then types.
- **Styles**: Use CSS modules (`styles.module.css`) colocated with components; import as `styles` and use `className={styles.xyz}`.
- **3D / R3F**: Prefer `@react-three/fiber` and `@react-three/drei`. Type mesh refs as `Ref<Mesh>` with `Ref` from `react` and `Mesh` from `three`; use declarative `<mesh>`, `<meshStandardMaterial>`, etc.
- **Exports**: Prefer named exports for components and types. Use `default` only when it matches existing pattern in the file (e.g. route pages).

# Planning and implementation

- **Plans**: When making a plan, design each step so it is **self-contained and testable**. No step should depend on unfinished later work for you or the user to verify it.
- **One step at a time**: Implement **one** step, then **pause** and wait for feedback on that step's changes.
- **Continue only after approval**: Do not move to the next step until the user is happy with the current step and explicitly gives the green light to continue (e.g. "looks good", "continue", "next").
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
20.19.0
22 changes: 0 additions & 22 deletions Dockerfile

This file was deleted.

19 changes: 9 additions & 10 deletions app/3d/cube.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
import type { MeshProps } from "../types/types";
import type { Ref } from 'react';
import type { Mesh } from 'three';

type CubeProps = MeshProps;
export default function Cube({ children, ...props }: CubeProps) {
type CubeProps = MeshProps & { ref?: Ref<Mesh> };

export default function Cube({ ref: refProp, children, ...props }: CubeProps) {
return (
<mesh {...props}>
<mesh ref={refProp} {...props}>
<boxGeometry />
{children}
</mesh>
);
}

export type PurpleCubeProps = Readonly<Omit<CubeProps, 'position-x' | 'scale'>>
export function PurpleCube(props: PurpleCubeProps ) {
return <Cube
position-x={2}
scale={1.5}
{...props}
>
export function PurpleCube({ ref: refProp, ...props }: PurpleCubeProps) {
return <Cube ref={refProp} position-x={2} scale={1.5} {...props}>
<meshStandardMaterial color="mediumpurple" />
</Cube>
</Cube>;
}
15 changes: 10 additions & 5 deletions app/3d/sphere.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
import type { MeshProps } from "../types/types";
import type { Ref } from 'react';
import type { Mesh } from 'three';

export type SphereProps = Readonly<MeshProps & {
color?: string;
ref?: Ref<Mesh>;
}>;

export default function Sphere({ children, ...props }: SphereProps) {
export function Sphere({ ref: refProp, children, ...props }: SphereProps) {
return (
<mesh {...props}>
<mesh ref={refProp} {...props}>
<sphereGeometry />
{children}
</mesh>
);
};
}

export function OrangeSphere(props: Readonly<Omit<SphereProps, 'position-x'>>) {
return <Sphere position-x={-2} {...props}>
export type OrangeSphereProps = Readonly<Omit<SphereProps, 'position-x'>>;
export function OrangeSphere({ ref: refProp, children, ...props }: OrangeSphereProps) {
return <Sphere ref={refProp} position-x={-2} {...props}>
<meshStandardMaterial color="orange" />
{children}
</Sphere>;
}
38 changes: 17 additions & 21 deletions app/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,19 @@
--color-surface: oklch(1 0.01 166.77);
--color-text: oklch(0.2101 0.0318 264.66);

--icon-size-xs: 2.25em;
--icon-size-sm: 2.5em;
--icon-size-md: 3em;
--icon-size-lg: 5em;
--icon-size-xl: 6em;
--icon-size-2xl: 10em;
--icon-size-xs: 2.25rem;
--icon-size-sm: 2.5rem;
--icon-size-md: 3rem;
--icon-size-lg: 5rem;
--icon-size-xl: 6rem;

--font-size-sm: 16px;
--font-size-md: 21px;
--font-size-lg: 24px;
--font-size-sm: 1rem;
--font-size-md: 1.3125rem;
--font-size-lg: 1.5rem;

--spacing-xs: 0.25rem;
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
--spacing-lg: 2rem;
--spacing-xl: 4rem;
--spacing-2xl: 8rem;
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 32px;
}

@media (prefers-color-scheme: dark) {
Expand Down Expand Up @@ -52,19 +48,19 @@ html:has(body > .parent-no-scroll) {
overflow-y: hidden;
}

input, button, textarea, select {
font: inherit;
}

a {
text-decoration: none;
color: inherit;
}

p {
max-width: 60ch;
}

.row {
display: flex;
flex-direction: row;
gap: var(--spacing-md);
line-height: 1.5;
text-wrap: pretty;
}

.col {
Expand Down
10 changes: 5 additions & 5 deletions app/routes/demos/02-drei-app/main-scene.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { PivotControls, Text, Float, MeshReflectorMaterial } from '@react-three/drei'
import { useRef, type RefObject } from 'react';

import type { Object3D } from 'three';
import type { Mesh, Object3D } from 'three';

import BasicSetup from '~/3d/basic-setup';
import { PurpleCube } from '~/3d/cube';
Expand All @@ -10,8 +10,8 @@ import { OrangeSphere } from '~/3d/sphere';
import Label from '~/sketched-components/label/label';
export default function Experience() {

const cubeRef = useRef<Object3D>(null);
const sphereRef = useRef<Object3D>(null);
const cubeRef = useRef<Mesh>(null);
const sphereRef = useRef<Mesh>(null);

const occludeObjects = [sphereRef, cubeRef] as RefObject<Object3D>[];

Expand All @@ -25,7 +25,7 @@ export default function Experience() {
lineWidth={4}
scale={2}
>
<OrangeSphere>
<OrangeSphere ref={sphereRef}>
<Label position={[1, 1, 0]} distanceFactor={8} occlude={occludeObjects}>
That's a sphere! 👍
</Label>
Expand All @@ -40,7 +40,7 @@ export default function Experience() {
resolution={1024}
/>
</Floor>
<PurpleCube />
<PurpleCube ref={cubeRef} />
<Float speed={5} floatIntensity={2} >
<Text
color="salmon"
Expand Down
8 changes: 0 additions & 8 deletions app/routes/demos/02-drei-app/styles.module.css

This file was deleted.

2 changes: 1 addition & 1 deletion app/routes/demos/03-debug-ui/main-scene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useControls, levaStore } from "leva";
import BasicSetup from "~/3d/basic-setup";
import Cube from "~/3d/cube";
import { GreenFloor } from "~/3d/floor";
import Sphere from "~/3d/sphere";
import { Sphere } from "~/3d/sphere";

export default function MainScene() {

Expand Down
26 changes: 17 additions & 9 deletions app/routes/demos/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,26 @@ import styles from "./styles.module.css";

import SketchyLevaPanel from "~/sketched-components/leva-panel";
import SketchyLink from "~/sketched-components/link";
import { useDemoParams } from "~/utils/hooks/use-demo-params";
import HomeIcon from "~/utils/icons/home.svg?react";

export default function Layout() {
const { showHomeButton, debugMode } = useDemoParams();
const showSketchyLevaPanel = !debugMode;

return <main className="full-screen parent-no-scroll">
<SketchyLink
to="/"
icon={HomeIcon}
className={`${styles['home-link']}`}
arialLabelledBy="home-link"
/>
<SketchyLevaPanel />
<Leva hidden />
<Outlet />
{showHomeButton && (
<SketchyLink
to="/"
icon={HomeIcon}
className={`${styles['home-link']}`}
ariaLabel="Go to home"
/>
)}
{showSketchyLevaPanel && <SketchyLevaPanel className={styles['leva-panel-container']} />}
<Leva hidden={showSketchyLevaPanel} />
<div className="full-screen" style={{ isolation: "isolate" }}>
<Outlet />
</div>
</main>;
}
20 changes: 4 additions & 16 deletions app/routes/demos/styles.module.css
Original file line number Diff line number Diff line change
@@ -1,19 +1,3 @@

.sketchy-link-icon {
width: var(--icon-size-xs);
aspect-ratio: 1/1;
padding: var(--spacing-xs);

@media (min-width: 728px) {
width: var(--icon-size-md);
}

@media (min-width: 2048px) {
padding: var(--spacing-sm);
width: var(--icon-size-lg);
}
}

.home-link {
top: var(--spacing-md);
left: var(--spacing-md);
Expand All @@ -22,4 +6,8 @@
top: var(--spacing-lg);
left: var(--spacing-lg);
}
}

.leva-panel-container {
z-index: 1;
}
8 changes: 4 additions & 4 deletions app/routes/home/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ export default function Home() {
useScrollMemory();

return (
<div className={`col align-items-center ${styles['home-container']}`}>
<div className="align-items-center">
<main className={`col align-items-center ${styles['home-container']}`}>
<div className={styles['title-container']}>
<SketchyH1>React Three Fiber Demos</SketchyH1>
<h2>by <a href="https://www.linkedin.com/in/gianfranco-zamboni/" target="_blank">Gianfranco Zamboni</a></h2>
<h2>by <a href="https://www.linkedin.com/in/gianfranco-zamboni/" target="_blank" rel="noopener noreferrer">Gianfranco Zamboni</a></h2>
</div>
<p>A set of demos I made to practice for the last chapter of Three JS Journey where I learned to integrate Three JS with React.</p>
<div className={styles['demo-grid']}>
Expand All @@ -39,6 +39,6 @@ export default function Home() {
className={DEMOS.length % 2 === 0 ? styles['two-columns-item'] : ''}
/>
</div>
</div>
</main>
);
}
12 changes: 8 additions & 4 deletions app/routes/home/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,6 @@
gap: var(--spacing-lg);
}

@media (min-width: 1200px) {
gap: var(--spacing-xl);
}

p {
text-align: justify;

Expand All @@ -45,3 +41,11 @@ h2, a {
font-family: "Indie Flower", cursive;
}


.title-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: var(--spacing-sm);
}
9 changes: 2 additions & 7 deletions app/sketched-components/button/styles.module.css
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
.sketchy-button {
background-color: var(--color-surface);
color: var(--color-text);
overflow: visible;
z-index: 100;
border: none;
padding-block: var(--spacing-sm);
padding-inline: var(--spacing-sm) ;
color: var(--color-text);
cursor: pointer;
position: relative;
padding: var(--spacing-sm) ;
}
2 changes: 1 addition & 1 deletion app/sketched-components/card/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export function SketchedCard({
target={targetBlank ? '_blank' : '_self'}
>
<SketchyBorder
className={`relative ${styles['card']} ${commonStyles['sketchy-container-margin']}`}
className={`relative ${styles['card']}`}
>
<SketchyShadow offsetX={0.5} offsetY={1} strokeWidth="sm"/>
<div className={styles['thumbnail-container']}>
Expand Down
Loading