From b0b22ad8e8a5120c4a110dd93f845b95d433e2f2 Mon Sep 17 00:00:00 2001 From: Bela Bohlender Date: Tue, 5 Mar 2024 02:17:09 +0100 Subject: [PATCH] feat: add apfel kit --- README.md | 1 - examples/apfel/index.html | 13 + examples/apfel/package.json | 24 + examples/apfel/src/App.tsx | 469 ++++++++++++++++++++ examples/apfel/src/index.tsx | 9 + examples/apfel/tsconfig.json | 8 + examples/apfel/vite.config.ts | 17 + examples/card/package.json | 3 +- examples/card/src/App.jsx | 10 +- examples/dashboard/package.json | 6 +- examples/dashboard/src/App.tsx | 6 +- examples/default/package.json | 3 +- examples/default/src/App.tsx | 6 +- examples/lucide/package.json | 3 +- examples/market/package.json | 3 +- examples/market/src/App.tsx | 10 +- packages/kits/apfel/LICENSE | 15 + packages/kits/apfel/button.tsx | 89 ++++ packages/kits/apfel/card.tsx | 20 + packages/kits/apfel/checkbox.tsx | 52 +++ packages/kits/apfel/list.tsx | 79 ++++ packages/kits/apfel/loading.tsx | 58 +++ packages/kits/apfel/package.json | 18 + packages/kits/apfel/progress.tsx | 30 ++ packages/kits/apfel/registry.json | 30 ++ packages/kits/apfel/slider.tsx | 148 ++++++ packages/kits/apfel/tab-bar.tsx | 112 +++++ packages/kits/apfel/tabs.tsx | 106 +++++ packages/kits/apfel/text-input.tsx | 99 +++++ packages/kits/apfel/theme.tsx | 38 ++ packages/kits/default/LICENSE | 8 + packages/kits/default/slider.tsx | 3 +- packages/kits/default/theme.tsx | 2 +- packages/uikit/src/active.ts | 55 +++ packages/uikit/src/components/container.tsx | 10 +- packages/uikit/src/components/content.tsx | 10 +- packages/uikit/src/components/custom.tsx | 10 +- packages/uikit/src/components/icon.tsx | 10 +- packages/uikit/src/components/image.tsx | 10 +- packages/uikit/src/components/label.tsx | 0 packages/uikit/src/components/root.tsx | 10 +- packages/uikit/src/components/svg.tsx | 10 +- packages/uikit/src/components/text.tsx | 10 +- packages/uikit/src/components/utils.ts | 5 +- packages/uikit/src/flex/node.ts | 12 +- packages/uikit/src/panel/react.tsx | 10 +- pnpm-lock.yaml | 167 ++++--- 47 files changed, 1693 insertions(+), 134 deletions(-) create mode 100644 examples/apfel/index.html create mode 100644 examples/apfel/package.json create mode 100644 examples/apfel/src/App.tsx create mode 100644 examples/apfel/src/index.tsx create mode 100644 examples/apfel/tsconfig.json create mode 100644 examples/apfel/vite.config.ts create mode 100644 packages/kits/apfel/LICENSE create mode 100644 packages/kits/apfel/button.tsx create mode 100644 packages/kits/apfel/card.tsx create mode 100644 packages/kits/apfel/checkbox.tsx create mode 100644 packages/kits/apfel/list.tsx create mode 100644 packages/kits/apfel/loading.tsx create mode 100644 packages/kits/apfel/package.json create mode 100644 packages/kits/apfel/progress.tsx create mode 100644 packages/kits/apfel/registry.json create mode 100644 packages/kits/apfel/slider.tsx create mode 100644 packages/kits/apfel/tab-bar.tsx create mode 100644 packages/kits/apfel/tabs.tsx create mode 100644 packages/kits/apfel/text-input.tsx create mode 100644 packages/kits/apfel/theme.tsx create mode 100644 packages/uikit/src/active.ts delete mode 100644 packages/uikit/src/components/label.tsx diff --git a/README.md b/README.md index ee7077c5..3c9caff6 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,6 @@ TODO Release - feat: nesting inside non root/container components (e.g. image) - fix: scrollbar border radius to high (happens with very long panels) - feat: drag/click threshold -- feat: add apfel components - feat: input - fix: decrease clipping rect when scrollbar present diff --git a/examples/apfel/index.html b/examples/apfel/index.html new file mode 100644 index 00000000..cf853ae4 --- /dev/null +++ b/examples/apfel/index.html @@ -0,0 +1,13 @@ + + + + + + + Document + + + +
+ + diff --git a/examples/apfel/package.json b/examples/apfel/package.json new file mode 100644 index 00000000..eb612a75 --- /dev/null +++ b/examples/apfel/package.json @@ -0,0 +1,24 @@ +{ + "type": "module", + "dependencies": { + "@coconut-xr/xinteraction": "^0.1.12", + "@preact/signals-core": "^1.5.1", + "@react-three/drei": "^9.96.1", + "@react-three/fiber": "^8.15.13", + "@react-three/postprocessing": "^2.16.0", + "@react-three/rapier": "^1.3.0", + "@react-three/uikit": "workspace:^", + "@react-three/uikit-lucide": "workspace:^", + "@splinetool/r3f-spline": "^1.0.2", + "@types/three": "^0.160.0", + "maath": "^0.10.7", + "r3f-perf": "^7.1.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "three": "^0.160.0" + }, + "scripts": { + "dev": "vite --host", + "build": "vite build" + } +} diff --git a/examples/apfel/src/App.tsx b/examples/apfel/src/App.tsx new file mode 100644 index 00000000..2fef0fb6 --- /dev/null +++ b/examples/apfel/src/App.tsx @@ -0,0 +1,469 @@ +import { Canvas } from '@react-three/fiber' +import { Fullscreen, Text, Container, setPreferredColorScheme } from '@react-three/uikit' +import { BoxSelect, ChevronRight, Info } from '@react-three/uikit-lucide' +import { Perf } from 'r3f-perf' +import { XWebPointers, noEvents } from '@coconut-xr/xinteraction/react' +import { Card } from '@/card' +import { Checkbox } from '@/checkbox' +import { List, ListItem } from '@/list' +import { Button } from '@/button' +import { Progress } from '@/progress' +import { Tabs, TabsButton } from '@/tabs' +import { Loading } from '@/loading' +import { Slider } from '@/slider' +import { TabBar, TabBarItem } from '@/tab-bar' +import { Defaults } from '@/theme' + +export default function App() { + return ( + + + + + + + + + + + + + + + ) +} + +export function CheckboxesPage() { + return ( + + + + + ) +} + +export function ButtonsPage() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) +} + +export function ListsPage() { + return ( + + + + + Subtitle} + trailingAccessory={} + > + Title + + }> + Title + + Subtitle} + selected + trailingAccessory={} + > + Title + + + + + + Subtitle} + leadingAccessory={} + trailingAccessory={ + + } + > + Title + + } + trailingAccessory={ + + } + > + Title + + Subtitle} + selected + leadingAccessory={} + trailingAccessory={ + + } + > + Title + + + + + + + + Subtitle} + trailingAccessory={} + > + Title + + }> + Title + + Subtitle} + trailingAccessory={} + > + Title + + + + + + Subtitle} + leadingAccessory={} + trailingAccessory={ + + } + > + Title + + } + trailingAccessory={ + + } + > + Title + + Subtitle} + leadingAccessory={} + trailingAccessory={ + + } + > + Title + + + + + + ) +} + +export function ProgressIndicatorsPage() { + return ( + + + + + + + + + + + + + + + ) +} + +export function SegmentedControlsPage() { + return ( + + + + Label + + + Label + + + Label + + + Long Label + + + Disabled + + + + + + Label + + + + Label + + + + Label + + + + Disabled + + + + + Label + + + Label + + + Label + + + Long Label + + + Disabled + + + + ) +} + +export function SlidersPage() { + return ( + + + + + } /> + } /> + + + + + } /> + } /> + + + ) +} + +export function TabBarsPage() { + return ( + + }> + Label + + }> + Label + + }> + Label + + }> + Label + + + ) +} diff --git a/examples/apfel/src/index.tsx b/examples/apfel/src/index.tsx new file mode 100644 index 00000000..feac8eed --- /dev/null +++ b/examples/apfel/src/index.tsx @@ -0,0 +1,9 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import App from './App' + +createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/examples/apfel/tsconfig.json b/examples/apfel/tsconfig.json new file mode 100644 index 00000000..1b8e0236 --- /dev/null +++ b/examples/apfel/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "paths": { + "@/*": ["../../packages/kits/apfel/*"] + } + } +} diff --git a/examples/apfel/vite.config.ts b/examples/apfel/vite.config.ts new file mode 100644 index 00000000..3cf507d2 --- /dev/null +++ b/examples/apfel/vite.config.ts @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite' +import path from 'path' +import react from '@vitejs/plugin-react' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], + optimizeDeps: { + include: ['@react-three/uikit-lucide', '@react-three/uikit'], + }, + resolve: { + alias: [ + { find: '@', replacement: path.resolve(__dirname, '../../packages/kits/apfel') }, + { find: '@react-three/uikit', replacement: path.resolve(__dirname, '../../packages/uikit/src/index.ts') }, + ], + }, +}) diff --git a/examples/card/package.json b/examples/card/package.json index bac92ea0..3dcfca48 100644 --- a/examples/card/package.json +++ b/examples/card/package.json @@ -17,6 +17,7 @@ "three": "^0.160.0" }, "scripts": { - "dev": "vite --host" + "dev": "vite --host", + "build": "vite build" } } diff --git a/examples/card/src/App.jsx b/examples/card/src/App.jsx index 51a3cb38..7a18b075 100644 --- a/examples/card/src/App.jsx +++ b/examples/card/src/App.jsx @@ -2,7 +2,7 @@ import { Environment, MeshPortalMaterial, PerspectiveCamera } from '@react-three import { Canvas, extend, useFrame } from '@react-three/fiber' import { Root, Container, Text, setPreferredColorScheme, Content } from '@react-three/uikit' import { BellRing, Check } from '@react-three/uikit-lucide' -import { DefaultColors, colors } from '@/theme' +import { Defaults, colors } from '@/theme' import { Avatar } from '@/avatar' import { Button } from '@/button' import { CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/card' @@ -26,9 +26,9 @@ export default function App() { - + - + @@ -54,7 +54,7 @@ export function CardPage() { }) return ( - + - + ) } diff --git a/examples/dashboard/package.json b/examples/dashboard/package.json index e1ad8b8b..63a24f6c 100644 --- a/examples/dashboard/package.json +++ b/examples/dashboard/package.json @@ -1,8 +1,7 @@ { "type": "module", "dependencies": { - "@coconut-xr/koestlich": "^0.3.12", - "@coconut-xr/natuerlich": "^0.0.49", + "@coconut-xr/xinteraction": "^0.1.12", "@react-three/drei": "^9.96.1", "@react-three/fiber": "^8.15.13", "@react-three/postprocessing": "^2.16.0", @@ -17,6 +16,7 @@ "zustand": "4" }, "scripts": { - "dev": "vite --host" + "dev": "vite --host", + "build": "vite build" } } diff --git a/examples/dashboard/src/App.tsx b/examples/dashboard/src/App.tsx index 293ec177..a23d1dbe 100644 --- a/examples/dashboard/src/App.tsx +++ b/examples/dashboard/src/App.tsx @@ -5,7 +5,7 @@ import { EffectComposer, TiltShift2 } from '@react-three/postprocessing' import { Container, Root, Text, setPreferredColorScheme } from '@react-three/uikit' import { Activity, CreditCard, DollarSign, Users } from '@react-three/uikit-lucide' -import { DefaultColors, colors } from '@/theme' +import { Defaults, colors } from '@/theme' import { Button } from '@/button' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/card' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/tabs' @@ -30,13 +30,13 @@ export default function App() { gl={{ localClippingEnabled: true }} > - + - + diff --git a/examples/default/package.json b/examples/default/package.json index e3b506b9..ca6ce2de 100644 --- a/examples/default/package.json +++ b/examples/default/package.json @@ -12,6 +12,7 @@ "react-dom": "^18.2.0" }, "scripts": { - "dev": "vite --host" + "dev": "vite --host", + "build": "vite build" } } diff --git a/examples/default/src/App.tsx b/examples/default/src/App.tsx index 124bc167..c4b65038 100644 --- a/examples/default/src/App.tsx +++ b/examples/default/src/App.tsx @@ -6,7 +6,7 @@ import { Perf } from 'r3f-perf' import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/accordion' import { Alert, AlertDescription, AlertIcon, AlertTitle } from '@/alert' -import { DefaultColors, colors } from '@/theme' +import { Defaults, colors } from '@/theme' import { Avatar } from '@/avatar' import { Badge } from '@/badge' import { Button } from '@/button' @@ -71,13 +71,13 @@ export default function App() { justifyContent="center" padding={32} > - + - + ) diff --git a/examples/lucide/package.json b/examples/lucide/package.json index a8d6546b..37e13c84 100644 --- a/examples/lucide/package.json +++ b/examples/lucide/package.json @@ -9,6 +9,7 @@ "react-dom": "^18.2.0" }, "scripts": { - "dev": "vite --host" + "dev": "vite --host", + "build": "vite build" } } diff --git a/examples/market/package.json b/examples/market/package.json index 8df37172..c3deb8d3 100644 --- a/examples/market/package.json +++ b/examples/market/package.json @@ -13,6 +13,7 @@ "three": "^0.160.0" }, "scripts": { - "dev": "vite --host" + "dev": "vite --host", + "build": "vite build" } } diff --git a/examples/market/src/App.tsx b/examples/market/src/App.tsx index 7f8fb791..13dde8c1 100644 --- a/examples/market/src/App.tsx +++ b/examples/market/src/App.tsx @@ -3,7 +3,7 @@ import { Canvas } from '@react-three/fiber' import { EffectComposer, TiltShift2 } from '@react-three/postprocessing' import { Root, Container, Image, Text, Fullscreen, DefaultProperties } from '@react-three/uikit' import { PlusCircle } from '@react-three/uikit-lucide' -import { DefaultColors, colors } from '@/theme.js' +import { Defaults, colors } from '@/theme.js' import { DialogAnchor } from '@/dialog.js' import { Tabs, TabsList, TabsContent, TabsTrigger } from '@/tabs.js' import { Separator } from '@/separator.js' @@ -23,11 +23,11 @@ export default function App() { gl={{ localClippingEnabled: true }} > {/* - + - + @@ -35,11 +35,11 @@ export default function App() { */} - + - + ) diff --git a/packages/kits/apfel/LICENSE b/packages/kits/apfel/LICENSE new file mode 100644 index 00000000..8662f42e --- /dev/null +++ b/packages/kits/apfel/LICENSE @@ -0,0 +1,15 @@ +Copyright 2024 Bela Bohlender + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Copyright 2023 Coconut Capital + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/kits/apfel/button.tsx b/packages/kits/apfel/button.tsx new file mode 100644 index 00000000..8b6e6c90 --- /dev/null +++ b/packages/kits/apfel/button.tsx @@ -0,0 +1,89 @@ +import { Container, DefaultProperties } from '@react-three/uikit' +import { ComponentPropsWithoutRef } from 'react' +import { colors } from './theme' + +const sizes = { + xs: { + iconSize: 12, + height: 24, + padding: 6, + borderRadius: 4, + fontSize: 8, + }, + sm: { + height: 32, + padding: 12, + borderRadius: 8, + fontSize: 12, + iconSize: 14, + }, + md: { + height: 44, + padding: 20, + borderRadius: 12, + fontSize: 14, + iconSize: 18, + }, + lg: { + height: 52, + padding: 25, + borderRadius: 16, + fontSize: 16, + iconSize: 22, + }, + xl: { + height: 56, + padding: 29, + borderRadius: 20, + fontSize: 18, + iconSize: 28, + }, +} + +type Variant = 'pill' | 'rect' | 'icon' +type Size = keyof typeof sizes + +export function Button({ + children, + size = 'md', + variant = 'rect', + platter, + selected, + disabled, + ...props +}: ComponentPropsWithoutRef & { + size?: Size + variant?: Variant + platter?: boolean + selected?: boolean + disabled?: boolean +}) { + const { borderRadius, fontSize, height, padding, iconSize } = sizes[size] + return ( + + + {children} + + + ) +} diff --git a/packages/kits/apfel/card.tsx b/packages/kits/apfel/card.tsx new file mode 100644 index 00000000..1cddda1d --- /dev/null +++ b/packages/kits/apfel/card.tsx @@ -0,0 +1,20 @@ +import { Container, DefaultProperties } from '@react-three/uikit' +import { ComponentPropsWithoutRef } from 'react' +import { GlassMaterial, colors } from './theme' + +export function Card({ children, ...props }: ComponentPropsWithoutRef) { + return ( + + {children} + + ) +} diff --git a/packages/kits/apfel/checkbox.tsx b/packages/kits/apfel/checkbox.tsx new file mode 100644 index 00000000..4d3f70ad --- /dev/null +++ b/packages/kits/apfel/checkbox.tsx @@ -0,0 +1,52 @@ +import { Container } from '@react-three/uikit' +import { Check } from '@react-three/uikit-lucide' +import { ComponentPropsWithoutRef, useState } from 'react' +import { colors } from './theme' + +type CheckboxProps = ComponentPropsWithoutRef & { + selected?: boolean + defaultSelected?: boolean + disabled?: boolean + onSelectedChange?(value: boolean): void +} + +export function Checkbox({ selected, disabled = false, defaultSelected, onSelectedChange, ...props }: CheckboxProps) { + const [internalValue, setInternalValue] = useState(defaultSelected ?? false) + const value = selected != null ? selected : internalValue + + return ( + { + if (disabled) { + return + } + setInternalValue(!value) + onSelectedChange?.(!value) + props.onClick?.(e) + }} + > + {value && } + + ) +} diff --git a/packages/kits/apfel/list.tsx b/packages/kits/apfel/list.tsx new file mode 100644 index 00000000..48a03fde --- /dev/null +++ b/packages/kits/apfel/list.tsx @@ -0,0 +1,79 @@ +import { Container, DefaultProperties } from '@react-three/uikit' +import { ComponentPropsWithoutRef, ReactNode, createContext, useContext } from 'react' +import { colors } from './theme' + +type Type = 'plain' | 'inset' + +type ListProps = ComponentPropsWithoutRef & { + type?: Type +} + +const ListContext = createContext('plain') + +export function List({ type = 'plain', ...props }: ListProps) { + return ( + + + + ) +} + +type ListItemProps = ComponentPropsWithoutRef & { + subtitle?: ReactNode + selected?: boolean + leadingAccessory?: ReactNode + trailingAccessory?: ReactNode + isFirst?: boolean + isLast?: boolean +} + +export function ListItem({ + children, + subtitle, + selected, + leadingAccessory, + trailingAccessory, + isFirst, + isLast, + ...props +}: ListItemProps) { + const type = useContext(ListContext) + + return ( + + + {leadingAccessory && {leadingAccessory}} + + {children} + + {subtitle} + + + {trailingAccessory && {trailingAccessory}} + + + ) +} diff --git a/packages/kits/apfel/loading.tsx b/packages/kits/apfel/loading.tsx new file mode 100644 index 00000000..07c26519 --- /dev/null +++ b/packages/kits/apfel/loading.tsx @@ -0,0 +1,58 @@ +import { Container } from '@react-three/uikit' +import { useFrame } from '@react-three/fiber' +import { ComponentPropsWithoutRef, useMemo } from 'react' +import { signal } from '@preact/signals-core' +import { colors } from './theme' + +const sizes = { + sm: { diameter: 20, pillWidth: 3, pillHeight: 6 }, + md: { diameter: 28, pillWidth: 4, pillHeight: 10 }, + lg: { diameter: 44, pillWidth: 6, pillHeight: 16 }, +} + +type Size = keyof typeof sizes + +const PILL_AMOUNT = 8 + +export function Loading({ + size = 'md', + ...props +}: ComponentPropsWithoutRef & { + size?: Size +}) { + const pillOpacities = useMemo(() => new Array(PILL_AMOUNT).fill(undefined).map(() => signal(0)), []) + + useFrame(({ clock }) => { + for (let i = 0; i < PILL_AMOUNT; i++) { + const opacity = pillOpacities[i] + const interval = 0.8 + const pillOffset = (i / PILL_AMOUNT) * interval + opacity.value = 1 - ((clock.elapsedTime + pillOffset) % interval) + } + }) + + const { diameter, pillHeight, pillWidth } = sizes[size] + + return ( + + {pillOpacities.map((opacity, i) => ( + + + + ))} + + ) +} diff --git a/packages/kits/apfel/package.json b/packages/kits/apfel/package.json new file mode 100644 index 00000000..aac20766 --- /dev/null +++ b/packages/kits/apfel/package.json @@ -0,0 +1,18 @@ +{ + "scripts": { + "check:prettier": "prettier --check .", + "check:eslint": "eslint '**/*.{tsx,ts}'", + "fix:prettier": "prettier --write .", + "fix:eslint": "eslint '**/*.{tsx,ts}' --fix" + }, + "devDependencies": { + "@preact/signals-core": "^1.5.1", + "@react-three/fiber": "^8.15.13", + "@react-three/uikit": "workspace:^", + "@react-three/uikit-lucide": "workspace:^", + "@types/react": "^18.2.47", + "@types/three": "^0.160.0", + "three": "^0.160.0" + }, + "type": "module" +} diff --git a/packages/kits/apfel/progress.tsx b/packages/kits/apfel/progress.tsx new file mode 100644 index 00000000..3a19e0ef --- /dev/null +++ b/packages/kits/apfel/progress.tsx @@ -0,0 +1,30 @@ +import { Container } from '@react-three/uikit' +import { ComponentPropsWithoutRef } from 'react' +import { colors } from './theme' + +export function Progress({ + value = 0, + ...props +}: ComponentPropsWithoutRef & { + value?: number +}) { + return ( + + + + ) +} diff --git a/packages/kits/apfel/registry.json b/packages/kits/apfel/registry.json new file mode 100644 index 00000000..a484d328 --- /dev/null +++ b/packages/kits/apfel/registry.json @@ -0,0 +1,30 @@ +{ + "button": { + "files": ["button.tsx"] + }, + "card": { + "files": ["card.tsx"] + }, + "checkbox": { + "files": ["checkbox.tsx"] + }, + "list": { + "files": ["list.tsx"] + }, + "loading": { + "files": ["loading.tsx"] + }, + "progress": { + "files": ["progress.tsx"] + }, + "slider": { + "files": ["slider.tsx"] + }, + "tab-bar": { + "files": ["tab-bar.tsx"], + "registryDependencies": ["card"] + }, + "tabs": { + "files": ["tabs.tsx"] + } +} diff --git a/packages/kits/apfel/slider.tsx b/packages/kits/apfel/slider.tsx new file mode 100644 index 00000000..79872d3f --- /dev/null +++ b/packages/kits/apfel/slider.tsx @@ -0,0 +1,148 @@ +import { ThreeEvent } from '@react-three/fiber' +import type { EventHandlers } from '@react-three/fiber/dist/declarations/src/core/events' +import { ComponentInternals, Container, DefaultProperties } from '@react-three/uikit' +import { ComponentPropsWithoutRef, ReactNode, useMemo, useRef, useState } from 'react' +import { Vector3 } from 'three' +import { clamp } from 'three/src/math/MathUtils.js' +import { GlassMaterial, colors } from './theme' + +const vectorHelper = new Vector3() + +const sizes = { + xs: { height: 12, knobHeight: 8 }, + sm: { height: 16, knobHeight: 12 }, + md: { height: 28, knobHeight: 20 }, + lg: { height: 44, knobHeight: 32 }, +} + +type Size = keyof typeof sizes + +export function Slider({ + value: providedValue, + defaultValue, + onValueChange, + min = 0, + max = 100, + step = 1, + size = 'md', + icon, + disabled, + ...props +}: ComponentPropsWithoutRef & { + disabled?: boolean + value?: number + defaultValue?: number + onValueChange?(value: number): void + min?: number + max?: number + step?: number + size?: Size + icon?: ReactNode +}) { + const [uncontrolled, setUncontrolled] = useState(defaultValue) + const value = providedValue ?? uncontrolled ?? 50 + const ref = useRef(null) + const onChange = useRef(onValueChange) + onChange.current = onValueChange + const hasProvidedValue = providedValue != null + const handler = useMemo(() => { + let down: boolean = false + function setValue(e: ThreeEvent) { + if (ref.current == null) { + return + } + vectorHelper.copy(e.point) + ref.current.interactionPanel.worldToLocal(vectorHelper) + const newValue = Math.min( + Math.max(Math.round(((vectorHelper.x + 0.5) * (max - min) + min) / step) * step, min), + max, + ) + if (!hasProvidedValue) { + setUncontrolled(newValue) + } + onChange.current?.(newValue) + e.stopPropagation() + } + return { + onPointerDown(e) { + down = true + setValue(e) + ;(e.target as HTMLElement).setPointerCapture(e.pointerId) + }, + onPointerMove(e) { + if (!down) { + return + } + setValue(e) + }, + onPointerUp(e) { + if (!down) { + return + } + down = false + e.stopPropagation() + }, + } satisfies EventHandlers + }, [max, min, hasProvidedValue, step]) + + const range = max - min + const width = `${((1 - 0.03) * clamp(value / range, 0, 1) + 0.03) * 100}%` as const + + const { height, knobHeight } = sizes[size] + const knobPadding = (height - knobHeight) / 2 + const showIcon = size == 'md' || size == 'lg' + const iconHeight = size === 'md' ? 12 : 18 + + return ( + + + + {showIcon && ( + + + {icon} + + + )} + + + + + ) +} diff --git a/packages/kits/apfel/tab-bar.tsx b/packages/kits/apfel/tab-bar.tsx new file mode 100644 index 00000000..bf9aadfb --- /dev/null +++ b/packages/kits/apfel/tab-bar.tsx @@ -0,0 +1,112 @@ +import { Container, DefaultProperties } from '@react-three/uikit' +import { + ComponentPropsWithoutRef, + ReactNode, + SetStateAction, + createContext, + useCallback, + useContext, + useMemo, + useRef, + useState, +} from 'react' +import { Card } from './card' +import { colors } from './theme' + +type TabBarContext = { + value: unknown + setValue(value: unknown): void + isExpanded: boolean + setIsExpanded(value: SetStateAction): void +} + +const TabBarContext = createContext(undefined) + +export function TabBar({ + value: valueProp, + defaultValue, + onValueChange, + ...props +}: ComponentPropsWithoutRef & { + value?: string + defaultValue?: string + onValueChange?(value: string): void +}) { + const [internalValue, setInternalValue] = useState(defaultValue) + const value = valueProp !== undefined ? valueProp : internalValue + + const setValue = useCallback((value: string) => { + setInternalValue(value) + onValueChange?.(value) + }, []) + + const [isExpanded, setIsExpanded] = useState(false) + const context = useMemo(() => ({ isExpanded, setIsExpanded, value, setValue }), [isExpanded, value]) + + const timeoutRef = useRef() + + return ( + + { + if (hovered) { + timeoutRef.current = setTimeout(() => setIsExpanded(true), 300) as any + } else { + clearTimeout(timeoutRef.current) + setIsExpanded(false) + } + }} + {...props} + /> + + ) +} + +export function TabBarItem({ + value: tabValue, + children, + icon, + ...props +}: ComponentPropsWithoutRef & { + value: string + icon: ReactNode +}) { + const { isExpanded, value, setValue } = useContext(TabBarContext)! + const isSelected = value === tabValue + + return ( + { + setValue(tabValue) + props.onClick?.(e) + }} + > + + + + {icon} + + + {isExpanded && {children}} + + + ) +} diff --git a/packages/kits/apfel/tabs.tsx b/packages/kits/apfel/tabs.tsx new file mode 100644 index 00000000..ca39efb1 --- /dev/null +++ b/packages/kits/apfel/tabs.tsx @@ -0,0 +1,106 @@ +import { Container, DefaultProperties } from '@react-three/uikit' +import { ComponentPropsWithoutRef, createContext, useContext, useMemo, useRef, useState } from 'react' +import { GlassMaterial, colors } from './theme' + +type TabsContext = { + value?: string + onValueChange?(value: string): void + disabled?: boolean +} + +const TabsContext = createContext({}) + +export function Tabs({ + value, + defaultValue, + onValueChange, + disabled, + ...props +}: ComponentPropsWithoutRef & { + value?: string + defaultValue?: string + onValueChange?(value: string): void + disabled?: boolean +}) { + const [internalValue, setInternalValue] = useState(defaultValue) + const currentValue = value != null ? value : internalValue + + const onValueChangeRef = useRef(onValueChange) + onValueChangeRef.current = onValueChange + + const context = useMemo( + () => ({ + value: currentValue, + onValueChange: (value) => { + setInternalValue(value) + onValueChangeRef.current?.(value) + }, + disabled, + }), + [currentValue, disabled], + ) + + const opacity = disabled ? 0.3 : 0.4 + + return ( + + + + ) +} + +type SegmentedControlButtonProps = ComponentPropsWithoutRef & { + value: string + disabled?: boolean +} + +export function TabsButton({ children, value, disabled, ...props }: SegmentedControlButtonProps) { + const { value: currentValue, onValueChange, disabled: tabsDisabled } = useContext(TabsContext) as TabsContext + + return ( + { + if (disabled) return + onValueChange?.(value) + props.onClick?.(e) + }} + > + {currentValue === value && !tabsDisabled && ( + + )} + + + {children} + + + + ) +} diff --git a/packages/kits/apfel/text-input.tsx b/packages/kits/apfel/text-input.tsx new file mode 100644 index 00000000..7fc401b7 --- /dev/null +++ b/packages/kits/apfel/text-input.tsx @@ -0,0 +1,99 @@ +/*import { Input } from "@coconut-xr/input"; +import { Container, DefaultStyleProvider, Text } from "@coconut-xr/koestlich"; +import { makeBorderMaterial } from "@coconut-xr/xmaterials"; +import { ComponentPropsWithoutRef, ReactNode, useState } from "react"; +import { MeshPhongMaterial } from "three"; + +type Style = "pill" | "rect"; + +type TextInputProps = ComponentPropsWithoutRef & { + style?: Style; + disabled?: boolean; + placeholder?: string; + value: string; + onValueChange: (value: string) => void; + prefix?: ReactNode; +}; + +const material = makeBorderMaterial(MeshPhongMaterial, { + specular: "#888", + shininess: 50, +}); + +export function TextInput({ + style = "rect", + disabled, + placeholder, + prefix, + value, + onValueChange, + ...props +}: TextInputProps) { + const [hoverCount, setHoverCount] = useState(0); + + const opacity = disabled ? 0.3 : hoverCount > 0 ? 0.2 : 0.4; + + return ( + { + setHoverCount((current) => current + 1); + props.onPointerEnter?.(e); + }} + onPointerLeave={(e) => { + setHoverCount((current) => current - 1); + props.onPointerLeave?.(e); + }} + > + + {prefix && ( + + + {prefix} + + + )} + + 0 ? 0 : undefined} + > + {placeholder} + + + + + + ); +} +*/ diff --git a/packages/kits/apfel/theme.tsx b/packages/kits/apfel/theme.tsx new file mode 100644 index 00000000..8909481d --- /dev/null +++ b/packages/kits/apfel/theme.tsx @@ -0,0 +1,38 @@ +import { DefaultProperties } from '@react-three/uikit' +import { ComponentPropsWithoutRef } from 'react' +import { Color, MeshPhongMaterial } from 'three' + +export class GlassMaterial extends MeshPhongMaterial { + constructor() { + super({ + specular: '#555', + shininess: 100, + }) + } +} + +function hsl(h: number, s: number, l: number) { + return new Color().setHSL(h / 360, s / 100, l / 100, 'srgb') +} + +export const colors = { + foreground: hsl(0, 0, 100), + background: hsl(0, 0, 0), + card: hsl(0, 0, 53), + cardForeground: hsl(0, 0, 100), + accent: hsl(210, 100, 52), + accentForeground: hsl(0, 0, 100), +} + +export function Defaults(props: ComponentPropsWithoutRef) { + return ( + + ) +} diff --git a/packages/kits/default/LICENSE b/packages/kits/default/LICENSE index 5dcf5030..a6dbdd62 100644 --- a/packages/kits/default/LICENSE +++ b/packages/kits/default/LICENSE @@ -1,3 +1,11 @@ +Copyright 2024 Bela Bohlender + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + MIT License Copyright (c) 2023 shadcn diff --git a/packages/kits/default/slider.tsx b/packages/kits/default/slider.tsx index 99801409..ec3097d0 100644 --- a/packages/kits/default/slider.tsx +++ b/packages/kits/default/slider.tsx @@ -26,7 +26,8 @@ export function Slider({ } & Omit, 'children'>) { const [uncontrolled, setUncontrolled] = useState(defaultValue) const value = providedValue ?? uncontrolled ?? 50 - const percentage = `${value}%` as const + const range = max - min + const percentage = `${(100 * value) / range}%` as const const ref = useRef(null) const onChange = useRef(onValueChange) onChange.current = onValueChange diff --git a/packages/kits/default/theme.tsx b/packages/kits/default/theme.tsx index feda120a..11fc571c 100644 --- a/packages/kits/default/theme.tsx +++ b/packages/kits/default/theme.tsx @@ -51,7 +51,7 @@ export const colors = basedOnPreferredColorScheme({ }, }) -export function DefaultColors(props: ComponentPropsWithoutRef) { +export function Defaults(props: ComponentPropsWithoutRef) { return ( = T & { + active?: T + onActiveChange?: (active: boolean) => void +} + +export type ActiveEventHandlers = Pick + +export function useApplyActiveProperties( + collection: ManagerCollection, + properties: WithClasses> & EventHandlers, +): ActiveEventHandlers | undefined { + const activeSignal = useMemo(() => signal>([]), []) + // eslint-disable-next-line react-hooks/exhaustive-deps + const translate = useMemo(() => createConditionalPropertyTranslator(() => activeSignal.value.length > 0), []) + let activePropertiesExist = false + + useTraverseProperties(properties, (p) => { + if (p.active == null) { + return + } + activePropertiesExist = true + translate(collection, p.active) + }) + + if (!activePropertiesExist && properties.onActiveChange == null) { + //no need to listen to hover + activeSignal.value.length = 0 + return undefined + } + const onLeave = (e: ThreeEvent) => { + activeSignal.value = activeSignal.value.filter((id) => id != e.pointerId) + if (properties.onActiveChange == null || activeSignal.value.length > 0) { + return + } + properties.onActiveChange(false) + } + return { + onPointerDown: (e) => { + activeSignal.value = [e.pointerId, ...activeSignal.value] + if (properties.onActiveChange == null || activeSignal.value.length != 1) { + return + } + properties.onActiveChange(true) + }, + onPointerUp: onLeave, + onPointerLeave: onLeave, + } +} diff --git a/packages/uikit/src/components/container.tsx b/packages/uikit/src/components/container.tsx index 4e03ba28..9c67a113 100644 --- a/packages/uikit/src/components/container.tsx +++ b/packages/uikit/src/components/container.tsx @@ -47,6 +47,7 @@ import { useApplyResponsiveProperties } from '../responsive.js' import { Group } from 'three' import { ElementType, OrderInfoProvider, ZIndexOffset, useOrderInfo } from '../order.js' import { useApplyPreferredColorSchemeProperties } from '../dark.js' +import { useApplyActiveProperties } from '../active.js' export type ContainerProperties = WithConditionals< WithClasses< @@ -108,6 +109,7 @@ export const Container = forwardRef< useApplyPreferredColorSchemeProperties(collection, properties) useApplyResponsiveProperties(collection, properties) const hoverHandlers = useApplyHoverProperties(collection, properties) + const activeHandlers = useApplyActiveProperties(collection, properties) finalizeCollection(collection) useLayoutListeners(properties, node.size) @@ -129,7 +131,13 @@ export const Container = forwardRef< useComponentInternals(ref, node, interactionPanel, scrollPosition) return ( - + diff --git a/packages/uikit/src/components/content.tsx b/packages/uikit/src/components/content.tsx index e4ba8db0..bae7e9e8 100644 --- a/packages/uikit/src/components/content.tsx +++ b/packages/uikit/src/components/content.tsx @@ -46,6 +46,7 @@ import { WithClasses, useApplyProperties } from '../properties/default.js' import { useApplyResponsiveProperties } from '../responsive.js' import { CameraDistanceRef, ElementType, OrderInfo, ZIndexOffset, setupRenderOrder, useOrderInfo } from '../order.js' import { useApplyPreferredColorSchemeProperties } from '../dark.js' +import { useApplyActiveProperties } from '../active.js' export type ContentProperties = WithConditionals< WithClasses< @@ -111,6 +112,7 @@ export const Content = forwardRef< useApplyPreferredColorSchemeProperties(collection, properties) useApplyResponsiveProperties(collection, properties) const hoverHandlers = useApplyHoverProperties(collection, properties) + const activeHandlers = useApplyActiveProperties(collection, properties) const aspectRatio = useMemo( () => computed(() => { @@ -159,7 +161,13 @@ export const Content = forwardRef< useComponentInternals(ref, node, interactionPanel) return ( - + diff --git a/packages/uikit/src/components/custom.tsx b/packages/uikit/src/components/custom.tsx index c0e5d930..8541a46b 100644 --- a/packages/uikit/src/components/custom.tsx +++ b/packages/uikit/src/components/custom.tsx @@ -28,6 +28,7 @@ import { useApplyResponsiveProperties } from '../responsive.js' import { ElementType, setupRenderOrder, useOrderInfo, ZIndexOffset } from '../order.js' import { effect } from '@preact/signals-core' import { useApplyPreferredColorSchemeProperties } from '../dark.js' +import { useApplyActiveProperties } from '../active.js' export type CustomContainerProperties = WithConditionals< WithClasses>> @@ -97,6 +98,7 @@ export const CustomContainer = forwardRef< useApplyPreferredColorSchemeProperties(collection, properties) useApplyResponsiveProperties(collection, properties) const hoverHandlers = useApplyHoverProperties(collection, properties) + const activeHandlers = useApplyActiveProperties(collection, properties) finalizeCollection(collection) useLayoutListeners(properties, node.size) @@ -105,7 +107,13 @@ export const CustomContainer = forwardRef< useComponentInternals(ref, node, meshRef) return ( - + + diff --git a/packages/uikit/src/components/image.tsx b/packages/uikit/src/components/image.tsx index e2f47b83..ee15c83b 100644 --- a/packages/uikit/src/components/image.tsx +++ b/packages/uikit/src/components/image.tsx @@ -41,6 +41,7 @@ import { WithClasses, useApplyProperties } from '../properties/default.js' import { useApplyResponsiveProperties } from '../responsive.js' import { ElementType, ZIndexOffset, setupRenderOrder, useOrderInfo } from '../order.js' import { useApplyPreferredColorSchemeProperties } from '../dark.js' +import { useApplyActiveProperties } from '../active.js' export type ImageFit = 'cover' | 'fill' const FIT_DEFAULT: ImageFit = 'fill' @@ -135,6 +136,7 @@ export const Image = forwardRef< useApplyPreferredColorSchemeProperties(collection, properties) useApplyResponsiveProperties(collection, properties) const hoverHandlers = useApplyHoverProperties(collection, properties) + const activeHandlers = useApplyActiveProperties(collection, properties) writeCollection(collection, 'backgroundColor', 0xffffff) if (properties.keepAspectRatio ?? true) { writeCollection(collection, 'aspectRatio', aspectRatio) @@ -164,7 +166,13 @@ export const Image = forwardRef< useComponentInternals(ref, node, mesh) return ( - + ) diff --git a/packages/uikit/src/components/label.tsx b/packages/uikit/src/components/label.tsx deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/uikit/src/components/root.tsx b/packages/uikit/src/components/root.tsx index 7ba72c27..8f1dcd36 100644 --- a/packages/uikit/src/components/root.tsx +++ b/packages/uikit/src/components/root.tsx @@ -51,6 +51,7 @@ import { RootSizeProvider, useApplyResponsiveProperties } from '../responsive.js import { loadYogaFromGH } from '../flex/load-binary.js' import { ElementType, OrderInfoProvider, patchRenderOrder, useOrderInfo } from '../order.js' import { useApplyPreferredColorSchemeProperties } from '../dark.js' +import { useApplyActiveProperties } from '../active.js' export const DEFAULT_PRECISION = 0.1 export const DEFAULT_PIXEL_SIZE = 0.002 @@ -157,6 +158,7 @@ export const Root = forwardRef< useApplyPreferredColorSchemeProperties(collection, properties) useApplyResponsiveProperties(collection, properties, node.size) const hoverHandlers = useApplyHoverProperties(collection, properties) + const activeHandlers = useApplyActiveProperties(collection, properties) writeCollection(collection, 'width', useDivide(sizeX, pixelSize)) writeCollection(collection, 'height', useDivide(sizeY, pixelSize)) finalizeCollection(collection) @@ -179,7 +181,13 @@ export const Root = forwardRef< return ( <> - + diff --git a/packages/uikit/src/components/svg.tsx b/packages/uikit/src/components/svg.tsx index c777da09..7bdbc008 100644 --- a/packages/uikit/src/components/svg.tsx +++ b/packages/uikit/src/components/svg.tsx @@ -47,6 +47,7 @@ import { WithClasses, useApplyProperties } from '../properties/default.js' import { useApplyResponsiveProperties } from '../responsive.js' import { CameraDistanceRef, ElementType, OrderInfo, ZIndexOffset, setupRenderOrder, useOrderInfo } from '../order.js' import { useApplyPreferredColorSchemeProperties } from '../dark.js' +import { useApplyActiveProperties } from '../active.js' export type SvgProperties = WithConditionals< WithClasses< @@ -194,6 +195,7 @@ export const Svg = forwardRef< useApplyPreferredColorSchemeProperties(collection, properties) useApplyResponsiveProperties(collection, properties) const hoverHandlers = useApplyHoverProperties(collection, properties) + const activeHandlers = useApplyActiveProperties(collection, properties) writeCollection(collection, 'aspectRatio', aspectRatio) finalizeCollection(collection) @@ -235,7 +237,13 @@ export const Svg = forwardRef< useComponentInternals(ref, node, interactionPanel) return ( - + diff --git a/packages/uikit/src/components/text.tsx b/packages/uikit/src/components/text.tsx index 7515355c..9c2b0b8a 100644 --- a/packages/uikit/src/components/text.tsx +++ b/packages/uikit/src/components/text.tsx @@ -40,6 +40,7 @@ import { useApplyResponsiveProperties } from '../responsive.js' import { Group } from 'three' import { ElementType, ZIndexOffset, useOrderInfo } from '../order.js' import { useApplyPreferredColorSchemeProperties } from '../dark.js' +import { useApplyActiveProperties } from '../active.js' export type TextProperties = WithConditionals< WithClasses< @@ -100,6 +101,7 @@ export const Text = forwardRef< useApplyPreferredColorSchemeProperties(collection, properties) useApplyResponsiveProperties(collection, properties) const hoverHandlers = useApplyHoverProperties(collection, properties) + const activeHandlers = useApplyActiveProperties(collection, properties) writeCollection(collection, 'measureFunc', measureFunc) finalizeCollection(collection) @@ -108,7 +110,13 @@ export const Text = forwardRef< useComponentInternals(ref, node, interactionPanel) return ( - + ) diff --git a/packages/uikit/src/components/utils.ts b/packages/uikit/src/components/utils.ts index 66372aa7..52094ee9 100644 --- a/packages/uikit/src/components/utils.ts +++ b/packages/uikit/src/components/utils.ts @@ -5,12 +5,14 @@ import { FlexNode, Inset } from '../flex/node.js' import { WithHover } from '../hover.js' import { WithResponsive } from '../responsive.js' import { WithPreferredColorScheme } from '../dark.js' +import { WithActive } from '../active.js' -export type WithConditionals = WithHover & WithResponsive & WithPreferredColorScheme +export type WithConditionals = WithHover & WithResponsive & WithPreferredColorScheme & WithActive export type ComponentInternals = { pixelSize: number size: ReadonlySignal + center: ReadonlySignal borderInset: ReadonlySignal paddingInset: ReadonlySignal scrollPosition?: Signal @@ -29,6 +31,7 @@ export function useComponentInternals( borderInset: node.borderInset, paddingInset: node.paddingInset, pixelSize: node.pixelSize, + center: node.relativeCenter, size: node.size, interactionPanel: interactionPanel instanceof Mesh ? interactionPanel : interactionPanel.current!, scrollPosition, diff --git a/packages/uikit/src/flex/node.ts b/packages/uikit/src/flex/node.ts index fbd84e81..015b9b08 100644 --- a/packages/uikit/src/flex/node.ts +++ b/packages/uikit/src/flex/node.ts @@ -1,4 +1,4 @@ -import { Group, Vector2Tuple } from 'three' +import { Group, Object3D, Vector2Tuple } from 'three' import { Signal, batch, computed, effect, signal } from '@preact/signals-core' import { EDGE_TOP, @@ -133,15 +133,19 @@ export class FlexNode implements WithImmediateProperties { } //commiting the children - const children = this.children[0]?.groupRef.current?.parent?.children! + let groupChildren: Array | undefined this.children.sort((child1, child2) => { - const i1 = children.indexOf(child1.groupRef.current as any) + groupChildren ??= child1.groupRef.current?.parent?.children + if (groupChildren == null) { + return 0 + } + const i1 = groupChildren.indexOf(child1.groupRef.current as any) if (i1 === -1) { throw new Error( `${child1.groupRef.current} doesnt have the same parent as ${this.children[0].groupRef.current}`, ) } - const i2 = children.indexOf(child2.groupRef.current as any) + const i2 = groupChildren.indexOf(child2.groupRef.current as any) if (i2 === -1) { throw new Error( `${child2.groupRef.current} doesnt have the same parent as ${this.children[0].groupRef.current}`, diff --git a/packages/uikit/src/panel/react.tsx b/packages/uikit/src/panel/react.tsx index 2a0b1955..f3f71183 100644 --- a/packages/uikit/src/panel/react.tsx +++ b/packages/uikit/src/panel/react.tsx @@ -16,16 +16,19 @@ import { ManagerCollection, PropertyTransformation } from '../properties/utils.j import { useBatchedProperties } from '../properties/batched.js' import { CameraDistanceRef, ElementType, OrderInfo } from '../order.js' import { panelGeometry } from './utils.js' +import { ActiveEventHandlers } from '../active.js' export function InteractionGroup({ handlers, hoverHandlers, + activeHandlers, matrix, children, groupRef, }: { handlers: EventHandlers hoverHandlers: HoverEventHandlers | undefined + activeHandlers: ActiveEventHandlers | undefined matrix: Signal children?: ReactNode groupRef: RefObject @@ -43,12 +46,13 @@ export function InteractionGroup({ /** handlers + hover handlers */ onPointerOut={mergeHandlers(handlers.onPointerOut, hoverHandlers?.onPointerOut)} onPointerOver={mergeHandlers(handlers.onPointerOver, hoverHandlers?.onPointerOver)} + /** handlers + active handlers */ + onPointerUp={mergeHandlers(handlers.onPointerUp, activeHandlers?.onPointerUp)} + onPointerDown={mergeHandlers(handlers.onPointerDown, activeHandlers?.onPointerDown)} + onPointerLeave={mergeHandlers(handlers.onPointerLeave, activeHandlers?.onPointerLeave)} /** only handlers */ - onPointerUp={handlers.onPointerUp} - onPointerDown={handlers.onPointerDown} onPointerMove={handlers.onPointerMove} onWheel={handlers.onWheel} - onPointerLeave={handlers.onPointerLeave} onClick={handlers.onClick} onContextMenu={handlers.onContextMenu} onDoubleClick={handlers.onDoubleClick} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 78d7b51f..829685fe 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -84,6 +84,54 @@ importers: specifier: ^5.0.12 version: 5.0.12(@types/node@20.11.0) + examples/apfel: + dependencies: + '@coconut-xr/xinteraction': + specifier: ^0.1.12 + version: 0.1.12(@react-three/fiber@8.15.13)(react@18.2.0)(three@0.160.0) + '@preact/signals-core': + specifier: ^1.5.1 + version: 1.5.1 + '@react-three/drei': + specifier: ^9.96.1 + version: 9.96.1(@react-three/fiber@8.15.13)(@types/react@18.2.47)(@types/three@0.160.0)(react-dom@18.2.0)(react@18.2.0)(three@0.160.0) + '@react-three/fiber': + specifier: ^8.15.13 + version: 8.15.13(react-dom@18.2.0)(react@18.2.0)(three@0.160.0) + '@react-three/postprocessing': + specifier: ^2.16.0 + version: 2.16.0(@react-three/fiber@8.15.13)(@types/three@0.160.0)(react@18.2.0)(three@0.160.0) + '@react-three/rapier': + specifier: ^1.3.0 + version: 1.3.0(@react-three/fiber@8.15.13)(react@18.2.0)(three@0.160.0) + '@react-three/uikit': + specifier: workspace:^ + version: link:../../packages/uikit + '@react-three/uikit-lucide': + specifier: workspace:^ + version: link:../../packages/icons/lucide + '@splinetool/r3f-spline': + specifier: ^1.0.2 + version: 1.0.2(@react-three/fiber@8.15.13)(@splinetool/loader@1.0.54) + '@types/three': + specifier: ^0.160.0 + version: 0.160.0 + maath: + specifier: ^0.10.7 + version: 0.10.7(@types/three@0.160.0)(three@0.160.0) + r3f-perf: + specifier: ^7.1.2 + version: 7.1.2(@react-three/fiber@8.15.13)(@types/react@18.2.47)(@types/three@0.160.0)(react-dom@18.2.0)(react@18.2.0)(three@0.160.0) + react: + specifier: ^18.2.0 + version: 18.2.0 + react-dom: + specifier: ^18.2.0 + version: 18.2.0(react@18.2.0) + three: + specifier: ^0.160.0 + version: 0.160.0 + examples/card: dependencies: '@preact/signals-core': @@ -131,12 +179,9 @@ importers: examples/dashboard: dependencies: - '@coconut-xr/koestlich': - specifier: ^0.3.12 - version: 0.3.12(@react-three/fiber@8.15.13)(react@18.2.0)(three@0.160.0) - '@coconut-xr/natuerlich': - specifier: ^0.0.49 - version: 0.0.49(@coconut-xr/koestlich@0.3.12)(@react-three/fiber@8.15.13)(@types/react@18.2.47)(react@18.2.0)(three@0.160.0) + '@coconut-xr/xinteraction': + specifier: ^0.1.12 + version: 0.1.12(@react-three/fiber@8.15.13)(react@18.2.0)(three@0.160.0) '@react-three/drei': specifier: ^9.96.1 version: 9.96.1(@react-three/fiber@8.15.13)(@types/react@18.2.47)(@types/three@0.160.0)(react-dom@18.2.0)(react@18.2.0)(three@0.160.0) @@ -314,6 +359,30 @@ importers: specifier: ^3.0.0 version: 3.0.0 + packages/kits/apfel: + devDependencies: + '@preact/signals-core': + specifier: ^1.5.1 + version: 1.5.1 + '@react-three/fiber': + specifier: ^8.15.13 + version: 8.15.13(react-dom@18.2.0)(react@18.2.0)(three@0.160.0) + '@react-three/uikit': + specifier: workspace:^ + version: link:../../uikit + '@react-three/uikit-lucide': + specifier: workspace:^ + version: link:../../icons/lucide + '@types/react': + specifier: ^18.2.47 + version: 18.2.47 + '@types/three': + specifier: ^0.160.0 + version: 0.160.0 + three: + specifier: ^0.160.0 + version: 0.160.0 + packages/kits/default: dependencies: tunnel-rat: @@ -674,64 +743,6 @@ packages: resolution: {integrity: sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ==} dev: false - /@coconut-xr/flex@3.0.4: - resolution: {integrity: sha512-F8OVP+0oBSvX3aQohHAV+XmhvbU66FtOKXJZ70Z0uolaecPpc13XsJU+U1ryEZ+0ywCP7Nd1UWmCRK+UKqrGZA==} - dependencies: - base64-js: 1.5.1 - yoga-wasm-web: 0.3.3 - dev: false - - /@coconut-xr/glyph@0.0.6(three@0.160.0): - resolution: {integrity: sha512-+2kwmNpZzKM17mrWpAlSODziK+XXp1OL4Cb/u8smolCy5mcELcTvaergWJzipzZoDa6SBIWGX5HjjHSlfHoepw==} - peerDependencies: - three: '>=0.140.0' - dependencies: - three: 0.160.0 - dev: false - - /@coconut-xr/koestlich@0.3.12(@react-three/fiber@8.15.13)(react@18.2.0)(three@0.160.0): - resolution: {integrity: sha512-Dex78txRqFIHSsNeIV3cWnQtwpcKM/n8RgBYx860lhGpD5vKTK3kf6N+6DWIpyvi7GVZ+vW/5/ea3Y1ReRvfnQ==} - peerDependencies: - '@react-three/fiber': '*' - react: '*' - three: '>=0.140.0' - dependencies: - '@coconut-xr/flex': 3.0.4 - '@coconut-xr/glyph': 0.0.6(three@0.160.0) - '@coconut-xr/xmaterials': 0.0.3(three@0.160.0) - '@react-three/fiber': 8.15.13(react-dom@18.2.0)(react@18.2.0)(three@0.160.0) - react: 18.2.0 - suspend-react: 0.1.3(react@18.2.0) - three: 0.160.0 - yoga-wasm-web: 0.3.3 - dev: false - - /@coconut-xr/natuerlich@0.0.49(@coconut-xr/koestlich@0.3.12)(@react-three/fiber@8.15.13)(@types/react@18.2.47)(react@18.2.0)(three@0.160.0): - resolution: {integrity: sha512-DBoIV9qt0KQWii+Ko01MtrlVoXHGonBWQa87baVmJ6kjnGDIwSL2UkSstXl5vXkzZW0T+dY9jnPOlHZxNFpNcQ==} - peerDependencies: - '@coconut-xr/koestlich': '*' - '@react-three/fiber': '*' - react: '*' - three: '*' - peerDependenciesMeta: - '@coconut-xr/koestlich': - optional: true - dependencies: - '@coconut-xr/koestlich': 0.3.12(@react-three/fiber@8.15.13)(react@18.2.0)(three@0.160.0) - '@coconut-xr/xinteraction': 0.1.12(@react-three/fiber@8.15.13)(react@18.2.0)(three@0.160.0) - '@coconut-xr/xmaterials': 0.0.3(three@0.160.0) - '@react-three/fiber': 8.15.13(react-dom@18.2.0)(react@18.2.0)(three@0.160.0) - '@webxr-input-profiles/motion-controllers': 1.0.0 - meshline: 3.1.6(three@0.160.0) - react: 18.2.0 - suspend-react: 0.0.9(react@18.2.0) - three: 0.160.0 - zustand: 4.4.7(@types/react@18.2.47)(react@18.2.0) - transitivePeerDependencies: - - '@types/react' - - immer - dev: false - /@coconut-xr/xinteraction@0.1.12(@react-three/fiber@8.15.13)(react@18.2.0)(three@0.160.0): resolution: {integrity: sha512-nuKWTDz9qx1HRw7MASvqhxO7i676FVWdh+vITFhQYpQem7QLyJqSaU/gw3l7Qh7QYZENmvvte4X83D1oih/htA==} peerDependencies: @@ -749,14 +760,6 @@ packages: three: 0.160.0 dev: false - /@coconut-xr/xmaterials@0.0.3(three@0.160.0): - resolution: {integrity: sha512-lqchBqGz5cbKzIAZhVHR/BvHH9ReS6rtZo+FMU/Ra4B/htJGA1UgzjTor6ybUuRLHgB9o1MWcW9GJ2KX/qukNg==} - peerDependencies: - three: '>=0.140.0' - dependencies: - three: 0.160.0 - dev: false - /@cspotcode/source-map-support@0.8.1: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} @@ -2715,10 +2718,6 @@ packages: - supports-color dev: true - /@webxr-input-profiles/motion-controllers@1.0.0: - resolution: {integrity: sha512-Ppxde+G1/QZbU8ShCQg+eq5VtlcL/FPkerF1dkDOLlIml0LJD1tFqnCZYR0SrHzYleIQ2siRnOx7xbFLaCpExQ==} - dev: false - /acorn-jsx@5.3.2(acorn@8.11.3): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -5100,14 +5099,6 @@ packages: engines: {node: '>= 8'} dev: true - /meshline@3.1.6(three@0.160.0): - resolution: {integrity: sha512-8JZJOdaL5oz3PI/upG8JvP/5FfzYUOhrkJ8np/WKvXzl0/PZ2V9pqTvCIjSKv+w9ccg2xb+yyBhXAwt6ier3ug==} - peerDependencies: - three: '>=0.137' - dependencies: - three: 0.160.0 - dev: false - /meshline@3.1.7(three@0.160.0): resolution: {integrity: sha512-uf9fPI9wy0Ie0kZjvKuIkf2n7gi3ih0wdTeb/kmSvmzpPyEL5d9lFohg9+JV9VC4sQUBOZDgxu6fnjn57goSHg==} peerDependencies: @@ -6461,14 +6452,6 @@ packages: engines: {node: '>= 0.4'} dev: true - /suspend-react@0.0.9(react@18.2.0): - resolution: {integrity: sha512-668Pxy4z54fhjpnPqw6olj3vvpV03VywFNXazyNSYSTuHaRw3NJJjO5l8R49AUk5fMTkJXGVGaaVSgdaO01S3w==} - peerDependencies: - react: '>=17.0' - dependencies: - react: 18.2.0 - dev: false - /suspend-react@0.1.3(react@18.2.0): resolution: {integrity: sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==} peerDependencies: