diff options
Diffstat (limited to 'apps/web/src/components/editor/panels/timeline')
6 files changed, 404 insertions, 78 deletions
diff --git a/apps/web/src/components/editor/panels/timeline/bookmarks.tsx b/apps/web/src/components/editor/panels/timeline/bookmarks.tsx index cfcb1e23..64824f9a 100644 --- a/apps/web/src/components/editor/panels/timeline/bookmarks.tsx +++ b/apps/web/src/components/editor/panels/timeline/bookmarks.tsx @@ -1,21 +1,75 @@ +"use client"; + +import { useEffect, useState } from "react"; +import type { EditorCore } from "@/core"; import { useEditor } from "@/hooks/use-editor"; -import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants"; +import type { BookmarkDragState } from "@/hooks/timeline/use-bookmark-drag"; +import { BOOKMARK_TIME_EPSILON } from "@/lib/timeline/bookmarks"; +import { + DEFAULT_BOOKMARK_COLOR, + TIMELINE_CONSTANTS, +} from "@/constants/timeline-constants"; +import { DEFAULT_FPS } from "@/constants/project-constants"; import { getSnappedSeekTime } from "@/lib/time"; -import { Bookmark02Icon } from "@hugeicons/core-free-icons"; +import { + ArrowTurnBackwardIcon, + Delete02Icon, +} from "@hugeicons/core-free-icons"; import { HugeiconsIcon } from "@hugeicons/react"; +import type { Bookmark } from "@/types/timeline"; +import { + Popover, + PopoverAnchor, + PopoverContent, +} from "@/components/ui/popover"; +import { Input } from "@/components/ui/input"; +import { ColorPicker } from "@/components/ui/color-picker"; +import { Button } from "@/components/ui/button"; +import { Label } from "@/components/ui/label"; +import { uppercase } from "@/utils/string"; +import { clamp } from "@/utils/math"; + +const PIXELS_PER_SECOND = TIMELINE_CONSTANTS.PIXELS_PER_SECOND; +const MIN_BOOKMARK_WIDTH_PX = 2; +const BOOKMARK_MARKER_WIDTH_PX = 12; +const BOOKMARK_MARKER_HEIGHT_PX = 15; +const BOOKMARK_HALF_WIDTH_PX = BOOKMARK_MARKER_WIDTH_PX / 2; +const BOOKMARK_MARKER_CLIP_PATH = + "polygon(50% 100%, 12% 72%, 12% 10%, 88% 10%, 88% 72%)"; + +function seekToBookmarkTime({ + editor, + time, +}: { + editor: EditorCore; + time: number; +}) { + const activeProject = editor.project.getActive(); + const duration = editor.timeline.getTotalDuration(); + const fps = activeProject?.settings.fps ?? DEFAULT_FPS; + const snappedTime = getSnappedSeekTime({ rawTime: time, duration, fps }); + editor.playback.seek({ time: snappedTime }); +} interface TimelineBookmarksRowProps { zoomLevel: number; dynamicTimelineWidth: number; - handleWheel: (e: React.WheelEvent) => void; - handleTimelineContentClick: (e: React.MouseEvent) => void; - handleRulerTrackingMouseDown: (e: React.MouseEvent) => void; - handleRulerMouseDown: (e: React.MouseEvent) => void; + dragState: BookmarkDragState; + onBookmarkMouseDown: (params: { + event: React.MouseEvent; + bookmark: Bookmark; + }) => void; + handleWheel: (event: React.WheelEvent) => void; + handleTimelineContentClick: (event: React.MouseEvent) => void; + handleRulerTrackingMouseDown: (event: React.MouseEvent) => void; + handleRulerMouseDown: (event: React.MouseEvent) => void; } export function TimelineBookmarksRow({ zoomLevel, dynamicTimelineWidth, + dragState, + onBookmarkMouseDown, handleWheel, handleTimelineContentClick, handleRulerTrackingMouseDown, @@ -34,17 +88,23 @@ export function TimelineBookmarksRow({ aria-label="Timeline ruler" type="button" onWheel={handleWheel} - onClick={handleTimelineContentClick} + onClick={(event) => { + if (!event.currentTarget.contains(event.target as Node)) return; + handleTimelineContentClick(event); + }} onMouseDown={(event) => { + if (!event.currentTarget.contains(event.target as Node)) return; handleRulerMouseDown(event); handleRulerTrackingMouseDown(event); }} > - {activeScene.bookmarks.map((time: number) => ( + {activeScene.bookmarks.map((bookmark) => ( <TimelineBookmark - key={`bookmark-row-${time}`} - time={time} + key={`bookmark-${bookmark.time}`} + bookmark={bookmark} zoomLevel={zoomLevel} + dragState={dragState} + onBookmarkMouseDown={onBookmarkMouseDown} /> ))} </button> @@ -52,60 +112,305 @@ export function TimelineBookmarksRow({ ); } -export function TimelineBookmark({ - time, +function TimelineBookmark({ + bookmark, zoomLevel, + dragState, + onBookmarkMouseDown, }: { - time: number; + bookmark: Bookmark; zoomLevel: number; + dragState: BookmarkDragState; + onBookmarkMouseDown: (params: { + event: React.MouseEvent; + bookmark: Bookmark; + }) => void; }) { const editor = useEditor(); - const activeProject = editor.project.getActive(); const duration = editor.timeline.getTotalDuration(); + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + const isDragging = + dragState.isDragging && + dragState.bookmarkTime !== null && + Math.abs(dragState.bookmarkTime - bookmark.time) < BOOKMARK_TIME_EPSILON; + + const displayTime = isDragging ? dragState.currentTime : bookmark.time; + const time = bookmark.time; + const bookmarkDuration = bookmark.duration ?? 0; + const durationWidth = + bookmarkDuration > 0 ? bookmarkDuration * PIXELS_PER_SECOND * zoomLevel : 0; + const hasDurationRange = durationWidth > MIN_BOOKMARK_WIDTH_PX; + const bookmarkWidth = BOOKMARK_MARKER_WIDTH_PX + Math.max(durationWidth, 0); + const left = displayTime * PIXELS_PER_SECOND * zoomLevel; + const bookmarkLeft = left - BOOKMARK_HALF_WIDTH_PX; + const rightHalfLeft = BOOKMARK_HALF_WIDTH_PX + Math.max(durationWidth, 0); + const iconColor = bookmark.color ?? DEFAULT_BOOKMARK_COLOR; + + const handleSeek = () => seekToBookmarkTime({ editor, time }); + + const handleClick = (event: React.MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + if (event.detail === 2) { + setIsPopoverOpen(true); + } else { + handleSeek(); + } + }; + + const handleKeyDown = (event: React.KeyboardEvent) => { + if (event.key !== "Enter" && event.key !== " ") return; + event.preventDefault(); + handleSeek(); + }; - const handleBookmarkActivate = ({ - event, - }: { - event: - | React.MouseEvent<HTMLButtonElement> - | React.KeyboardEvent<HTMLButtonElement>; - }) => { + const handleMouseDown = (event: React.MouseEvent) => { + onBookmarkMouseDown({ event, bookmark }); + event.preventDefault(); event.stopPropagation(); - const framesPerSecond = activeProject?.settings.fps ?? 30; - const snappedTime = getSnappedSeekTime({ - rawTime: time, - duration, - fps: framesPerSecond, - }); - editor.playback.seek({ time: snappedTime }); }; return ( - <button - className="absolute top-0 h-10 w-0.5 cursor-pointer border-0 bg-transparent p-0" - style={{ - left: `${time * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel}px`, - }} - aria-label={`Seek to bookmark at ${time}s`} - type="button" - onMouseDown={(event) => { - event.preventDefault(); - event.stopPropagation(); - }} - onClick={(event) => handleBookmarkActivate({ event })} - onKeyDown={(event) => { - if (event.key !== "Enter" && event.key !== " ") return; - event.preventDefault(); - handleBookmarkActivate({ event }); - }} - > - <div className="text-primary absolute top-[-1px] left-[-5px]"> - <HugeiconsIcon - icon={Bookmark02Icon} - aria-hidden="true" - className="fill-primary size-3" + <Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen}> + <PopoverAnchor asChild> + <button + className={`absolute top-0 h-full min-w-0.5 border-0 bg-transparent p-0 ${isDragging ? "cursor-grabbing" : "cursor-grab"}`} + style={{ + left: `${bookmarkLeft}px`, + width: `${bookmarkWidth}px`, + }} + aria-label={`Bookmark at ${time.toFixed(1)}s`} + type="button" + onMouseDown={handleMouseDown} + onClick={handleClick} + onKeyDown={handleKeyDown} + > + {hasDurationRange ? ( + <div + className="absolute opacity-30" + style={{ + top: 1.5, + height: BOOKMARK_MARKER_HEIGHT_PX - 2.5, + left: BOOKMARK_HALF_WIDTH_PX, + width: durationWidth, + backgroundColor: iconColor, + }} + /> + ) : null} + <div + className="absolute top-0 overflow-hidden" + style={{ + left: 0, + width: BOOKMARK_HALF_WIDTH_PX, + height: BOOKMARK_MARKER_HEIGHT_PX, + }} + > + <div + className="absolute inset-0" + style={{ + width: BOOKMARK_MARKER_WIDTH_PX, + height: BOOKMARK_MARKER_HEIGHT_PX, + clipPath: BOOKMARK_MARKER_CLIP_PATH, + backgroundColor: "hsl(var(--background))", + }} + /> + <div + className="absolute" + style={{ + top: 1, + left: 1, + width: BOOKMARK_MARKER_WIDTH_PX - 2, + height: BOOKMARK_MARKER_HEIGHT_PX - 2, + clipPath: BOOKMARK_MARKER_CLIP_PATH, + backgroundColor: iconColor, + }} + /> + </div> + <div + className="absolute top-0 overflow-hidden" + style={{ + left: rightHalfLeft, + width: BOOKMARK_HALF_WIDTH_PX, + height: BOOKMARK_MARKER_HEIGHT_PX, + }} + > + <div + className="absolute top-0" + style={{ + left: -BOOKMARK_HALF_WIDTH_PX, + width: BOOKMARK_MARKER_WIDTH_PX, + height: BOOKMARK_MARKER_HEIGHT_PX, + clipPath: BOOKMARK_MARKER_CLIP_PATH, + backgroundColor: "hsl(var(--background))", + }} + /> + <div + className="absolute" + style={{ + top: 1, + left: 1 - BOOKMARK_HALF_WIDTH_PX, + width: BOOKMARK_MARKER_WIDTH_PX - 2, + height: BOOKMARK_MARKER_HEIGHT_PX - 2, + clipPath: BOOKMARK_MARKER_CLIP_PATH, + backgroundColor: iconColor, + }} + /> + </div> + </button> + </PopoverAnchor> + <PopoverContent + className="w-64 flex flex-col gap-3 p-3" + align="start" + side="bottom" + sideOffset={8} + onOpenAutoFocus={(event) => event.preventDefault()} + > + <BookmarkPopoverContent + bookmark={bookmark} + time={time} + timelineDuration={duration} + onPopoverClose={() => setIsPopoverOpen(false)} /> + </PopoverContent> + </Popover> + ); +} + +function BookmarkPopoverContent({ + bookmark, + time, + timelineDuration, + onPopoverClose, +}: { + bookmark: Bookmark; + time: number; + timelineDuration: number; + onPopoverClose: () => void; +}) { + const editor = useEditor(); + const [draftColorHex, setDraftColorHex] = useState( + (bookmark.color ?? DEFAULT_BOOKMARK_COLOR).replace("#", "").toUpperCase(), + ); + + useEffect(() => { + setDraftColorHex( + (bookmark.color ?? DEFAULT_BOOKMARK_COLOR).replace("#", "").toUpperCase(), + ); + }, [bookmark.color]); + + const handleRemove = () => { + editor.scenes.removeBookmark({ time }); + onPopoverClose(); + }; + + const handleUpdate = ({ + note, + color, + duration, + }: Partial<{ note: string; color: string; duration: number }>) => { + const updates: Partial<{ note: string; color: string; duration: number }> = + {}; + if (note !== undefined && note !== bookmark.note) updates.note = note; + if ( + color !== undefined && + color.toUpperCase() !== (bookmark.color ?? "").toUpperCase() + ) { + updates.color = color; + } + if (duration !== undefined && duration !== bookmark.duration) { + updates.duration = duration; + } + if (Object.keys(updates).length === 0) return; + editor.scenes.updateBookmark({ time, updates }); + }; + + return ( + <> + <div className="flex flex-col gap-2"> + <Label className="text-xs">Note</Label> + <Input + placeholder="Add a note..." + value={bookmark.note ?? ""} + onChange={(event) => handleUpdate({ note: event.target.value })} + className="h-8 text-sm" + /> + </div> + <div className="flex flex-col gap-2"> + <Label className="text-xs">Color</Label> + <div className="relative"> + <ColorPicker + value={uppercase({ string: draftColorHex })} + onChange={(color) => setDraftColorHex(uppercase({ string: color }))} + onChangeEnd={(color) => + handleUpdate({ color: `#${uppercase({ string: color })}` }) + } + className="bg-background border" + /> + {bookmark.color && + bookmark.color.replace(/^#/, "").toUpperCase() !== + DEFAULT_BOOKMARK_COLOR.replace(/^#/, "").toUpperCase() && ( + <Button + type="button" + variant="text" + size="text" + aria-label="Reset to default color" + className="absolute top-1/2 right-1 -translate-y-1/2 mr-1" + onClick={() => + editor.scenes.updateBookmark({ + time, + updates: { color: undefined }, + }) + } + > + <HugeiconsIcon + icon={ArrowTurnBackwardIcon} + className="!size-3.5" + /> + </Button> + )} + </div> </div> - </button> + <div className="flex flex-col gap-2"> + <Label className="text-xs">Duration</Label> + <div className="flex items-center gap-1.5"> + <Input + type="number" + min={0} + step={0.1} + value={bookmark.duration ?? 0} + onChange={(event) => { + const parsed = parseFloat(event.target.value); + const value = Number.isNaN(parsed) + ? 0 + : clamp({ + value: parsed, + min: 0, + max: Math.max(0, timelineDuration - time), + }); + handleUpdate({ duration: value }); + }} + className="h-8 text-sm" + containerClassName="w-full" + /> + </div> + </div> + <Button + type="button" + variant="outline" + size="sm" + className="text-destructive hover:bg-destructive/10" + onClick={handleRemove} + onKeyDown={(event) => { + if (event.key === "Enter" || event.key === " ") { + handleRemove(); + } + }} + aria-label="delete bookmark" + > + <HugeiconsIcon icon={Delete02Icon} className="!size-3.5" /> + Delete + </Button> + </> ); } diff --git a/apps/web/src/components/editor/panels/timeline/index.tsx b/apps/web/src/components/editor/panels/timeline/index.tsx index 94edc2d0..7e6ff326 100644 --- a/apps/web/src/components/editor/panels/timeline/index.tsx +++ b/apps/web/src/components/editor/panels/timeline/index.tsx @@ -47,6 +47,8 @@ import { useTimelineSeek } from "@/hooks/timeline/use-timeline-seek"; import { useTimelineDragDrop } from "@/hooks/timeline/use-timeline-drag-drop"; import { TimelineRuler } from "./timeline-ruler"; import { TimelineBookmarksRow } from "./bookmarks"; +import { useBookmarkDrag } from "@/hooks/timeline/use-bookmark-drag"; +import { useEdgeAutoScroll } from "@/hooks/timeline/use-edge-auto-scroll"; import { useTimelineStore } from "@/stores/timeline-store"; import { useEditor } from "@/hooks/use-editor"; import { useTimelinePlayhead } from "@/hooks/timeline/use-timeline-playhead"; @@ -126,6 +128,17 @@ export function Timeline() { onSnapPointChange: handleSnapPointChange, }); + const { + dragState: bookmarkDragState, + handleBookmarkMouseDown, + lastMouseXRef: bookmarkLastMouseXRef, + } = useBookmarkDrag({ + zoomLevel, + scrollRef: tracksScrollRef, + snappingEnabled, + onSnapPointChange: handleSnapPointChange, + }); + const { handleRulerMouseDown: handlePlayheadRulerMouseDown } = useTimelinePlayhead({ zoomLevel, @@ -168,10 +181,18 @@ export function Timeline() { containerWidth, ); + useEdgeAutoScroll({ + isActive: bookmarkDragState.isDragging, + getMouseClientX: () => bookmarkLastMouseXRef.current, + rulerScrollRef: tracksScrollRef, + tracksScrollRef, + contentWidth: dynamicTimelineWidth, + }); + const showSnapIndicator = snappingEnabled && currentSnapPoint !== null && - (dragState.isDragging || isResizing); + (dragState.isDragging || bookmarkDragState.isDragging || isResizing); const { handleTracksMouseDown, @@ -351,7 +372,7 @@ export function Timeline() { > <div ref={timelineHeaderRef} - className="bg-background sticky top-0 z-30 flex flex-col" + className="bg-background sticky top-0 flex flex-col" > <TimelineRuler zoomLevel={zoomLevel} @@ -366,6 +387,8 @@ export function Timeline() { <TimelineBookmarksRow zoomLevel={zoomLevel} dynamicTimelineWidth={dynamicTimelineWidth} + dragState={bookmarkDragState} + onBookmarkMouseDown={handleBookmarkMouseDown} handleWheel={handleWheel} handleTimelineContentClick={handleRulerClick} handleRulerTrackingMouseDown={handleRulerMouseDown} @@ -433,7 +456,7 @@ export function Timeline() { /> </div> </ContextMenuTrigger> - <ContextMenuContent className="z-200 w-40"> + <ContextMenuContent className="w-40"> <ContextMenuItem icon={<HugeiconsIcon icon={TaskAdd02Icon} />} onClick={(e) => { diff --git a/apps/web/src/components/editor/panels/timeline/snap-indicator.tsx b/apps/web/src/components/editor/panels/timeline/snap-indicator.tsx index 914edccb..e70f0cd7 100644 --- a/apps/web/src/components/editor/panels/timeline/snap-indicator.tsx +++ b/apps/web/src/components/editor/panels/timeline/snap-indicator.tsx @@ -38,7 +38,7 @@ export function SnapIndicator({ return ( <div - className="pointer-events-none absolute z-90" + className="pointer-events-none absolute" style={{ left: `${leftPosition}px`, top: topPosition, diff --git a/apps/web/src/components/editor/panels/timeline/timeline-element.tsx b/apps/web/src/components/editor/panels/timeline/timeline-element.tsx index 59ce88da..628855f6 100644 --- a/apps/web/src/components/editor/panels/timeline/timeline-element.tsx +++ b/apps/web/src/components/editor/panels/timeline/timeline-element.tsx @@ -29,6 +29,7 @@ import type { MediaAsset } from "@/types/assets"; import { mediaSupportsAudio } from "@/lib/media/media-utils"; import { getActionDefinition, type TAction, invokeAction } from "@/lib/actions"; import { useElementSelection } from "@/hooks/timeline/element/use-element-selection"; +import { resolveStickerId } from "@/lib/stickers"; import Image from "next/image"; import { ScissorIcon, @@ -139,9 +140,7 @@ export function TimelineElement({ <ContextMenu> <ContextMenuTrigger asChild> <div - className={`absolute top-0 h-full select-none ${ - isBeingDragged ? "z-30" : "z-10" - }`} + className={`absolute top-0 h-full select-none`} style={{ left: `${elementLeft}px`, width: `${elementWidth}px`, @@ -155,7 +154,6 @@ export function TimelineElement({ element={element} track={track} isSelected={isSelected} - isBeingDragged={isBeingDragged} hasAudio={hasAudio} isMuted={isMuted} mediaAssets={mediaAssets} @@ -165,7 +163,7 @@ export function TimelineElement({ /> </div> </ContextMenuTrigger> - <ContextMenuContent className="z-200 w-64"> + <ContextMenuContent className="w-64"> <ActionMenuItem action="split" icon={<HugeiconsIcon icon={ScissorIcon} />} @@ -227,7 +225,6 @@ function ElementInner({ element, track, isSelected, - isBeingDragged, hasAudio, isMuted, mediaAssets, @@ -238,7 +235,6 @@ function ElementInner({ element: TimelineElementType; track: TimelineTrack; isSelected: boolean; - isBeingDragged: boolean; hasAudio: boolean; isMuted: boolean; mediaAssets: MediaAsset[]; @@ -248,7 +244,7 @@ function ElementInner({ element: TimelineElementType, ) => void; handleResizeStart: (params: { - e: React.MouseEvent; + event: React.MouseEvent; elementId: string; side: "left" | "right"; }) => void; @@ -259,7 +255,7 @@ function ElementInner({ { type: track.type, }, - )} ${isBeingDragged ? "z-30" : "z-10"} ${canElementBeHidden(element) && element.hidden ? "opacity-50" : ""}`} + )} ${canElementBeHidden(element) && element.hidden ? "opacity-50" : ""}`} > <button type="button" @@ -321,7 +317,7 @@ function ResizeHandle({ side: "left" | "right"; elementId: string; handleResizeStart: (params: { - e: React.MouseEvent; + event: React.MouseEvent; elementId: string; side: "left" | "right"; }) => void; @@ -330,8 +326,8 @@ function ResizeHandle({ return ( <button type="button" - className={`bg-primary absolute top-0 bottom-0 z-50 flex w-[0.6rem] items-center justify-center ${isLeft ? "left-0 cursor-w-resize" : "right-0 cursor-e-resize"}`} - onMouseDown={(e) => handleResizeStart({ e, elementId, side })} + className={`bg-primary absolute top-0 bottom-0 flex w-[0.6rem] items-center justify-center ${isLeft ? "left-0 cursor-w-resize" : "right-0 cursor-e-resize"}`} + onMouseDown={(event) => handleResizeStart({ event, elementId, side })} aria-label={`${isLeft ? "Left" : "Right"} resize handle`} > <div className="bg-foreground h-[1.5rem] w-[0.2rem] rounded-full" /> @@ -362,7 +358,10 @@ function ElementContent({ return ( <div className="flex size-full items-center gap-2 pl-2"> <Image - src={`https://api.iconify.design/${element.iconName}.svg?width=20&height=20`} + src={resolveStickerId({ + stickerId: element.stickerId, + options: { width: 20, height: 20 }, + })} alt={element.name} className="size-5 shrink-0" width={20} diff --git a/apps/web/src/components/editor/panels/timeline/timeline-playhead.tsx b/apps/web/src/components/editor/panels/timeline/timeline-playhead.tsx index 8af35dc9..ebb2a37e 100644 --- a/apps/web/src/components/editor/panels/timeline/timeline-playhead.tsx +++ b/apps/web/src/components/editor/panels/timeline/timeline-playhead.tsx @@ -72,20 +72,22 @@ export function TimelinePlayhead({ aria-valuemax={duration} aria-valuenow={playheadPosition} tabIndex={0} - className="pointer-events-auto absolute z-60" + className="pointer-events-none absolute z-5" style={{ left: `${leftPosition}px`, top: 0, height: `${totalHeight}px`, width: "2px", }} - onMouseDown={handlePlayheadMouseDown} onKeyDown={handlePlayheadKeyDown} > - <div className="bg-foreground absolute left-0 h-full w-0.5 cursor-col-resize" /> + <div className="bg-foreground pointer-events-none absolute left-0 h-full w-0.5" /> - <div - className={`absolute top-1 left-1/2 size-3 -translate-x-1/2 transform rounded-full border-2 shadow-xs ${isSnappingToPlayhead ? "bg-foreground border-foreground" : "bg-foreground border-foreground/50"}`} + <button + type="button" + aria-label="Drag playhead" + className={`pointer-events-auto absolute top-1 left-1/2 size-3 -translate-x-1/2 transform cursor-col-resize rounded-full border-2 shadow-xs ${isSnappingToPlayhead ? "bg-foreground border-foreground" : "bg-foreground border-foreground/50"}`} + onMouseDown={handlePlayheadMouseDown} /> </div> ); diff --git a/apps/web/src/components/editor/panels/timeline/timeline-toolbar.tsx b/apps/web/src/components/editor/panels/timeline/timeline-toolbar.tsx index 0b02c63f..03ce88eb 100644 --- a/apps/web/src/components/editor/panels/timeline/timeline-toolbar.tsx +++ b/apps/web/src/components/editor/panels/timeline/timeline-toolbar.tsx @@ -171,7 +171,7 @@ function SceneSelector() { <SplitButtonLeft>{currentScene?.name || "No Scene"}</SplitButtonLeft> <SplitButtonSeparator /> <ScenesView> - <SplitButtonRight onClick={() => {}} type="button"> + <SplitButtonRight onClick={() => {}}> <HugeiconsIcon icon={Layers01Icon} className="size-4" /> </SplitButtonRight> </ScenesView> @@ -222,7 +222,6 @@ function ToolbarRightSection({ <Button variant="text" size="icon" - type="button" onClick={() => onZoom({ direction: "out" })} > <HugeiconsIcon icon={SearchMinusIcon} /> @@ -240,7 +239,6 @@ function ToolbarRightSection({ <Button variant="text" size="icon" - type="button" onClick={() => onZoom({ direction: "in" })} > <HugeiconsIcon icon={SearchAddIcon} /> @@ -269,7 +267,6 @@ function ToolbarButton({ <Button variant={isActive ? "secondary" : "text"} size="icon" - type="button" onClick={(event) => onClick({ event })} className={cn( "rounded-sm", |
