feat: add toolbar
This commit is contained in:
parent
c836c26a05
commit
f0445c208b
8 changed files with 199 additions and 18 deletions
|
|
@ -11,6 +11,8 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-menubar": "^1.1.2",
|
||||
"@radix-ui/react-toggle": "^1.1.0",
|
||||
"@radix-ui/react-toggle-group": "^1.1.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"konva": "^9.3.16",
|
||||
|
|
|
|||
|
|
@ -11,6 +11,12 @@ importers:
|
|||
'@radix-ui/react-menubar':
|
||||
specifier: ^1.1.2
|
||||
version: 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@radix-ui/react-toggle':
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@radix-ui/react-toggle-group':
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
class-variance-authority:
|
||||
specifier: ^0.7.1
|
||||
version: 0.7.1
|
||||
|
|
@ -634,6 +640,32 @@ packages:
|
|||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-toggle-group@1.1.0':
|
||||
resolution: {integrity: sha512-PpTJV68dZU2oqqgq75Uzto5o/XfOVgkrJ9rulVmfTKxWp3HfUjHE6CP/WLRR4AzPX9HWxw7vFow2me85Yu+Naw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-toggle@1.1.0':
|
||||
resolution: {integrity: sha512-gwoxaKZ0oJ4vIgzsfESBuSgJNdc0rv12VhHgcqN0TEJmmZixXG/2XpsLK8kzNWYcnaoRIEEQc0bEi3dIvdUpjw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-callback-ref@1.1.0':
|
||||
resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==}
|
||||
peerDependencies:
|
||||
|
|
@ -2279,6 +2311,32 @@ snapshots:
|
|||
optionalDependencies:
|
||||
'@types/react': 18.3.12
|
||||
|
||||
'@radix-ui/react-toggle-group@1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.0
|
||||
'@radix-ui/react-context': 1.1.0(@types/react@18.3.12)(react@18.3.1)
|
||||
'@radix-ui/react-direction': 1.1.0(@types/react@18.3.12)(react@18.3.1)
|
||||
'@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@radix-ui/react-roving-focus': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@radix-ui/react-toggle': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.12)(react@18.3.1)
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.12
|
||||
'@types/react-dom': 18.3.1
|
||||
|
||||
'@radix-ui/react-toggle@1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.0
|
||||
'@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.12)(react@18.3.1)
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.12
|
||||
'@types/react-dom': 18.3.1
|
||||
|
||||
'@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.3.12)(react@18.3.1)':
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
|
|
|
|||
17
src/App.tsx
17
src/App.tsx
|
|
@ -1,5 +1,6 @@
|
|||
import { useEffect, useRef, useState } from "react";
|
||||
import { Layer, Stage } from "react-konva";
|
||||
import { Hand } from "lucide-react";
|
||||
|
||||
import {
|
||||
Menubar,
|
||||
|
|
@ -12,6 +13,7 @@ import {
|
|||
MenubarSubTrigger,
|
||||
MenubarTrigger,
|
||||
} from "@/components/ui/menubar";
|
||||
import { ToggleGroup, ToggleGroupItem } from "./components/ui/toggle-group";
|
||||
|
||||
import ThemeChanger from "./components/menubar/theme-changer";
|
||||
import Blocks from "./components/blocks";
|
||||
|
|
@ -31,6 +33,7 @@ function App() {
|
|||
const [stageCoords, setStageCoords] = useState<Position>({ x: 0, y: 0 });
|
||||
const [mousePosition, setMousePosition] = useState<Position>({ x: 0, y: 0 });
|
||||
|
||||
const [tool, setTool] = useState<Tool>("hand");
|
||||
const [blocks, setBlocks] = useState<Block[]>([]);
|
||||
|
||||
const onMouseMove = (e) => {
|
||||
|
|
@ -68,10 +71,12 @@ function App() {
|
|||
}, []);
|
||||
|
||||
return (
|
||||
<main className="h-screen grid grid-rows-[2.5rem_1fr]">
|
||||
<Menubar className="rounded-none border-t-0 border-x-0">
|
||||
<main className="h-screen grid grid-rows-[2.5rem_1fr] grid-cols-[2.5rem_1fr]">
|
||||
<Menubar className="rounded-none border-t-0 border-x-0 col-span-2">
|
||||
<MenubarMenu>
|
||||
<a href="https://github.com/trafficlunar/blockmatic" className="ml-4 mr-2">blockmatic</a>
|
||||
<a href="https://github.com/trafficlunar/blockmatic" className="ml-4 mr-2">
|
||||
blockmatic
|
||||
</a>
|
||||
|
||||
<MenubarTrigger>File</MenubarTrigger>
|
||||
<MenubarContent>
|
||||
|
|
@ -99,6 +104,12 @@ function App() {
|
|||
</MenubarMenu>
|
||||
</Menubar>
|
||||
|
||||
<ToggleGroup type="single" size={"sm"} value={tool} className="flex flex-col justify-start py-0.5 border-r border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-950">
|
||||
<ToggleGroupItem value="hand">
|
||||
<Hand />
|
||||
</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
|
||||
<div ref={stageContainerRef} className="relative w-full h-full">
|
||||
<Stage
|
||||
width={stageSize.width}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { Image as KonvaImage } from "react-konva";
|
|||
|
||||
import blocksData from "@/lib/blocks/programmer-art/average_colors.json";
|
||||
|
||||
function Blocks({ blocks, setBlocks }: { blocks: Block[], setBlocks: React.Dispatch<React.SetStateAction<Block[]>> }) {
|
||||
function Blocks({ blocks, setBlocks }: { blocks: Block[]; setBlocks: React.Dispatch<React.SetStateAction<Block[]>> }) {
|
||||
const [images, setImages] = useState<{ [key: string]: HTMLImageElement }>({});
|
||||
|
||||
const findClosestBlock = (r: number, g: number, b: number) => {
|
||||
|
|
@ -52,12 +52,12 @@ function Blocks({ blocks, setBlocks }: { blocks: Block[], setBlocks: React.Dispa
|
|||
const block = findClosestBlock(imageData.data[i], imageData.data[i + 1], imageData.data[i + 2]);
|
||||
|
||||
const x = Math.floor((i / 4) % imageData.width);
|
||||
const y = Math.floor((i / 4) / imageData.width);
|
||||
const y = Math.floor(i / 4 / imageData.width);
|
||||
|
||||
newBlocks.push({
|
||||
name: block,
|
||||
x,
|
||||
y
|
||||
y,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useEffect, useState } from "react";
|
||||
|
||||
function CursorInformation({ mousePosition, blocks }: { mousePosition: Position, blocks: Block[] }) {
|
||||
const [position, setPosition] = useState({ x: 0, y: 0 });
|
||||
function CursorInformation({ mousePosition, blocks }: { mousePosition: Position; blocks: Block[] }) {
|
||||
const [position, setPosition] = useState({ x: 0, y: 0 });
|
||||
const [block, setBlock] = useState<Block>();
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -14,18 +14,22 @@ function CursorInformation({ mousePosition, blocks }: { mousePosition: Position,
|
|||
y: snappedY,
|
||||
});
|
||||
|
||||
setBlock(blocks.find(b => b.x === snappedX && b.y === snappedY));
|
||||
setBlock(blocks.find((b) => b.x === snappedX && b.y === snappedY));
|
||||
}
|
||||
}, [mousePosition]);
|
||||
|
||||
return <div className="absolute left-4 bottom-4 flex flex-col gap-1">
|
||||
<div className="bg-zinc-900 px-2 py-1 rounded shadow-xl w-fit">{block?.name ?? "air"}</div>
|
||||
return (
|
||||
<div className="absolute left-4 bottom-4 flex flex-col gap-1">
|
||||
<div className="bg-white dark:bg-zinc-950 px-2 py-1 rounded shadow-xl w-fit border border-zinc-200 dark:border-zinc-800">
|
||||
{block?.name ?? "air"}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4 bg-zinc-900 px-2 py-1 rounded shadow-xl w-fit">
|
||||
<span>X: {position.x}</span>
|
||||
<span>Y: {position.y}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-4 bg-white dark:bg-zinc-950 px-2 py-1 rounded shadow-xl w-fit border border-zinc-200 dark:border-zinc-800">
|
||||
<span>X: {position.x}</span>
|
||||
<span>Y: {position.y}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default CursorInformation
|
||||
export default CursorInformation;
|
||||
|
|
|
|||
59
src/components/ui/toggle-group.tsx
Normal file
59
src/components/ui/toggle-group.tsx
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import * as React from "react"
|
||||
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
|
||||
import { type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { toggleVariants } from "@/components/ui/toggle"
|
||||
|
||||
const ToggleGroupContext = React.createContext<
|
||||
VariantProps<typeof toggleVariants>
|
||||
>({
|
||||
size: "default",
|
||||
variant: "default",
|
||||
})
|
||||
|
||||
const ToggleGroup = React.forwardRef<
|
||||
React.ElementRef<typeof ToggleGroupPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root> &
|
||||
VariantProps<typeof toggleVariants>
|
||||
>(({ className, variant, size, children, ...props }, ref) => (
|
||||
<ToggleGroupPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn("flex items-center justify-center gap-1", className)}
|
||||
{...props}
|
||||
>
|
||||
<ToggleGroupContext.Provider value={{ variant, size }}>
|
||||
{children}
|
||||
</ToggleGroupContext.Provider>
|
||||
</ToggleGroupPrimitive.Root>
|
||||
))
|
||||
|
||||
ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName
|
||||
|
||||
const ToggleGroupItem = React.forwardRef<
|
||||
React.ElementRef<typeof ToggleGroupPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &
|
||||
VariantProps<typeof toggleVariants>
|
||||
>(({ className, children, variant, size, ...props }, ref) => {
|
||||
const context = React.useContext(ToggleGroupContext)
|
||||
|
||||
return (
|
||||
<ToggleGroupPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
toggleVariants({
|
||||
variant: context.variant || variant,
|
||||
size: context.size || size,
|
||||
}),
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</ToggleGroupPrimitive.Item>
|
||||
)
|
||||
})
|
||||
|
||||
ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName
|
||||
|
||||
export { ToggleGroup, ToggleGroupItem }
|
||||
45
src/components/ui/toggle.tsx
Normal file
45
src/components/ui/toggle.tsx
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as TogglePrimitive from "@radix-ui/react-toggle"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const toggleVariants = cva(
|
||||
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-white transition-colors hover:bg-zinc-100 hover:text-zinc-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-950 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-zinc-100 data-[state=on]:text-zinc-900 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 gap-2 dark:ring-offset-zinc-950 dark:hover:bg-zinc-800 dark:hover:text-zinc-400 dark:focus-visible:ring-zinc-300 dark:data-[state=on]:bg-zinc-800 dark:data-[state=on]:text-zinc-50",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-transparent",
|
||||
outline:
|
||||
"border border-zinc-200 bg-transparent hover:bg-zinc-100 hover:text-zinc-900 dark:border-zinc-800 dark:hover:bg-zinc-800 dark:hover:text-zinc-50",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 px-3 min-w-10",
|
||||
sm: "h-9 px-2.5 min-w-9",
|
||||
lg: "h-11 px-5 min-w-11",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const Toggle = React.forwardRef<
|
||||
React.ElementRef<typeof TogglePrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> &
|
||||
VariantProps<typeof toggleVariants>
|
||||
>(({ className, variant, size, ...props }, ref) => (
|
||||
<TogglePrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(toggleVariants({ variant, size, className }))}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
|
||||
Toggle.displayName = TogglePrimitive.Root.displayName
|
||||
|
||||
export { Toggle, toggleVariants }
|
||||
2
src/types.d.ts
vendored
2
src/types.d.ts
vendored
|
|
@ -6,3 +6,5 @@ interface Position {
|
|||
interface Block extends Position {
|
||||
name: string;
|
||||
}
|
||||
|
||||
type Tool = "hand";
|
||||
Loading…
Reference in a new issue