Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
6e5ccf5
feat: add vscode extension suggestion injection point
itsdouges May 11, 2025
ae00b02
chore: bootstrap triplex generator
itsdouges May 12, 2025
048542a
feat: add tools to readme
itsdouges May 12, 2025
4b92d4f
chore: update
itsdouges May 12, 2025
1e1123c
feat: add triplex cli
itsdouges May 12, 2025
1bd7e9c
feat: add tools to three homepage
itsdouges May 12, 2025
1d654dd
feat: add label prop
itsdouges May 12, 2025
241bdd4
chore: rename
itsdouges May 12, 2025
1f1038f
fix: label
itsdouges May 12, 2025
89c7d1b
chore: add test runner
itsdouges May 12, 2025
392e210
chore: add tests
itsdouges May 12, 2025
53698a0
feat: codegen providers
itsdouges May 12, 2025
42ca408
chore: rename
itsdouges May 12, 2025
f92909f
chore: install
itsdouges May 12, 2025
b68010c
chore: add build workflow
itsdouges May 12, 2025
b267bde
chore: add
itsdouges May 12, 2025
138f255
chore: type safe
itsdouges May 12, 2025
dd018e8
feat: add uikit theme switcher
itsdouges May 12, 2025
9b1edbd
chore: update
itsdouges May 12, 2025
d02a25c
chore: tweaks
itsdouges May 12, 2025
da29f9a
chore: remove
itsdouges May 12, 2025
ed066b8
fix: ignore when xr
itsdouges May 12, 2025
f6bca2d
chore: pass through
itsdouges May 12, 2025
267fd25
chore: remove
itsdouges May 12, 2025
2feb296
nit
itsdouges May 12, 2025
962dab9
chore: update
itsdouges May 12, 2025
b483f7b
chore: revert formatting change
itsdouges May 12, 2025
2745e6d
chore: nit
itsdouges May 12, 2025
dd49de6
chore: ignore test folders when building dist
itsdouges May 14, 2025
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
35 changes: 35 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Build

on:
pull_request:
push:
branches:
- main

jobs:
build:
runs-on: ubuntu-22.04

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '20'

- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: latest
run_install: true

- name: Build packages
run: pnpm --filter "@react-three/*" build

- name: Build website
run: pnpm --filter "react-three-org-website" build

- name: Build gh-app
run: pnpm --filter "github-app" build
29 changes: 29 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Test

on:
pull_request:
push:
branches:
- main

jobs:
test:
runs-on: ubuntu-22.04

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '20'

- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: latest
run_install: true

- name: Run tests
run: pnpm test
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ npm create @react-three
| `--offscreen` | Add [@react-three/offscreen](https://github.com/pmndrs/offscreen) for offscreen rendering |
| `--zustand` | Add [zustand](https://github.com/pmndrs/zustand) for state management |
| `--koota` | Add [koota](https://github.com/pmndrs/koota) for animation |
| `--triplex` | Add [Triplex](https://triplex.dev) for a visual development environment |
| `--package-manager <manager>` | Specify package manager (npm, yarn, or pnpm) |
| `--skip-setup` | Skip automatic dependency installation, dev server start, and browser opening |
| `-y, --yes` | Skip prompts and use default values |
Expand Down
1 change: 1 addition & 0 deletions apps/github/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const GenerateOptionsSchema = z.object({
rapier: z.boolean().optional(),
uikit: z.boolean().optional(),
xr: z.boolean().optional(),
triplex: z.boolean().optional(),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Let me know if it's possible to test the GitHub creation flow locally, otherwise will have to wait and see...

zustand: z.boolean().optional(),
})

Expand Down
90 changes: 48 additions & 42 deletions apps/react-three-org/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,25 @@ import { useQuery } from '@tanstack/react-query'
import { PackageCard } from '@/components/package-card'
import { ProjectConfigurator } from '@/components/project-configurator'
import { NavBar } from '@/components/nav-bar'
import { packages } from '@/lib/packages'
import { packages, tools, PackageIDs, ToolIDs } from '@/lib/packages'
import { BackgroundAnimation } from '@/components/background-animation'
import { Button } from './components/ui/button.js'
import { CheckSquare, PackageIcon, X } from 'lucide-react'
import { CogIcon, PackageIcon } from 'lucide-react'
import { Toaster } from 'sonner'
import { SelectionSection } from './components/selection-section.js'

const searchParams = new URLSearchParams(location.search)

const sessionAccessTokenKey = 'access_token'
const sessionAccessToken = sessionStorage.getItem(sessionAccessTokenKey)

export function App() {
const [state, setState] = useState(() => searchParams.get('state'))
const [selectedPackages, setSelectedPackages] = useState<string[]>([])
const [selectedPackages, setSelectedPackages] = useState<PackageIDs[]>([])
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Currently the CLI defaults to everything selected, where as the app defaults to nothing selected.

Should there be a sensible middle ground?

const [selectedTools, setSelectedTools] = useState<ToolIDs[]>(['triplex'])

if (state != null) {
return <GithubRepo state={state} />
}

const togglePackage = (packageId: string) => {
setSelectedPackages((prev) =>
prev.includes(packageId) ? prev.filter((id) => id !== packageId) : [...prev, packageId],
)
}

return (
<main className="relative min-h-screen flex flex-col pb-36">
<BackgroundAnimation />
Expand All @@ -43,55 +38,66 @@ export function App() {
{/* Visual separator using space instead of a border */}
<div className="mb-10 mt-8"></div>

<div className="flex flex-col mb-6">
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This has been abstracted into a component <SelectionSection />.

<div className="flex justify-between items-center">
<p className="text-sm font-medium text-white/70 flex items-center">
<PackageIcon className="h-3.5 w-3.5 mr-1.5 opacity-70" />
Select packages to include in your project
</p>
<Button
variant="ghost"
size="sm"
className="text-xs hover:bg-white/5 flex items-center"
onClick={() => {
if (selectedPackages.length === packages.length) {
setSelectedPackages([])
} else {
setSelectedPackages(packages.map((pkg) => pkg.id))
}
}}
aria-label={selectedPackages.length === packages.length ? "Deselect all packages" : "Select all packages"}
>
{selectedPackages.length === packages.length ? (
<X className="h-3.5 w-3.5 mr-1 opacity-70" />
) : (
<CheckSquare className="h-3.5 w-3.5 mr-1 opacity-70" />
)}
{selectedPackages.length === packages.length ? 'Deselect All' : 'Select All'}
</Button>
</div>
</div>
<SelectionSection
value={selectedPackages}
icon={PackageIcon}
label="packages"
onChange={setSelectedPackages}
options={packages}
/>

<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
{packages.map((pkg) => (
<PackageCard
key={pkg.id}
package={pkg}
isSelected={selectedPackages.includes(pkg.id)}
onToggle={() => togglePackage(pkg.id)}
onToggle={() => {
setSelectedPackages((prev) =>
prev.includes(pkg.id) ? prev.filter((id) => id !== pkg.id) : [...prev, pkg.id],
)
}}
/>
))}
</div>

{/* Visual separator using space instead of a border */}
<div className="mb-10 mt-8"></div>

<SelectionSection
label="tools"
value={selectedTools}
icon={CogIcon}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I used a cog icon, lmk if you'd prefer something else!

onChange={setSelectedTools}
options={tools}
/>

<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
{tools.map((pkg) => (
<PackageCard
key={pkg.id}
package={pkg}
isSelected={selectedTools.includes(pkg.id)}
onToggle={() => {
setSelectedTools((prev) =>
prev.includes(pkg.id) ? prev.filter((id) => id !== pkg.id) : [...prev, pkg.id],
)
}}
/>
))}
</div>

<ProjectConfigurator
createGithubRepo={() => {
const integrations: any = {}
for (const integration of selectedPackages) {
const selections = [...selectedPackages, ...selectedTools]
for (const integration of selections) {
integrations[integration] = true
}

setState(btoa(JSON.stringify(integrations)))
}}
selectedPackages={selectedPackages}
selections={[...selectedPackages, ...selectedTools]}
/>
</div>
<Toaster
Expand Down
10 changes: 6 additions & 4 deletions apps/react-three-org/src/components/project-configurator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import JSZip from 'jszip'
import { toast } from 'sonner'

interface ProjectConfiguratorProps {
selectedPackages: string[]
selections: string[]
createGithubRepo: () => void
}

export function ProjectConfigurator({ selectedPackages, createGithubRepo }: ProjectConfiguratorProps) {
const command = `npm create @react-three ${selectedPackages.length > 0 ? "-- " : ""}${selectedPackages.map((id) => `--${id}`).join(' ')}`
export function ProjectConfigurator({ selections, createGithubRepo }: ProjectConfiguratorProps) {
const command = `npm create @react-three ${selections.length > 0 ? '-- ' : ''}${selections
.map((id) => `--${id}`)
.join(' ')}`

const copyToClipboard = async () => {
try {
Expand Down Expand Up @@ -41,7 +43,7 @@ export function ProjectConfigurator({ selectedPackages, createGithubRepo }: Proj
onClick={async () => {
const element = document.createElement('a')
const options: any = {}
for (const selectedPackage of selectedPackages) {
for (const selectedPackage of selections) {
options[selectedPackage] = true
}
const files = generate(options)
Expand Down
51 changes: 51 additions & 0 deletions apps/react-three-org/src/components/selection-section.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { JSX } from 'react'
import { Button } from './ui/button'
import { Package } from '../lib/packages'
import { CheckSquare, X } from 'lucide-react'

interface SelectionSectionProps<TOption extends string> {
icon: (props: { className?: string }) => React.ReactNode
options: Package[]
value: TOption[]
onChange: (value: TOption[]) => void
label: string
}

export function SelectionSection<TOption extends string>({
label,
value,
onChange,
options,
icon: Icon,
}: SelectionSectionProps<TOption>) {
return (
<div className="flex flex-col mb-6">
<div className="flex justify-between items-center">
<p className="text-sm font-medium text-white/70 flex items-center">
<Icon className="h-3.5 w-3.5 mr-1.5 opacity-70" />
Select {label} to include in your project
</p>
<Button
variant="ghost"
size="sm"
className="text-xs hover:bg-white/5 flex items-center"
onClick={() => {
if (options.length === value.length) {
onChange([])
} else {
onChange(options.map((pkg) => pkg.id as TOption))
}
}}
aria-label={value.length === options.length ? `Deselect all ${label}` : `Select all ${label}`}
>
{value.length === options.length ? (
<X className="h-3.5 w-3.5 mr-1 opacity-70" />
) : (
<CheckSquare className="h-3.5 w-3.5 mr-1 opacity-70" />
)}
{value.length === options.length ? 'Deselect All' : 'Select All'}
</Button>
</div>
</div>
)
}
16 changes: 15 additions & 1 deletion apps/react-three-org/src/lib/packages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,18 @@ export const packages: Package[] = [
githubUrl: "https://github.com/pmndrs/zustand",
docsUrl: "https://zustand.docs.pmnd.rs/",
},
]
] as const satisfies Package[]

export const tools = [
{
id: 'triplex',
name: 'Triplex',
description: 'Build the 2D and 3D web without coding. Your visual workspace for React / Three Fiber.',
githubUrl: 'https://github.com/try-triplex/triplex',
docsUrl: 'https://triplex.dev/docs/get-started',
},
] as const satisfies Package[]

export type ToolIDs = (typeof tools)[number]['id']

export type PackageIDs = (typeof packages)[number]['id']
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
"dependencies": {
"ts-node": "^10.9.2",
"typescript": "^5.8.3",
"json": "^11.0.0"
"json": "^11.0.0",
"vitest": "^3.1.3"
},
"scripts": {
"test": "vitest"
}
}
2 changes: 1 addition & 1 deletion packages/react-three-create/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"build": "tsc",
"build": "tsc --project ./tsconfig.build.json",
"dev": "node --loader ts-node/esm src/cli.ts"
},
"files": [
Expand Down
8 changes: 7 additions & 1 deletion packages/react-three-create/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ async function promptForOptions(name: string | undefined): Promise<GenerateOptio
{
type: 'confirm',
name: 'skipSetup' as const,
message: 'Skip automatic setup? (This will skip installing dependencies, starting the dev server, and opening the browser)',
message:
'Skip automatic setup? (This will skip installing dependencies, starting the dev server, and opening the browser)',
initial: false,
},
{
Expand Down Expand Up @@ -114,6 +115,7 @@ async function promptForOptions(name: string | undefined): Promise<GenerateOptio
{ title: 'Offscreen', value: 'offscreen', selected: true },
{ title: 'Zustand', value: 'zustand', selected: true },
{ title: 'Koota', value: 'koota', selected: true },
{ title: 'Triplex', value: 'triplex', selected: true },
],
},
] satisfies Array<PromptObject>
Expand Down Expand Up @@ -142,6 +144,7 @@ async function promptForOptions(name: string | undefined): Promise<GenerateOptio
offscreen: answers.integrations?.includes('offscreen') ? {} : undefined,
zustand: answers.integrations?.includes('zustand') ? {} : undefined,
koota: answers.integrations?.includes('koota') ? {} : undefined,
triplex: answers.integrations?.includes('triplex') ? {} : undefined,
packageManager: answers.packageManager === 'custom' ? answers.customPackageManager : answers.packageManager,
skipSetup: answers.skipSetup,
}
Expand All @@ -161,6 +164,7 @@ interface CliOptions {
offscreen?: boolean
zustand?: boolean
koota?: boolean
triplex?: boolean
'package-manager'?: string
'skip-setup'?: boolean
yes?: boolean
Expand All @@ -184,6 +188,7 @@ async function main() {
.option('--offscreen', 'add @react-three/offscreen')
.option('--zustand', 'add zustand')
.option('--koota', 'add koota')
.option('--triplex', 'set up triplex development environment')
.option('--package-manager <manager>', 'specify package manager (e.g. npm, yarn, pnpm)')
.option(
'--skip-setup',
Expand All @@ -210,6 +215,7 @@ async function main() {
offscreen: options.offscreen ? {} : undefined,
zustand: options.zustand ? {} : undefined,
koota: options.koota ? {} : undefined,
triplex: options.triplex,
packageManager: options['package-manager'],
skipSetup: options['skip-setup'],
}
Expand Down
Loading