diff --git a/README.md b/README.md index 1808e530..f2ca214a 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,6 @@ Build performant 3D user interfaces for Three.js using @react-three/fiber and yo TODO Release -- fix bug: 3 draw calls for TabsDemo - fix bug: interaction panel disappear unmount and mount - add shadcn components - cli for kits diff --git a/examples/dashboard/date-range-picker.tsx b/examples/dashboard/date-range-picker.tsx index 23ecc8d2..5e70c99e 100644 --- a/examples/dashboard/date-range-picker.tsx +++ b/examples/dashboard/date-range-picker.tsx @@ -1,6 +1,6 @@ -import { Button } from "@/button.js"; -import { Calendar } from "@react-three/uikit-lucide"; -import { Text } from "@react-three/uikit"; +import { Button } from '@/button.js' +import { Calendar } from '@react-three/uikit-lucide' +import { Text } from '@react-three/uikit' export function CalendarDateRangePicker() { return ( @@ -8,5 +8,5 @@ export function CalendarDateRangePicker() { Jan 20, 2023 - Feb 09, 2023 - ); + ) } diff --git a/examples/dashboard/index.tsx b/examples/dashboard/index.tsx index 8f982c09..ab0a3d69 100644 --- a/examples/dashboard/index.tsx +++ b/examples/dashboard/index.tsx @@ -1,7 +1,7 @@ -import { StrictMode } from 'react' -import { Canvas } from '@react-three/fiber' +import { StrictMode, Suspense, useEffect, useRef, useState } from 'react' +import { Canvas, useFrame } from '@react-three/fiber' import { createRoot } from 'react-dom/client' -import { Container, Fullscreen, Root, Text } from '@react-three/uikit' +import { Container, CustomContainer, Root, Text } from '@react-three/uikit' import { DefaultColors, colors } from '@/defaults.js' import { Button } from '@/button.js' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/card.js' @@ -13,8 +13,11 @@ import { RecentSales } from './recent-sales.js' import { TeamSwitcher } from './team-switcher.js' import { UserNav } from './user-nav.js' import { Activity, CreditCard, DollarSign, Users } from '@react-three/uikit-lucide' -import { Environment, OrbitControls } from '@react-three/drei' -import { Perf } from 'r3f-perf' +import { ContactShadows, Environment, MeshTransmissionMaterial, useGLTF, useTexture } from '@react-three/drei' +import { Group, MathUtils, RepeatWrapping } from 'three' +import { DialogAnchor } from '@/dialog.js' +//@ts-ignore +import { EffectComposer, DepthOfField, Vignette } from '@react-three/postprocessing' createRoot(document.getElementById('root')!).render( @@ -23,62 +26,106 @@ createRoot(document.getElementById('root')!).render( ) function App() { + const [open, setOpen] = useState(false) return ( - - - - - - - - - - - - - - - - - - - + + + + + + + + + + - - - - - - - + + + + ) } -export function DashboardPage() { +function Model({ hinge, open, setOpen, ...props }: any) { + const group = useRef(null) + const normalMap = useTexture('/scratches.jpg') + useEffect(() => { + normalMap.repeat.setScalar(2) + normalMap.wrapS = RepeatWrapping + normalMap.wrapT = RepeatWrapping + normalMap.needsUpdate = true + }, [normalMap]) + // Load model + const { nodes, materials } = useGLTF('/mac-draco.glb') + // Take care of cursor state on hover + // Make it float in the air when it's opened + useFrame((state) => { + const t = state.clock.getElapsedTime() + if (group.current == null) { + return + } + group.current.rotation.x = MathUtils.lerp(group.current.rotation.x, Math.cos(t / 10) / 10 + 0.25, 0.1) + group.current.rotation.y = MathUtils.lerp(group.current.rotation.y, Math.sin(t / 10) / 4, 0.1) + group.current.rotation.z = MathUtils.lerp(group.current.rotation.z, Math.sin(t / 10) / 10, 0.1) + group.current.position.y = MathUtils.lerp(group.current.position.y, (-2 + Math.sin(t)) / 3, 0.1) + }) + // The view was auto-generated by: https://github.com/pmndrs/gltfjsx + // Events and spring animations were added afterwards + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) +} + +export function DashboardPage({ open, setOpen }: { open: boolean; setOpen: (open: boolean) => void }) { return ( @@ -86,7 +133,7 @@ export function DashboardPage() { - + @@ -120,7 +167,7 @@ export function DashboardPage() { - + @@ -138,7 +185,7 @@ export function DashboardPage() { - + - + - + - + Overview @@ -225,7 +272,7 @@ export function DashboardPage() { - + Recent Sales diff --git a/examples/dashboard/main-nav.tsx b/examples/dashboard/main-nav.tsx index 2a448253..b4222260 100644 --- a/examples/dashboard/main-nav.tsx +++ b/examples/dashboard/main-nav.tsx @@ -1,8 +1,8 @@ -import { colors } from "@/defaults.js"; -import { Container, Text } from "@react-three/uikit"; -import { ComponentPropsWithoutRef } from "react"; +import { colors } from '@/defaults.js' +import { Container, Text } from '@react-three/uikit' +import { ComponentPropsWithoutRef } from 'react' -export function MainNav(props: Omit, "children">) { +export function MainNav(props: Omit, 'children'>) { return ( @@ -18,5 +18,5 @@ export function MainNav(props: Omit, Settings - ); + ) } diff --git a/examples/dashboard/overview.tsx b/examples/dashboard/overview.tsx index 165a9f4e..40113a2c 100644 --- a/examples/dashboard/overview.tsx +++ b/examples/dashboard/overview.tsx @@ -1,65 +1,70 @@ -import { colors } from "@/defaults.js"; -import { Container, Text } from "@react-three/uikit"; +import { colors } from '@/defaults.js' +import { Container, Text } from '@react-three/uikit' const data = [ { - name: "Jan", + name: 'Jan', total: Math.floor(Math.random() * 5000) + 1000, }, { - name: "Feb", + name: 'Feb', total: Math.floor(Math.random() * 5000) + 1000, }, { - name: "Mar", + name: 'Mar', total: Math.floor(Math.random() * 5000) + 1000, }, { - name: "Apr", + name: 'Apr', total: Math.floor(Math.random() * 5000) + 1000, }, { - name: "May", + name: 'May', total: Math.floor(Math.random() * 5000) + 1000, }, { - name: "Jun", + name: 'Jun', total: Math.floor(Math.random() * 5000) + 1000, }, { - name: "Jul", + name: 'Jul', total: Math.floor(Math.random() * 5000) + 1000, }, { - name: "Aug", + name: 'Aug', total: Math.floor(Math.random() * 5000) + 1000, }, { - name: "Sep", + name: 'Sep', total: Math.floor(Math.random() * 5000) + 1000, }, { - name: "Oct", + name: 'Oct', total: Math.floor(Math.random() * 5000) + 1000, }, { - name: "Nov", + name: 'Nov', total: Math.floor(Math.random() * 5000) + 1000, }, { - name: "Dec", + name: 'Dec', total: Math.floor(Math.random() * 5000) + 1000, }, -]; +] -const max = 6000; +const max = 6000 -const yAxisLabels = ["$6000", "$4500", "$3000", "$1500", "$0"]; +const yAxisLabels = ['$6000', '$4500', '$3000', '$1500', '$0'] export function Overview() { return ( - + {yAxisLabels.map((label) => ( {label} @@ -72,7 +77,7 @@ export function Overview() { @@ -84,5 +89,5 @@ export function Overview() { ))} - ); + ) } diff --git a/examples/dashboard/package.json b/examples/dashboard/package.json index a5af1fb9..56d13b5f 100644 --- a/examples/dashboard/package.json +++ b/examples/dashboard/package.json @@ -4,11 +4,14 @@ "@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/uikit": "workspace:^", "@react-three/uikit-lucide": "workspace:^", + "@types/three": "^0.160.0", "r3f-perf": "^7.1.2", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "three": "^0.160.0" }, "scripts": { "dev": "vite --host" diff --git a/examples/dashboard/public/mac-draco.glb b/examples/dashboard/public/mac-draco.glb new file mode 100644 index 00000000..2c25bdc0 Binary files /dev/null and b/examples/dashboard/public/mac-draco.glb differ diff --git a/examples/dashboard/public/scratches.jpg b/examples/dashboard/public/scratches.jpg new file mode 100644 index 00000000..804ac643 Binary files /dev/null and b/examples/dashboard/public/scratches.jpg differ diff --git a/examples/dashboard/user-nav.tsx b/examples/dashboard/user-nav.tsx index 82de6eb0..0eb8410f 100644 --- a/examples/dashboard/user-nav.tsx +++ b/examples/dashboard/user-nav.tsx @@ -1,5 +1,92 @@ -import { Avatar } from "@/avatar.js"; +import { Avatar } from '@/avatar.js' +import { Button } from '@/button.js' +import { Text, Container, CustomContainer } from '@react-three/uikit' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@/dialog.js' +import { BellRing } from '@react-three/uikit-lucide' +import { colors } from '@/defaults.js' +import { Switch } from '@/switch.js' -export function UserNav() { - return ; +const notifications = [ + { + title: 'Your call has been confirmed.', + description: '1 hour ago', + }, + { + title: 'You have a new message!', + description: '1 hour ago', + }, + { + title: 'Your subscription is expiring soon!', + description: '2 hours ago', + }, +] + +export function UserNav({ open, setOpen }: { open: boolean; setOpen: (open: boolean) => void }) { + return ( + + + + + + + + + + + Edit profile + + + Make changes to your profile here. Click save when you're done. + + + + + + + Push Notifications + + + Send notifications to device. + + + + + + {notifications.map((notification, index) => ( + + + + + {notification.title} + + + {notification.description} + + + + ))} + + + + + + + ) } diff --git a/examples/default/index.tsx b/examples/default/index.tsx index 02d70e16..f4b36601 100644 --- a/examples/default/index.tsx +++ b/examples/default/index.tsx @@ -31,6 +31,16 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/tabs.js' import { Toggle } from '@/toggle.js' import { ToggleGroup, ToggleGroupItem } from '@/toggle-group.js' import { Perf } from 'r3f-perf' +import { + Dialog, + DialogAnchor, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@/dialog.js' createRoot(document.getElementById('root')!).render( @@ -52,15 +62,59 @@ function App() { justifyContent="center" padding={32} > - - - - - + + + + + + + ) } + +export function DialogDemo() { + return ( + + + + + + + + Edit profile + + + Make changes to your profile here. Click save when you're done. + + + + + + {/**/} + + + + {/**/} + + + + + + + + ) +} + export function ToggleGroupDemo() { return ( diff --git a/packages/kits/default/accordion.tsx b/packages/kits/default/accordion.tsx index 31bd04f6..dcdfbb9b 100644 --- a/packages/kits/default/accordion.tsx +++ b/packages/kits/default/accordion.tsx @@ -43,7 +43,7 @@ export function AccordionTrigger({ children, ...props }: ComponentPropsWithoutRe paddingY={16} {...props} > - {children} + {children} ) diff --git a/packages/kits/default/alert-dialog.tsx b/packages/kits/default/alert-dialog.tsx new file mode 100644 index 00000000..1f18f8b3 --- /dev/null +++ b/packages/kits/default/alert-dialog.tsx @@ -0,0 +1,66 @@ +const AlertDialog = AlertDialogPrimitive.Root + +const AlertDialogTrigger = AlertDialogPrimitive.Trigger + +const AlertDialogPortal = AlertDialogPrimitive.Portal + +export function AlertDialogOverlay() { + return ( + + ) +} + +export function AlertDialogContent() { + return ( + + + + + ) +} + +export function AlertDialogHeader() { + return
+} + +export function AlertDialogFooter() { + return
+} + +export function AlertDialogTitle() { + return +} + +export function AlertDialogDescription() { + return ( + + ) +} + +export function AlertDialogAction() { + return +} + +export function AlertDialogCancel() { + return ( + + ) +} diff --git a/packages/kits/default/alert.tsx b/packages/kits/default/alert.tsx index 2cf170e8..46d56a28 100644 --- a/packages/kits/default/alert.tsx +++ b/packages/kits/default/alert.tsx @@ -26,7 +26,7 @@ export function AlertIcon(props: ComponentPropsWithoutRef) { export function AlertTitle({ children, ...props }: ComponentPropsWithoutRef) { return ( - + {children} diff --git a/packages/kits/default/badge.tsx b/packages/kits/default/badge.tsx index dd1c7652..c0101d95 100644 --- a/packages/kits/default/badge.tsx +++ b/packages/kits/default/badge.tsx @@ -55,7 +55,7 @@ export function Badge({ const { containerProps, defaultProps } = badgeVariants[variant] return ( - + {children} diff --git a/packages/kits/default/button.tsx b/packages/kits/default/button.tsx index 4bc4cc4d..928bff19 100644 --- a/packages/kits/default/button.tsx +++ b/packages/kits/default/button.tsx @@ -107,6 +107,7 @@ export function Button({ + +function Calendar({}: { mode?: 'single' }) { + return ( + , + IconRight: ({ ...props }) => , + }} + {...props} + /> + ) +} diff --git a/packages/kits/default/carousel.tsx b/packages/kits/default/carousel.tsx new file mode 100644 index 00000000..b6f5918b --- /dev/null +++ b/packages/kits/default/carousel.tsx @@ -0,0 +1,226 @@ +'use client' + +import * as React from 'react' +import useEmblaCarousel, { type UseEmblaCarouselType } from 'embla-carousel-react' +import { ArrowLeft, ArrowRight } from 'lucide-react' + +import { cn } from '@/lib/utils' +import { Button } from '@/registry/default/ui/button' + +type CarouselApi = UseEmblaCarouselType[1] +type UseCarouselParameters = Parameters +type CarouselOptions = UseCarouselParameters[0] +type CarouselPlugin = UseCarouselParameters[1] + +type CarouselProps = { + opts?: CarouselOptions + plugins?: CarouselPlugin + orientation?: 'horizontal' | 'vertical' + setApi?: (api: CarouselApi) => void +} + +type CarouselContextProps = { + carouselRef: ReturnType[0] + api: ReturnType[1] + scrollPrev: () => void + scrollNext: () => void + canScrollPrev: boolean + canScrollNext: boolean +} & CarouselProps + +const CarouselContext = React.createContext(null) + +function useCarousel() { + const context = React.useContext(CarouselContext) + + if (!context) { + throw new Error('useCarousel must be used within a ') + } + + return context +} + +const Carousel = React.forwardRef & CarouselProps>( + ({ orientation = 'horizontal', opts, setApi, plugins, className, children, ...props }, ref) => { + const [carouselRef, api] = useEmblaCarousel( + { + ...opts, + axis: orientation === 'horizontal' ? 'x' : 'y', + }, + plugins, + ) + const [canScrollPrev, setCanScrollPrev] = React.useState(false) + const [canScrollNext, setCanScrollNext] = React.useState(false) + + const onSelect = React.useCallback((api: CarouselApi) => { + if (!api) { + return + } + + setCanScrollPrev(api.canScrollPrev()) + setCanScrollNext(api.canScrollNext()) + }, []) + + const scrollPrev = React.useCallback(() => { + api?.scrollPrev() + }, [api]) + + const scrollNext = React.useCallback(() => { + api?.scrollNext() + }, [api]) + + const handleKeyDown = React.useCallback( + (event: React.KeyboardEvent) => { + if (event.key === 'ArrowLeft') { + event.preventDefault() + scrollPrev() + } else if (event.key === 'ArrowRight') { + event.preventDefault() + scrollNext() + } + }, + [scrollPrev, scrollNext], + ) + + React.useEffect(() => { + if (!api || !setApi) { + return + } + + setApi(api) + }, [api, setApi]) + + React.useEffect(() => { + if (!api) { + return + } + + onSelect(api) + api.on('reInit', onSelect) + api.on('select', onSelect) + + return () => { + api?.off('select', onSelect) + } + }, [api, onSelect]) + + return ( + +
+ {children} +
+
+ ) + }, +) +Carousel.displayName = 'Carousel' + +const CarouselContent = React.forwardRef>( + ({ className, ...props }, ref) => { + const { carouselRef, orientation } = useCarousel() + + return ( +
+
+
+ ) + }, +) +CarouselContent.displayName = 'CarouselContent' + +const CarouselItem = React.forwardRef>( + ({ className, ...props }, ref) => { + const { orientation } = useCarousel() + + return ( +
+ ) + }, +) +CarouselItem.displayName = 'CarouselItem' + +const CarouselPrevious = React.forwardRef>( + ({ className, variant = 'outline', size = 'icon', ...props }, ref) => { + const { orientation, scrollPrev, canScrollPrev } = useCarousel() + + return ( + + ) + }, +) +CarouselPrevious.displayName = 'CarouselPrevious' + +const CarouselNext = React.forwardRef>( + ({ className, variant = 'outline', size = 'icon', ...props }, ref) => { + const { orientation, scrollNext, canScrollNext } = useCarousel() + + return ( + + ) + }, +) +CarouselNext.displayName = 'CarouselNext' + +export { type CarouselApi, Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext } diff --git a/packages/kits/default/collapsible.tsx b/packages/kits/default/collapsible.tsx new file mode 100644 index 00000000..1bbaed5a --- /dev/null +++ b/packages/kits/default/collapsible.tsx @@ -0,0 +1,11 @@ +'use client' + +import * as CollapsiblePrimitive from '@radix-ui/react-collapsible' + +const Collapsible = CollapsiblePrimitive.Root + +const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger + +const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent + +export { Collapsible, CollapsibleTrigger, CollapsibleContent } diff --git a/packages/kits/default/command.tsx b/packages/kits/default/command.tsx new file mode 100644 index 00000000..a97f5ae5 --- /dev/null +++ b/packages/kits/default/command.tsx @@ -0,0 +1,122 @@ +/*interface CommandDialogProps extends DialogProps {} + +const CommandDialog = ({ children, ...props }: CommandDialogProps) => { + return ( + + + + {children} + + + + ) +}*/ + + + +export function Command() { + export function +} + +const CommandInput = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( +
+ + +
+)) + +CommandInput.displayName = CommandPrimitive.Input.displayName + +const CommandList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) + +CommandList.displayName = CommandPrimitive.List.displayName + +const CommandEmpty = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>((props, ref) => ) + +CommandEmpty.displayName = CommandPrimitive.Empty.displayName + +const CommandGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) + +CommandGroup.displayName = CommandPrimitive.Group.displayName + +const CommandSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +CommandSeparator.displayName = CommandPrimitive.Separator.displayName + +const CommandItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) + +CommandItem.displayName = CommandPrimitive.Item.displayName + +const CommandShortcut = ({ className, ...props }: React.HTMLAttributes) => { + return +} +CommandShortcut.displayName = 'CommandShortcut' + +export { + Command, + CommandDialog, + CommandInput, + CommandList, + CommandEmpty, + CommandGroup, + CommandItem, + CommandShortcut, + CommandSeparator, +} diff --git a/packages/kits/default/context-menu.tsx b/packages/kits/default/context-menu.tsx new file mode 100644 index 00000000..dc39f0a5 --- /dev/null +++ b/packages/kits/default/context-menu.tsx @@ -0,0 +1,180 @@ +'use client' + +import * as React from 'react' +import * as ContextMenuPrimitive from '@radix-ui/react-context-menu' +import { Check, ChevronRight, Circle } from 'lucide-react' + +import { cn } from '@/lib/utils' + +const ContextMenu = ContextMenuPrimitive.Root + +const ContextMenuTrigger = ContextMenuPrimitive.Trigger + +const ContextMenuGroup = ContextMenuPrimitive.Group + +const ContextMenuPortal = ContextMenuPrimitive.Portal + +const ContextMenuSub = ContextMenuPrimitive.Sub + +const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup + +const ContextMenuSubTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, children, ...props }, ref) => ( + + {children} + + +)) +ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName + +const ContextMenuSubContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName + +const ContextMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName + +const ContextMenuItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + +)) +ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName + +const ContextMenuCheckboxItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, checked, ...props }, ref) => ( + + + + + + + {children} + +)) +ContextMenuCheckboxItem.displayName = ContextMenuPrimitive.CheckboxItem.displayName + +const ContextMenuRadioItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)) +ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName + +const ContextMenuLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + +)) +ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName + +const ContextMenuSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName + +const ContextMenuShortcut = ({ className, ...props }: React.HTMLAttributes) => { + return +} +ContextMenuShortcut.displayName = 'ContextMenuShortcut' + +export { + ContextMenu, + ContextMenuTrigger, + ContextMenuContent, + ContextMenuItem, + ContextMenuCheckboxItem, + ContextMenuRadioItem, + ContextMenuLabel, + ContextMenuSeparator, + ContextMenuShortcut, + ContextMenuGroup, + ContextMenuPortal, + ContextMenuSub, + ContextMenuSubContent, + ContextMenuSubTrigger, + ContextMenuRadioGroup, +} diff --git a/packages/kits/default/defaults.tsx b/packages/kits/default/defaults.tsx index c65f95d0..47b94d82 100644 --- a/packages/kits/default/defaults.tsx +++ b/packages/kits/default/defaults.tsx @@ -9,7 +9,7 @@ export const colors = { cardForeground: new Color().setHSL(222.2 / 360, 0.84, 0.049, 'srgb'), popover: new Color().setHSL(0, 0, 1, 'srgb'), popoverForeground: new Color().setHSL(222.2, 0.84, 0.049, 'srgb'), - primary: new Color().setHSL(222.2 / 360, 0.474, 0.112, 'srgb'), + primary: new Color().setHSL(100.2 / 360, 0.774, 0.512, 'srgb'), primaryForeground: new Color().setHSL(210 / 360, 0.4, 0.98, 'srgb'), secondary: new Color().setHSL(210 / 360, 0.4, 0.961, 'srgb'), secondaryForeground: new Color().setHSL(222.2 / 360, 0.474, 0.112, 'srgb'), @@ -25,5 +25,14 @@ export const colors = { } export function DefaultColors(props: ComponentPropsWithoutRef) { - return + return ( + + ) } diff --git a/packages/kits/default/dialog.tsx b/packages/kits/default/dialog.tsx new file mode 100644 index 00000000..827ea021 --- /dev/null +++ b/packages/kits/default/dialog.tsx @@ -0,0 +1,193 @@ +import { Container, DefaultProperties } from '@react-three/uikit' +import { colors } from './defaults.js' +import { + ComponentPropsWithoutRef, + ReactNode, + createContext, + memo, + useCallback, + useContext, + useEffect, + useMemo, + useRef, + useState, +} from 'react' +import { X } from '@react-three/uikit-lucide' + +type DialogAnchorSetElement = (prevElement: JSX.Element | undefined, element: JSX.Element | undefined) => void + +const DialogAnchorContext = createContext(null as any) + +export function DialogAnchor({ children }: { children?: ReactNode }) { + const [element, setElement] = useState(undefined) + const set = useCallback( + (prevElement, element) => setElement((e) => (e === prevElement ? element : e)), + [], + ) + return ( + <> + + {element} + + ) +} + +const DialogAnchorProvider = memo(({ children, set }: { children?: ReactNode; set: DialogAnchorSetElement }) => { + return {children} +}) + +const DialogContext = createContext<{ + setOpen: (open: boolean) => void + setContent: (element: JSX.Element) => void +}>(null as any) + +export function Dialog({ + children, + open, + onOpenChange, +}: { + children?: ReactNode + open: boolean + onOpenChange: (open: boolean) => void +}) { + const setElement = useContext(DialogAnchorContext) + const contentRef = useRef(undefined) + const displayedRef = useRef(undefined) + useEffect(() => { + if (!open) { + setElement(displayedRef.current, undefined) + displayedRef.current = undefined + return + } + if (contentRef.current == null) { + return + } + setElement(undefined, contentRef.current) + displayedRef.current = contentRef.current + }, [open, setElement]) + const ref = useRef(onOpenChange) + ref.current = onOpenChange + const value = useMemo( + () => ({ + setContent(content: JSX.Element) { + if (displayedRef.current != null) { + setElement(displayedRef.current, content) + displayedRef.current = content + } + contentRef.current = content + }, + setOpen(open: boolean) { + ref.current?.(open) + }, + }), + [setElement], + ) + return {children} +} + +export function DialogTrigger({ children }: { children?: ReactNode }) { + const { setOpen } = useContext(DialogContext) + return setOpen(true)}>{children} +} + +export function DialogOverlay(props: ComponentPropsWithoutRef) { + return ( + e.stopPropagation()} + onWheel={(e) => e.stopPropagation()} + positionType="absolute" + inset={0} + zIndexOffset={50} + backgroundColor="black" + backgroundOpacity={0.8} + {...props} + /> + ) +} + +export function useCloseDialog() { + const { setOpen } = useContext(DialogContext) + return useCallback(() => setOpen(false), [setOpen]) +} + +export function DialogContent({ children, sm, ...props }: ComponentPropsWithoutRef) { + const { setOpen, setContent } = useContext(DialogContext) + useEffect(() => + setContent( + { + setOpen(false) + e.stopPropagation() + }} + alignItems="center" + justifyContent="center" + > + e.stopPropagation()} + zIndexOffset={{ major: 50, minor: 1 }} + positionType="relative" + width="100%" + gap={16} + border={1} + backgroundColor={colors.background} + padding={24} + sm={{ borderRadius: 8, ...sm }} + {...props} + > + {children} + setOpen(false)} + cursor="pointer" + positionType="absolute" + positionRight={16} + positionTop={16} + borderRadius={2} + opacity={0.7} + backgroundOpacity={0.7} + hover={{ opacity: 0.7, backgroundOpacity: 0.7 }} + width={16} + height={16} + /> + + , + ), + ) + return null +} + +export function DialogHeader({ children, ...props }: ComponentPropsWithoutRef) { + return ( + + + {children} + + + ) +} + +export function DialogFooter(props: ComponentPropsWithoutRef) { + return ( + + ) +} + +export function DialogTitle({ children }: { children?: ReactNode }) { + return ( + + {children} + + ) +} + +export function DialogDescription({ children }: { children?: ReactNode }) { + return ( + + {children} + + ) +} diff --git a/packages/kits/default/drawer.tsx b/packages/kits/default/drawer.tsx new file mode 100644 index 00000000..1d1ff5be --- /dev/null +++ b/packages/kits/default/drawer.tsx @@ -0,0 +1,89 @@ +'use client' + +import * as React from 'react' +import { Drawer as DrawerPrimitive } from 'vaul' + +import { cn } from '@/lib/utils' + +const Drawer = ({ shouldScaleBackground = true, ...props }: React.ComponentProps) => ( + +) +Drawer.displayName = 'Drawer' + +const DrawerTrigger = DrawerPrimitive.Trigger + +const DrawerPortal = DrawerPrimitive.Portal + +const DrawerClose = DrawerPrimitive.Close + +const DrawerOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName + +const DrawerContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + +
+ {children} + + +)) +DrawerContent.displayName = 'DrawerContent' + +const DrawerHeader = ({ className, ...props }: React.HTMLAttributes) => ( +
+) +DrawerHeader.displayName = 'DrawerHeader' + +const DrawerFooter = ({ className, ...props }: React.HTMLAttributes) => ( +
+) +DrawerFooter.displayName = 'DrawerFooter' + +const DrawerTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DrawerTitle.displayName = DrawerPrimitive.Title.displayName + +const DrawerDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DrawerDescription.displayName = DrawerPrimitive.Description.displayName + +export { + Drawer, + DrawerPortal, + DrawerOverlay, + DrawerTrigger, + DrawerClose, + DrawerContent, + DrawerHeader, + DrawerFooter, + DrawerTitle, + DrawerDescription, +} diff --git a/packages/kits/default/dropdown-menu.tsx b/packages/kits/default/dropdown-menu.tsx new file mode 100644 index 00000000..4f266f12 --- /dev/null +++ b/packages/kits/default/dropdown-menu.tsx @@ -0,0 +1,181 @@ +'use client' + +import * as React from 'react' +import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu' +import { Check, ChevronRight, Circle } from 'lucide-react' + +import { cn } from '@/lib/utils' + +const DropdownMenu = DropdownMenuPrimitive.Root + +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger + +const DropdownMenuGroup = DropdownMenuPrimitive.Group + +const DropdownMenuPortal = DropdownMenuPrimitive.Portal + +const DropdownMenuSub = DropdownMenuPrimitive.Sub + +const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup + +const DropdownMenuSubTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, children, ...props }, ref) => ( + + {children} + + +)) +DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName + +const DropdownMenuSubContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName + +const DropdownMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)) +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName + +const DropdownMenuItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + +)) +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName + +const DropdownMenuCheckboxItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, checked, ...props }, ref) => ( + + + + + + + {children} + +)) +DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName + +const DropdownMenuRadioItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)) +DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName + +const DropdownMenuLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + +)) +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName + +const DropdownMenuSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName + +const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes) => { + return +} +DropdownMenuShortcut.displayName = 'DropdownMenuShortcut' + +export { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuGroup, + DropdownMenuPortal, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuRadioGroup, +} diff --git a/packages/kits/default/hover-card.tsx b/packages/kits/default/hover-card.tsx new file mode 100644 index 00000000..7b9302ac --- /dev/null +++ b/packages/kits/default/hover-card.tsx @@ -0,0 +1,29 @@ +'use client' + +import * as React from 'react' +import * as HoverCardPrimitive from '@radix-ui/react-hover-card' + +import { cn } from '@/lib/utils' + +const HoverCard = HoverCardPrimitive.Root + +const HoverCardTrigger = HoverCardPrimitive.Trigger + +const HoverCardContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => ( + +)) +HoverCardContent.displayName = HoverCardPrimitive.Content.displayName + +export { HoverCard, HoverCardTrigger, HoverCardContent } diff --git a/packages/kits/default/label.tsx b/packages/kits/default/label.tsx index 8d21a7ac..a5c436fb 100644 --- a/packages/kits/default/label.tsx +++ b/packages/kits/default/label.tsx @@ -3,7 +3,7 @@ import { ReactNode } from 'react' export function Label({ disabled, children }: { disabled?: boolean; children?: ReactNode }) { return ( - + {children} ) diff --git a/packages/kits/default/menu-bar.tsx b/packages/kits/default/menu-bar.tsx new file mode 100644 index 00000000..30b2c70c --- /dev/null +++ b/packages/kits/default/menu-bar.tsx @@ -0,0 +1,209 @@ +'use client' + +import * as React from 'react' +import * as MenubarPrimitive from '@radix-ui/react-menubar' +import { Check, ChevronRight, Circle } from 'lucide-react' + +import { cn } from '@/lib/utils' + +const MenubarMenu = MenubarPrimitive.Menu + +const MenubarGroup = MenubarPrimitive.Group + +const MenubarPortal = MenubarPrimitive.Portal + +const MenubarSub = MenubarPrimitive.Sub + +const MenubarRadioGroup = MenubarPrimitive.RadioGroup + +const Menubar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +Menubar.displayName = MenubarPrimitive.Root.displayName + +const MenubarTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName + +const MenubarSubTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, children, ...props }, ref) => ( + + {children} + + +)) +MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName + +const MenubarSubContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName + +const MenubarContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = 'start', alignOffset = -4, sideOffset = 8, ...props }, ref) => ( + + + +)) +MenubarContent.displayName = MenubarPrimitive.Content.displayName + +const MenubarItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + +)) +MenubarItem.displayName = MenubarPrimitive.Item.displayName + +const MenubarCheckboxItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, checked, ...props }, ref) => ( + + + + + + + {children} + +)) +MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName + +const MenubarRadioItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)) +MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName + +const MenubarLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + +)) +MenubarLabel.displayName = MenubarPrimitive.Label.displayName + +const MenubarSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName + +const MenubarShortcut = ({ className, ...props }: React.HTMLAttributes) => { + return +} +MenubarShortcut.displayname = 'MenubarShortcut' + +export { + Menubar, + MenubarMenu, + MenubarTrigger, + MenubarContent, + MenubarItem, + MenubarSeparator, + MenubarLabel, + MenubarCheckboxItem, + MenubarRadioGroup, + MenubarRadioItem, + MenubarPortal, + MenubarSubContent, + MenubarSubTrigger, + MenubarGroup, + MenubarSub, + MenubarShortcut, +} diff --git a/packages/kits/default/navigation-menu.tsx b/packages/kits/default/navigation-menu.tsx new file mode 100644 index 00000000..e2ca5b19 --- /dev/null +++ b/packages/kits/default/navigation-menu.tsx @@ -0,0 +1,120 @@ +import * as React from 'react' +import * as NavigationMenuPrimitive from '@radix-ui/react-navigation-menu' +import { cva } from 'class-variance-authority' +import { ChevronDown } from 'lucide-react' + +import { cn } from '@/lib/utils' + +const NavigationMenu = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + {children} + + +)) +NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName + +const NavigationMenuList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName + +const NavigationMenuItem = NavigationMenuPrimitive.Item + +const navigationMenuTriggerStyle = cva( + 'group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50', +) + +const NavigationMenuTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + {children}{' '} + +)) +NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName + +const NavigationMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName + +const NavigationMenuLink = NavigationMenuPrimitive.Link + +const NavigationMenuViewport = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( +
+ +
+)) +NavigationMenuViewport.displayName = NavigationMenuPrimitive.Viewport.displayName + +const NavigationMenuIndicator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +
+ +)) +NavigationMenuIndicator.displayName = NavigationMenuPrimitive.Indicator.displayName + +export { + navigationMenuTriggerStyle, + NavigationMenu, + NavigationMenuList, + NavigationMenuItem, + NavigationMenuContent, + NavigationMenuTrigger, + NavigationMenuLink, + NavigationMenuIndicator, + NavigationMenuViewport, +} diff --git a/packages/kits/default/package.json b/packages/kits/default/package.json index 0943c17c..6d8244b4 100644 --- a/packages/kits/default/package.json +++ b/packages/kits/default/package.json @@ -6,13 +6,16 @@ "fix:eslint": "eslint ./**/*.tsx --fix" }, "devDependencies": { - "@types/react": "^18.2.47", - "@types/three": "^0.160.0", - "three": "^0.160.0", "@preact/signals-core": "^1.5.1", + "@react-three/fiber": "^8.15.13", "@react-three/uikit": "workspace:^", "@react-three/uikit-lucide": "workspace:^", - "@react-three/fiber": "^8.15.13" + "@types/react": "^18.2.47", + "@types/three": "^0.160.0", + "three": "^0.160.0" }, - "type": "module" + "type": "module", + "dependencies": { + "tunnel-rat": "^0.1.2" + } } diff --git a/packages/kits/default/popover.tsx b/packages/kits/default/popover.tsx new file mode 100644 index 00000000..ce5e43fe --- /dev/null +++ b/packages/kits/default/popover.tsx @@ -0,0 +1,31 @@ +'use client' + +import * as React from 'react' +import * as PopoverPrimitive from '@radix-ui/react-popover' + +import { cn } from '@/lib/utils' + +const Popover = PopoverPrimitive.Root + +const PopoverTrigger = PopoverPrimitive.Trigger + +const PopoverContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => ( + + + +)) +PopoverContent.displayName = PopoverPrimitive.Content.displayName + +export { Popover, PopoverTrigger, PopoverContent } diff --git a/packages/kits/default/select.tsx b/packages/kits/default/select.tsx new file mode 100644 index 00000000..e22be052 --- /dev/null +++ b/packages/kits/default/select.tsx @@ -0,0 +1,145 @@ +'use client' + +import * as React from 'react' +import * as SelectPrimitive from '@radix-ui/react-select' +import { Check, ChevronDown, ChevronUp } from 'lucide-react' + +import { cn } from '@/lib/utils' + +const Select = SelectPrimitive.Root + +const SelectGroup = SelectPrimitive.Group + +const SelectValue = SelectPrimitive.Value + +const SelectTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + span]:line-clamp-1', + className, + )} + {...props} + > + {children} + + + + +)) +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName + +const SelectScrollUpButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName + +const SelectScrollDownButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName + +const SelectContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, position = 'popper', ...props }, ref) => ( + + + + + {children} + + + + +)) +SelectContent.displayName = SelectPrimitive.Content.displayName + +const SelectLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SelectLabel.displayName = SelectPrimitive.Label.displayName + +const SelectItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + + {children} + +)) +SelectItem.displayName = SelectPrimitive.Item.displayName + +const SelectSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SelectSeparator.displayName = SelectPrimitive.Separator.displayName + +export { + Select, + SelectGroup, + SelectValue, + SelectTrigger, + SelectContent, + SelectLabel, + SelectItem, + SelectSeparator, + SelectScrollUpButton, + SelectScrollDownButton, +} diff --git a/packages/kits/default/sheet.tsx b/packages/kits/default/sheet.tsx new file mode 100644 index 00000000..72266ee9 --- /dev/null +++ b/packages/kits/default/sheet.tsx @@ -0,0 +1,109 @@ +'use client' + +import * as React from 'react' +import * as SheetPrimitive from '@radix-ui/react-dialog' +import { cva, type VariantProps } from 'class-variance-authority' +import { X } from 'lucide-react' + +import { cn } from '@/lib/utils' + +const Sheet = SheetPrimitive.Root + +const SheetTrigger = SheetPrimitive.Trigger + +const SheetClose = SheetPrimitive.Close + +const SheetPortal = SheetPrimitive.Portal + +const SheetOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetOverlay.displayName = SheetPrimitive.Overlay.displayName + +const sheetVariants = cva( + 'fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500', + { + variants: { + side: { + top: 'inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top', + bottom: + 'inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom', + left: 'inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm', + right: + 'inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm', + }, + }, + defaultVariants: { + side: 'right', + }, + }, +) + +interface SheetContentProps + extends React.ComponentPropsWithoutRef, + VariantProps {} + +const SheetContent = React.forwardRef, SheetContentProps>( + ({ side = 'right', className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + + ), +) +SheetContent.displayName = SheetPrimitive.Content.displayName + +const SheetHeader = ({ className, ...props }: React.HTMLAttributes) => ( +
+) +SheetHeader.displayName = 'SheetHeader' + +const SheetFooter = ({ className, ...props }: React.HTMLAttributes) => ( +
+) +SheetFooter.displayName = 'SheetFooter' + +const SheetTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetTitle.displayName = SheetPrimitive.Title.displayName + +const SheetDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetDescription.displayName = SheetPrimitive.Description.displayName + +export { + Sheet, + SheetPortal, + SheetOverlay, + SheetTrigger, + SheetClose, + SheetContent, + SheetHeader, + SheetFooter, + SheetTitle, + SheetDescription, +} diff --git a/packages/kits/default/tabs.tsx b/packages/kits/default/tabs.tsx index 4f528082..f10d67ae 100644 --- a/packages/kits/default/tabs.tsx +++ b/packages/kits/default/tabs.tsx @@ -84,6 +84,7 @@ export function TabsTrigger({ opacity={disabled ? 0.5 : undefined} color={active ? colors.foreground : undefined} fontSize={14} + fontWeight="medium" lineHeight={1.43} wordBreak="keep-all" > diff --git a/packages/kits/default/toast.tsx b/packages/kits/default/toast.tsx new file mode 100644 index 00000000..2ef30e86 --- /dev/null +++ b/packages/kits/default/toast.tsx @@ -0,0 +1,111 @@ +import * as React from 'react' +import * as ToastPrimitives from '@radix-ui/react-toast' +import { cva, type VariantProps } from 'class-variance-authority' +import { X } from 'lucide-react' + +import { cn } from '@/lib/utils' + +const ToastProvider = ToastPrimitives.Provider + +const ToastViewport = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ToastViewport.displayName = ToastPrimitives.Viewport.displayName + +const toastVariants = cva( + 'group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full', + { + variants: { + variant: { + default: 'border bg-background text-foreground', + destructive: 'destructive group border-destructive bg-destructive text-destructive-foreground', + }, + }, + defaultVariants: { + variant: 'default', + }, + }, +) + +const Toast = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & VariantProps +>(({ className, variant, ...props }, ref) => { + return +}) +Toast.displayName = ToastPrimitives.Root.displayName + +const ToastAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ToastAction.displayName = ToastPrimitives.Action.displayName + +const ToastClose = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +ToastClose.displayName = ToastPrimitives.Close.displayName + +const ToastTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ToastTitle.displayName = ToastPrimitives.Title.displayName + +const ToastDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ToastDescription.displayName = ToastPrimitives.Description.displayName + +type ToastProps = React.ComponentPropsWithoutRef + +type ToastActionElement = React.ReactElement + +export { + type ToastProps, + type ToastActionElement, + ToastProvider, + ToastViewport, + Toast, + ToastTitle, + ToastDescription, + ToastClose, + ToastAction, +} diff --git a/packages/kits/default/toaster.tsx b/packages/kits/default/toaster.tsx new file mode 100644 index 00000000..1fecdd1a --- /dev/null +++ b/packages/kits/default/toaster.tsx @@ -0,0 +1,33 @@ +'use client' + +import { + Toast, + ToastClose, + ToastDescription, + ToastProvider, + ToastTitle, + ToastViewport, +} from '@/registry/default/ui/toast' +import { useToast } from '@/registry/default/ui/use-toast' + +export function Toaster() { + const { toasts } = useToast() + + return ( + + {toasts.map(function ({ id, title, description, action, ...props }) { + return ( + +
+ {title && {title}} + {description && {description}} +
+ {action} + +
+ ) + })} + +
+ ) +} diff --git a/packages/kits/default/toggle.tsx b/packages/kits/default/toggle.tsx index 2e1c49fa..edb65b1c 100644 --- a/packages/kits/default/toggle.tsx +++ b/packages/kits/default/toggle.tsx @@ -77,6 +77,7 @@ export function Toggle({ opacity={disabled ? 0.5 : undefined} fontSize={14} lineHeight={1.43} + fontWeight="medium" > {children} diff --git a/packages/kits/default/tooltip.tsx b/packages/kits/default/tooltip.tsx new file mode 100644 index 00000000..08fe308f --- /dev/null +++ b/packages/kits/default/tooltip.tsx @@ -0,0 +1,30 @@ +'use client' + +import * as React from 'react' +import * as TooltipPrimitive from '@radix-ui/react-tooltip' + +import { cn } from '@/lib/utils' + +const TooltipProvider = TooltipPrimitive.Provider + +const Tooltip = TooltipPrimitive.Root + +const TooltipTrigger = TooltipPrimitive.Trigger + +const TooltipContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + +)) +TooltipContent.displayName = TooltipPrimitive.Content.displayName + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } diff --git a/packages/kits/default/use-toast.tsx b/packages/kits/default/use-toast.tsx new file mode 100644 index 00000000..81989388 --- /dev/null +++ b/packages/kits/default/use-toast.tsx @@ -0,0 +1,187 @@ +// Inspired by react-hot-toast library +import * as React from 'react' + +import type { ToastActionElement, ToastProps } from '@/registry/default/ui/toast' + +const TOAST_LIMIT = 1 +const TOAST_REMOVE_DELAY = 1000000 + +type ToasterToast = ToastProps & { + id: string + title?: React.ReactNode + description?: React.ReactNode + action?: ToastActionElement +} + +const actionTypes = { + ADD_TOAST: 'ADD_TOAST', + UPDATE_TOAST: 'UPDATE_TOAST', + DISMISS_TOAST: 'DISMISS_TOAST', + REMOVE_TOAST: 'REMOVE_TOAST', +} as const + +let count = 0 + +function genId() { + count = (count + 1) % Number.MAX_SAFE_INTEGER + return count.toString() +} + +type ActionType = typeof actionTypes + +type Action = + | { + type: ActionType['ADD_TOAST'] + toast: ToasterToast + } + | { + type: ActionType['UPDATE_TOAST'] + toast: Partial + } + | { + type: ActionType['DISMISS_TOAST'] + toastId?: ToasterToast['id'] + } + | { + type: ActionType['REMOVE_TOAST'] + toastId?: ToasterToast['id'] + } + +interface State { + toasts: ToasterToast[] +} + +const toastTimeouts = new Map>() + +const addToRemoveQueue = (toastId: string) => { + if (toastTimeouts.has(toastId)) { + return + } + + const timeout = setTimeout(() => { + toastTimeouts.delete(toastId) + dispatch({ + type: 'REMOVE_TOAST', + toastId: toastId, + }) + }, TOAST_REMOVE_DELAY) + + toastTimeouts.set(toastId, timeout) +} + +export const reducer = (state: State, action: Action): State => { + switch (action.type) { + case 'ADD_TOAST': + return { + ...state, + toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), + } + + case 'UPDATE_TOAST': + return { + ...state, + toasts: state.toasts.map((t) => (t.id === action.toast.id ? { ...t, ...action.toast } : t)), + } + + case 'DISMISS_TOAST': { + const { toastId } = action + + // ! Side effects ! - This could be extracted into a dismissToast() action, + // but I'll keep it here for simplicity + if (toastId) { + addToRemoveQueue(toastId) + } else { + state.toasts.forEach((toast) => { + addToRemoveQueue(toast.id) + }) + } + + return { + ...state, + toasts: state.toasts.map((t) => + t.id === toastId || toastId === undefined + ? { + ...t, + open: false, + } + : t, + ), + } + } + case 'REMOVE_TOAST': + if (action.toastId === undefined) { + return { + ...state, + toasts: [], + } + } + return { + ...state, + toasts: state.toasts.filter((t) => t.id !== action.toastId), + } + } +} + +const listeners: Array<(state: State) => void> = [] + +let memoryState: State = { toasts: [] } + +function dispatch(action: Action) { + memoryState = reducer(memoryState, action) + listeners.forEach((listener) => { + listener(memoryState) + }) +} + +type Toast = Omit + +function toast({ ...props }: Toast) { + const id = genId() + + const update = (props: ToasterToast) => + dispatch({ + type: 'UPDATE_TOAST', + toast: { ...props, id }, + }) + const dismiss = () => dispatch({ type: 'DISMISS_TOAST', toastId: id }) + + dispatch({ + type: 'ADD_TOAST', + toast: { + ...props, + id, + open: true, + onOpenChange: (open) => { + if (!open) dismiss() + }, + }, + }) + + return { + id: id, + dismiss, + update, + } +} + +function useToast() { + const [state, setState] = React.useState(memoryState) + + React.useEffect(() => { + listeners.push(setState) + return () => { + const index = listeners.indexOf(setState) + if (index > -1) { + listeners.splice(index, 1) + } + } + }, [state]) + + return { + ...state, + toast, + dismiss: (toastId?: string) => dispatch({ type: 'DISMISS_TOAST', toastId }), + } +} + +export { useToast, toast } diff --git a/packages/uikit/package.json b/packages/uikit/package.json index f92e94c4..e641c5e1 100644 --- a/packages/uikit/package.json +++ b/packages/uikit/package.json @@ -24,7 +24,7 @@ ], "main": "dist/index.js", "scripts": { - "test": "mocha ./tests/*.spec.ts", + "test": "mocha ./tests/allocation.spec.ts", "build": "tsc", "inline-wasm": "wasmwrap --include-decode false --input node_modules/yoga-wasm-web/dist/yoga.wasm --output src/flex/wasm.ts", "fix:inline-wasm": "replace-in-files --string 'const base64 =' --replacement 'const base64: string =' src/flex/wasm.ts", diff --git a/packages/uikit/src/allocation/sorted-buckets.ts b/packages/uikit/src/allocation/sorted-buckets.ts index 216f66ff..b8812e1d 100644 --- a/packages/uikit/src/allocation/sorted-buckets.ts +++ b/packages/uikit/src/allocation/sorted-buckets.ts @@ -65,7 +65,7 @@ export function removeFromSortedBuckets( buckets: Array>, bucketIndex: number, element: E, - elementIndex: number, + elementIndex: number | undefined, activateElement: (element: E, bucket: Bucket, index: number) => void, clearBufferAt: (index: number) => void, setElementIndex: (element: E, index: number) => void, @@ -81,7 +81,7 @@ export function removeFromSortedBuckets( bucket.add.splice(addIndex, 1) return false } - if (elementIndex >= bucket.elements.length) { + if (elementIndex == null || elementIndex >= bucket.elements.length) { throw new Error(`no element at index ${elementIndex}`) } if (bucket.add.length > 0) { @@ -114,7 +114,7 @@ export function removeFromSortedBuckets( //we are at the last bucket => merge missing space with the previous bucket(s) let currentBucket = bucket - while (currentBucket.elements.length === 0 && bucketIndex > 0) { + while (currentBucket.elements.length === 0 && currentBucket.add.length == 0 && bucketIndex > 0) { const prevBucket = buckets[bucketIndex - 1] prevBucket.missingSpace += currentBucket.missingSpace currentBucket = buckets[--bucketIndex] @@ -141,7 +141,9 @@ export function updateSortedBucketsAllocation( lastBucketWithElements = i } - if (bucket.missingSpace === 0) { + const lastBucket = i === bucketsLength - 1 + + if (!lastBucket && bucket.missingSpace === 0) { continue } @@ -156,15 +158,15 @@ export function updateSortedBucketsAllocation( continue } const otherHasSpace = otherBucket.missingSpace < 0 - if (hasSpace && otherHasSpace) { + if (otherHasSpace && (lastBucket || hasSpace)) { //case 2 - both have space: merge space into bucket by shifting to other bucket (so that the hole is increased at the end of the bucket) shiftLeft(buckets, bufferCopyWithin, ii, i, Math.abs(otherBucket.missingSpace)) continue } - if (hasSpace === otherHasSpace) { + if (!hasSpace && !otherHasSpace) { continue } - //case 1 - has has space the other needs space: shift to the once with space + //case 1 - bucket has space the other needs space: shift to the one with space const shiftBy = Math.min(Math.abs(otherBucket.missingSpace), Math.abs(bucket.missingSpace)) if (hasSpace) { @@ -176,6 +178,10 @@ export function updateSortedBucketsAllocation( } } } + const newLastBucket = buckets[lastBucketWithElements] + for (let i = lastBucketWithElements + 1; i < bucketsLength; i++) { + newLastBucket.missingSpace += buckets[i].missingSpace + } bucketsLength = buckets.length = lastBucketWithElements + 1 //add elements at the end of the elements of the buckets diff --git a/packages/uikit/src/order.ts b/packages/uikit/src/order.ts index af9a9e71..0cf680fe 100644 --- a/packages/uikit/src/order.ts +++ b/packages/uikit/src/order.ts @@ -25,6 +25,10 @@ function reversePainterSortStable(a: RenderItem, b: RenderItem) { return bDistanceRef.current - aDistanceRef.current } +export function patchRenderOrder(renderer: WebGLRenderer): void { + renderer.setTransparentSort(reversePainterSortStable) +} + //the following order tries to represent the most common element order of the respective element types (e.g. panels are most likely the background element) export const ElementType = { Panel: 0, //render first @@ -108,7 +112,3 @@ export function setupRenderOrder(result: T, rootCameraDistance: CameraDistanc ;(result as any)[orderInfoKey] = orderInfo return result } - -export function patchRenderOrder(renderer: WebGLRenderer): void { - renderer.setTransparentSort(reversePainterSortStable) -} diff --git a/packages/uikit/src/panel/instanced-panel-group.ts b/packages/uikit/src/panel/instanced-panel-group.ts index 9172ca45..39bfea51 100644 --- a/packages/uikit/src/panel/instanced-panel-group.ts +++ b/packages/uikit/src/panel/instanced-panel-group.ts @@ -6,7 +6,6 @@ import { updateSortedBucketsAllocation, resizeSortedBucketsSpace, } from '../allocation/sorted-buckets.js' -import { defaultClippingData } from '../clipping.js' import { panelMaterialDefaultData } from './panel-material.js' import { InstancedPanel } from './instanced-panel.js' import { InstancedPanelMesh } from './instanced-panel-mesh.js' @@ -28,9 +27,6 @@ export class InstancedPanelGroup extends Group { this.instanceData.set(panelMaterialDefaultData, 16 * index) this.instanceData.addUpdateRange(16 * index, 16) this.instanceData.needsUpdate = true - this.instanceClipping.set(defaultClippingData, 16 * index) - this.instanceClipping.addUpdateRange(16 * index, 16) - this.instanceClipping.needsUpdate = true element.activate(bucket, indexInBucket) } @@ -79,7 +75,7 @@ export class InstancedPanelGroup extends Group { this.requestUpdate(0) } - delete(bucketIndex: number, elementIndex: number, panel: InstancedPanel): void { + delete(bucketIndex: number, elementIndex: number | undefined, panel: InstancedPanel): void { this.elementCount -= 1 if ( !removeFromSortedBuckets( diff --git a/packages/uikit/src/panel/instanced-panel-mesh.ts b/packages/uikit/src/panel/instanced-panel-mesh.ts index 948d4479..e797ecb5 100644 --- a/packages/uikit/src/panel/instanced-panel-mesh.ts +++ b/packages/uikit/src/panel/instanced-panel-mesh.ts @@ -19,7 +19,6 @@ export class InstancedPanelMesh extends Mesh { this.frustumCulled = false panelGeometry.attributes.aData = instanceData panelGeometry.attributes.aClipping = instanceClipping - this.frustumCulled = false } dispose() { diff --git a/packages/uikit/src/panel/instanced-panel.ts b/packages/uikit/src/panel/instanced-panel.ts index 6c096153..483b3c6b 100644 --- a/packages/uikit/src/panel/instanced-panel.ts +++ b/packages/uikit/src/panel/instanced-panel.ts @@ -198,8 +198,10 @@ export class InstancedPanel implements WithImmediateProperties, WithBatchedPrope return } this.active.value = false + this.group.delete(this.minorIndex, this.indexInBucket, this) this.insertedIntoGroup = false - this.group.delete(this.minorIndex, this.indexInBucket!, this) + this.bucket = undefined + this.indexInBucket = undefined const unsubscribeListLength = this.unsubscribeList.length for (let i = 0; i < unsubscribeListLength; i++) { this.unsubscribeList[i]() diff --git a/packages/uikit/src/panel/interaction-panel-mesh.ts b/packages/uikit/src/panel/interaction-panel-mesh.ts index 3659f0fa..73a50b78 100644 --- a/packages/uikit/src/panel/interaction-panel-mesh.ts +++ b/packages/uikit/src/panel/interaction-panel-mesh.ts @@ -64,9 +64,6 @@ export function makeClippedRaycast( const oldLength = intersects.length fn.call(mesh, raycaster, intersects) const clippingPlanes = clippingRect.value?.planes - if (clippingPlanes == null) { - return - } const outerMatrixWorld = rootGroup.matrixWorld outer: for (let i = intersects.length - 1; i >= oldLength; i--) { const intersection = intersects[i] @@ -74,6 +71,9 @@ export function makeClippedRaycast( orderInfo.majorIndex * 0.01 + orderInfo.elementType * 0.001 + //1-10 orderInfo.minorIndex * 0.00001 //1-100 + if (clippingPlanes == null) { + continue + } for (let ii = 0; ii < 4; ii++) { planeHelper.copy(clippingPlanes[ii]).applyMatrix4(outerMatrixWorld) if (planeHelper.distanceToPoint(intersection.point) < 0) { diff --git a/packages/uikit/src/panel/panel-material.ts b/packages/uikit/src/panel/panel-material.ts index f7f3e14b..fbbbdb38 100644 --- a/packages/uikit/src/panel/panel-material.ts +++ b/packages/uikit/src/panel/panel-material.ts @@ -364,6 +364,10 @@ export function compilePanelMaterial(parameters: WebGLProgramParametersWithUnifo if(backgroundOpacity < 0.0 && backgroundColor.r >= 0.0) { backgroundOpacity = 1.0; } + + if(backgroundOpacity <= 0.0) { + discard; + } diffuseColor.rgb = mix(borderColor, diffuseColor.rgb * backgroundColor, transition); diff --git a/packages/uikit/src/text/react.tsx b/packages/uikit/src/text/react.tsx index 02e718fb..bea6017d 100644 --- a/packages/uikit/src/text/react.tsx +++ b/packages/uikit/src/text/react.tsx @@ -65,7 +65,6 @@ export type FontFamilyUrls = Partial> const FontFamiliesContext = createContext>(null as any) -//TODO: update to point to THIS repo const defaultFontFamilyUrls = { inter: { light: 'https://pmndrs.github.io/uikit/fonts/inter-light.json', diff --git a/packages/uikit/tests/allocation.spec.ts b/packages/uikit/tests/allocation.spec.ts index 72a3f8d0..9fac6646 100644 --- a/packages/uikit/tests/allocation.spec.ts +++ b/packages/uikit/tests/allocation.spec.ts @@ -1,35 +1,35 @@ -import { expect } from "chai"; +import { expect } from 'chai' import { Bucket, addToSortedBuckets, removeFromSortedBuckets, resizeSortedBucketsSpace, updateSortedBucketsAllocation, -} from "../src/allocation/sorted-buckets.js"; +} from '../src/allocation/sorted-buckets.js' type Element = { - value: number; - index: number; -}; + value: number + index: number +} function createSortedBuckets(data: Float32Array, buckets: Array>) { const activateElement = (element: Element, bucket: Bucket, index: number) => { - data[bucket.offset + index] = element.value; - element.index = index; - console.log("activate", element.value, "at", bucket.offset, "/", index, ":", ...data); - }; + data[bucket.offset + index] = element.value + element.index = index + console.log('activate', element.value, 'at', bucket.offset, '/', index, ':', ...data) + } const setElementIndex = (element: Element, index: number) => { - element.index = index; - }; + element.index = index + } const copyBufferWithin = (targetIndex: number, startIndex: number, endIndex: number) => { - data.copyWithin(targetIndex, startIndex, endIndex); - console.log("copy ", startIndex, "-", endIndex, "to", targetIndex, ":", ...data); - }; + data.copyWithin(targetIndex, startIndex, endIndex) + console.log('copy ', startIndex, '-', endIndex, 'to', targetIndex, ':', ...data) + } return { add(value: number, bucketIndex: number): Element { - const result = { value, index: 0 }; - addToSortedBuckets(buckets, bucketIndex, result, activateElement); - return result; + const result = { value, index: 0 } + addToSortedBuckets(buckets, bucketIndex, result, activateElement) + return result }, remove(element: Element, bucketIndex: number) { removeFromSortedBuckets( @@ -41,31 +41,31 @@ function createSortedBuckets(data: Float32Array, buckets: Array> () => {}, setElementIndex, copyBufferWithin, - ); + ) }, update() { - updateSortedBucketsAllocation(buckets, activateElement, copyBufferWithin); + updateSortedBucketsAllocation(buckets, activateElement, copyBufferWithin) }, - }; + } } -describe("sorted buckets allocation", () => { - it("should add 3 elements to 2 different buckets", () => { - const data = new Float32Array(3); - const buckets: Array> = []; - resizeSortedBucketsSpace(buckets, 0, 3); - const sortedBuckets = createSortedBuckets(data, buckets); - sortedBuckets.add(22, 1); - sortedBuckets.add(33, 0); - sortedBuckets.add(99, 1); - sortedBuckets.update(); - expect(data.slice(0, 3)).to.deep.equal(new Float32Array([33, 22, 99])); - }); - it("should delete 3 elements from 2 different buckets", () => { - const data = new Float32Array([1, 2, 3, 4, 5]); - const element_1 = { index: 0, value: 1 }; - const element_3 = { index: 0, value: 3 }; - const element_5 = { index: 2, value: 5 }; +describe('sorted buckets allocation', () => { + it('should add 3 elements to 2 different buckets', () => { + const data = new Float32Array(3) + const buckets: Array> = [] + resizeSortedBucketsSpace(buckets, 0, 3) + const sortedBuckets = createSortedBuckets(data, buckets) + sortedBuckets.add(22, 1) + sortedBuckets.add(33, 0) + sortedBuckets.add(99, 1) + sortedBuckets.update() + expect(data.slice(0, 3)).to.deep.equal(new Float32Array([33, 22, 99])) + }) + it('should delete 3 elements from 2 different buckets', () => { + const data = new Float32Array([1, 2, 3, 4, 5]) + const element_1 = { index: 0, value: 1 } + const element_3 = { index: 0, value: 3 } + const element_5 = { index: 2, value: 5 } const buckets: Array> = [ { add: [], @@ -79,21 +79,18 @@ describe("sorted buckets allocation", () => { offset: 2, missingSpace: 0, }, - ]; - const sortedBuckets = createSortedBuckets(data, buckets); - sortedBuckets.remove(element_1, 0); - sortedBuckets.remove(element_3, 1); - sortedBuckets.remove(element_5, 1); - sortedBuckets.update(); - expect(data.slice(0, 2)).to.deep.equal(new Float32Array([2, 4])); - expect( - buckets[1].missingSpace, - "missing space must be moved to the end of the last bucket", - ).to.equal(-3); - }); - it("should add 3 and delete 2 elements to and from 2 different buckets", () => { - const element3: Element = { index: 1, value: 3 }; - const data = new Float32Array([1, 3, 5, 0, 0, 0]); + ] + const sortedBuckets = createSortedBuckets(data, buckets) + sortedBuckets.remove(element_1, 0) + sortedBuckets.remove(element_3, 1) + sortedBuckets.remove(element_5, 1) + sortedBuckets.update() + expect(data.slice(0, 2)).to.deep.equal(new Float32Array([2, 4])) + expect(buckets[1].missingSpace, 'missing space must be moved to the end of the last bucket').to.equal(-3) + }) + it('should add 3 and delete 2 elements to and from 2 different buckets', () => { + const element3: Element = { index: 1, value: 3 } + const data = new Float32Array([1, 3, 5, 0, 0, 0]) const buckets: Array> = [ { add: [], @@ -107,31 +104,25 @@ describe("sorted buckets allocation", () => { offset: 2, missingSpace: 0, }, - ]; - const sortedBuckets = createSortedBuckets(data, buckets); - resizeSortedBucketsSpace(buckets, 3, 6); - sortedBuckets.add(4, 1); - sortedBuckets.add(2, 0); - const element6 = sortedBuckets.add(6, 1); - sortedBuckets.update(); - expect( - buckets[1].missingSpace, - "missing space must be moved to the end of the last bucket", - ).to.equal(0); - expect(data.slice(0, 6)).to.deep.equal(new Float32Array([1, 3, 2, 5, 4, 6])); - sortedBuckets.remove(element3, 0); - sortedBuckets.remove(element6, 1); - sortedBuckets.update(); - expect(data.slice(0, 4)).to.deep.equal(new Float32Array([1, 2, 5, 4])); - expect( - buckets[1].missingSpace, - "missing space must be moved to the end of the last bucket", - ).to.equal(-2); - }); + ] + const sortedBuckets = createSortedBuckets(data, buckets) + resizeSortedBucketsSpace(buckets, 3, 6) + sortedBuckets.add(4, 1) + sortedBuckets.add(2, 0) + const element6 = sortedBuckets.add(6, 1) + sortedBuckets.update() + expect(buckets[1].missingSpace, 'missing space must be moved to the end of the last bucket').to.equal(0) + expect(data.slice(0, 6)).to.deep.equal(new Float32Array([1, 3, 2, 5, 4, 6])) + sortedBuckets.remove(element3, 0) + sortedBuckets.remove(element6, 1) + sortedBuckets.update() + expect(data.slice(0, 4)).to.deep.equal(new Float32Array([1, 2, 5, 4])) + expect(buckets[1].missingSpace, 'missing space must be moved to the end of the last bucket').to.equal(-2) + }) - it("should insert in one bucket and remove from the other", () => { - const element3: Element = { index: 1, value: 3 }; - const data = new Float32Array([1, 3, 5]); + it('should insert in one bucket and remove from the other', () => { + const element3: Element = { index: 1, value: 3 } + const data = new Float32Array([1, 3, 5]) const buckets: Array> = [ { add: [], @@ -145,17 +136,17 @@ describe("sorted buckets allocation", () => { offset: 2, missingSpace: 0, }, - ]; - const sortedBuckets = createSortedBuckets(data, buckets); - sortedBuckets.remove(element3, 0); - sortedBuckets.add(3, 1); - sortedBuckets.update(); - expect(data.slice(0, 3)).to.deep.equal(new Float32Array([1, 5, 3])); - }); + ] + const sortedBuckets = createSortedBuckets(data, buckets) + sortedBuckets.remove(element3, 0) + sortedBuckets.add(3, 1) + sortedBuckets.update() + expect(data.slice(0, 3)).to.deep.equal(new Float32Array([1, 5, 3])) + }) - it("should remove empty buckets", () => { - const element3: Element = { index: 0, value: 3 }; - const data = new Float32Array([1, 3, 5]); + it('should remove empty buckets', () => { + const element3: Element = { index: 0, value: 3 } + const data = new Float32Array([1, 3, 5]) const buckets: Array> = [ { add: [], @@ -172,15 +163,15 @@ describe("sorted buckets allocation", () => { offset: 2, missingSpace: 0, }, - ]; - const sortedBuckets = createSortedBuckets(data, buckets); - sortedBuckets.remove(element3, 1); - expect(buckets.length).to.equal(1); - expect(buckets[0].missingSpace).to.equal(-1); - }); + ] + const sortedBuckets = createSortedBuckets(data, buckets) + sortedBuckets.remove(element3, 1) + expect(buckets.length).to.equal(1) + expect(buckets[0].missingSpace).to.equal(-1) + }) - it("should have no effect when adding and removing before updating", () => { - const data = new Float32Array([1, 5, 0]); + it('should have no effect when adding and removing before updating', () => { + const data = new Float32Array([1, 5, 0]) const buckets: Array> = [ { add: [], @@ -191,11 +182,11 @@ describe("sorted buckets allocation", () => { offset: 0, missingSpace: 0, }, - ]; - const sortedBuckets = createSortedBuckets(data, buckets); - const element = sortedBuckets.add(3, 0); - sortedBuckets.remove(element, 0); - sortedBuckets.update(); - expect(data.slice(0, 3)).to.deep.equal(new Float32Array([1, 5, 0])); - }); -}); + ] + const sortedBuckets = createSortedBuckets(data, buckets) + const element = sortedBuckets.add(3, 0) + sortedBuckets.remove(element, 0) + sortedBuckets.update() + expect(data.slice(0, 3)).to.deep.equal(new Float32Array([1, 5, 0])) + }) +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7f1569a3..a7c41453 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -87,12 +87,18 @@ importers: '@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/uikit': specifier: workspace:^ version: link:../../packages/uikit '@react-three/uikit-lucide': specifier: workspace:^ version: link:../../packages/icons/lucide + '@types/three': + specifier: ^0.160.0 + version: 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) @@ -102,6 +108,9 @@ importers: react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) + three: + specifier: ^0.160.0 + version: 0.160.0 devDependencies: '@types/react': specifier: ^18.2.47 @@ -235,6 +244,10 @@ importers: version: 3.0.0 packages/kits/default: + dependencies: + tunnel-rat: + specifier: ^0.1.2 + version: 0.1.2(@types/react@18.2.47)(react@18.2.0) devDependencies: '@preact/signals-core': specifier: ^1.5.1 @@ -1499,6 +1512,25 @@ packages: three: 0.160.0 zustand: 3.7.2(react@18.2.0) + /@react-three/postprocessing@2.16.0(@react-three/fiber@8.15.13)(@types/three@0.160.0)(react@18.2.0)(three@0.160.0): + resolution: {integrity: sha512-Cc+VIOxD2jVEgXrc66W6yQaAxTMg02ef2N1B5ldyLtTt22n75JxolYTullQqY4zTsyLEmORvaO85SRlZwg6Avw==} + peerDependencies: + '@react-three/fiber': '>=8.0' + react: '>=18.0' + three: '>= 0.138.0' + dependencies: + '@react-three/fiber': 8.15.13(react-dom@18.2.0)(react@18.2.0)(three@0.160.0) + buffer: 6.0.3 + maath: 0.6.0(@types/three@0.160.0)(three@0.160.0) + n8ao: 1.8.1(postprocessing@6.34.3)(three@0.160.0) + postprocessing: 6.34.3(three@0.160.0) + react: 18.2.0 + three: 0.160.0 + three-stdlib: 2.29.4(three@0.160.0) + transitivePeerDependencies: + - '@types/three' + dev: false + /@resvg/resvg-js-android-arm-eabi@2.6.0: resolution: {integrity: sha512-lJnZ/2P5aMocrFMW7HWhVne5gH82I8xH6zsfH75MYr4+/JOaVcGCTEQ06XFohGMdYRP3v05SSPLPvTM/RHjxfA==} engines: {node: '>= 10'} @@ -4187,6 +4219,16 @@ packages: '@types/three': 0.160.0 three: 0.160.0 + /maath@0.6.0(@types/three@0.160.0)(three@0.160.0): + resolution: {integrity: sha512-dSb2xQuP7vDnaYqfoKzlApeRcR2xtN8/f7WV/TMAkBC8552TwTLtOO0JTcSygkYMjNDPoo6V01jTw/aPi4JrMw==} + peerDependencies: + '@types/three': '>=0.144.0' + three: '>=0.144.0' + dependencies: + '@types/three': 0.160.0 + three: 0.160.0 + dev: false + /make-dir@3.1.0: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} engines: {node: '>=8'} @@ -4389,6 +4431,16 @@ packages: update-notifier: 5.1.0 dev: false + /n8ao@1.8.1(postprocessing@6.34.3)(three@0.160.0): + resolution: {integrity: sha512-biKUW09KnflZpeWmbCy1gjuiyZsbeG6y+EsqV+1IDqQ1KqEydXc6nUUseZp9ZRbjvEOPnsvsjaTce8Pta0803A==} + peerDependencies: + postprocessing: '>=6.30.0' + three: '>=0.137' + dependencies: + postprocessing: 6.34.3(three@0.160.0) + three: 0.160.0 + dev: false + /nanoid@3.3.3: resolution: {integrity: sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -4756,6 +4808,15 @@ packages: source-map-js: 1.0.2 dev: true + /postprocessing@6.34.3(three@0.160.0): + resolution: {integrity: sha512-AjsxAGWeHMKlCTuWLeahNnPyFwR0c/pEmveq5mBz767lFBc1+HHYzk0aRGBCE9PZIjiA27oRL7lATd+fVOscRA==} + engines: {node: '>= 0.13.2'} + peerDependencies: + three: '>= 0.138.0 < 0.162.0' + dependencies: + three: 0.160.0 + dev: false + /potpack@1.0.2: resolution: {integrity: sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==}