fix(config): добавить новые зависимости и обновить конфигурацию компонентов
Docker images / prepare-release (push) Successful in 5s
Docker images / backend-image (push) Successful in 2m37s
Docker images / frontend-image (push) Successful in 2m22s
Docker images / updater-image (push) Successful in 38s
Docker images / notify-webhook (push) Has been skipped
Docker images / publish-release (push) Successful in 8s
Docker images / prepare-release (push) Successful in 5s
Docker images / backend-image (push) Successful in 2m37s
Docker images / frontend-image (push) Successful in 2m22s
Docker images / updater-image (push) Successful in 38s
Docker images / notify-webhook (push) Has been skipped
Docker images / publish-release (push) Successful in 8s
This commit is contained in:
@@ -0,0 +1,417 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import {
|
||||
Children,
|
||||
cloneElement,
|
||||
createContext,
|
||||
CSSProperties,
|
||||
isValidElement,
|
||||
ReactElement,
|
||||
ReactNode,
|
||||
useCallback,
|
||||
useContext,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from "react"
|
||||
import { mergeProps } from "@base-ui/react/merge-props"
|
||||
import { useRender } from "@base-ui/react/use-render"
|
||||
import {
|
||||
defaultDropAnimationSideEffects,
|
||||
DndContext,
|
||||
DragEndEvent,
|
||||
DragOverlay,
|
||||
DragStartEvent,
|
||||
DropAnimation,
|
||||
KeyboardSensor,
|
||||
MeasuringStrategy,
|
||||
Modifiers,
|
||||
MouseSensor,
|
||||
TouchSensor,
|
||||
UniqueIdentifier,
|
||||
useSensor,
|
||||
useSensors,
|
||||
type DraggableSyntheticListeners,
|
||||
} from "@dnd-kit/core"
|
||||
import {
|
||||
arrayMove,
|
||||
defaultAnimateLayoutChanges,
|
||||
rectSortingStrategy,
|
||||
SortableContext,
|
||||
sortableKeyboardCoordinates,
|
||||
useSortable,
|
||||
verticalListSortingStrategy,
|
||||
type AnimateLayoutChanges,
|
||||
} from "@dnd-kit/sortable"
|
||||
import { CSS } from "@dnd-kit/utilities"
|
||||
import { createPortal } from "react-dom"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
// Sortable Item Context
|
||||
const SortableItemContext = createContext<{
|
||||
listeners: DraggableSyntheticListeners | undefined
|
||||
isDragging?: boolean
|
||||
disabled?: boolean
|
||||
}>({
|
||||
listeners: undefined,
|
||||
isDragging: false,
|
||||
disabled: false,
|
||||
})
|
||||
|
||||
const IsOverlayContext = createContext(false)
|
||||
|
||||
const SortableInternalContext = createContext<{
|
||||
activeId: UniqueIdentifier | null
|
||||
modifiers?: Modifiers
|
||||
}>({
|
||||
activeId: null,
|
||||
modifiers: undefined,
|
||||
})
|
||||
|
||||
const animateLayoutChanges: AnimateLayoutChanges = (args) =>
|
||||
defaultAnimateLayoutChanges({ ...args, wasDragging: true })
|
||||
|
||||
const dropAnimationConfig: DropAnimation = {
|
||||
sideEffects: defaultDropAnimationSideEffects({
|
||||
styles: {
|
||||
active: {
|
||||
opacity: "0.4",
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
|
||||
// Multipurpose Sortable Component
|
||||
export interface SortableRootProps<T> extends Omit<
|
||||
useRender.ComponentProps<"div">,
|
||||
"onDragStart" | "onDragEnd" | "children"
|
||||
> {
|
||||
value: T[]
|
||||
onValueChange: (value: T[]) => void
|
||||
getItemValue: (item: T) => string
|
||||
children: ReactNode
|
||||
onMove?: (event: {
|
||||
event: DragEndEvent
|
||||
activeIndex: number
|
||||
overIndex: number
|
||||
}) => void
|
||||
strategy?: "horizontal" | "vertical" | "grid"
|
||||
onDragStart?: (event: DragStartEvent) => void
|
||||
onDragEnd?: (event: DragEndEvent) => void
|
||||
modifiers?: Modifiers
|
||||
}
|
||||
|
||||
function Sortable<T>({
|
||||
value,
|
||||
onValueChange,
|
||||
getItemValue,
|
||||
className,
|
||||
render,
|
||||
onMove,
|
||||
strategy = "vertical",
|
||||
onDragStart,
|
||||
onDragEnd,
|
||||
modifiers,
|
||||
children,
|
||||
...props
|
||||
}: SortableRootProps<T>) {
|
||||
const [activeId, setActiveId] = useState<UniqueIdentifier | null>(null)
|
||||
const [mounted, setMounted] = useState(false)
|
||||
|
||||
useLayoutEffect(() => setMounted(true), [])
|
||||
|
||||
const sensors = useSensors(
|
||||
useSensor(MouseSensor, {
|
||||
activationConstraint: {
|
||||
distance: 10,
|
||||
},
|
||||
}),
|
||||
useSensor(TouchSensor, {
|
||||
activationConstraint: {
|
||||
delay: 250,
|
||||
tolerance: 5,
|
||||
},
|
||||
}),
|
||||
useSensor(KeyboardSensor, {
|
||||
coordinateGetter: sortableKeyboardCoordinates,
|
||||
})
|
||||
)
|
||||
|
||||
const handleDragStart = useCallback(
|
||||
(event: DragStartEvent) => {
|
||||
setActiveId(event.active.id)
|
||||
onDragStart?.(event)
|
||||
},
|
||||
[onDragStart]
|
||||
)
|
||||
|
||||
const handleDragEnd = useCallback(
|
||||
(event: DragEndEvent) => {
|
||||
const { active, over } = event
|
||||
setActiveId(null)
|
||||
onDragEnd?.(event)
|
||||
|
||||
if (!over) return
|
||||
|
||||
// Handle item reordering
|
||||
const activeIndex = value.findIndex(
|
||||
(item: T) => getItemValue(item) === active.id
|
||||
)
|
||||
const overIndex = value.findIndex(
|
||||
(item: T) => getItemValue(item) === over.id
|
||||
)
|
||||
|
||||
if (activeIndex !== overIndex) {
|
||||
if (onMove) {
|
||||
onMove({ event, activeIndex, overIndex })
|
||||
} else {
|
||||
const newValue = arrayMove(value, activeIndex, overIndex)
|
||||
onValueChange(newValue)
|
||||
}
|
||||
}
|
||||
},
|
||||
[value, getItemValue, onValueChange, onMove, onDragEnd]
|
||||
)
|
||||
|
||||
const handleDragCancel = useCallback(() => {
|
||||
setActiveId(null)
|
||||
}, [])
|
||||
|
||||
const getStrategy = () => {
|
||||
switch (strategy) {
|
||||
case "horizontal":
|
||||
return rectSortingStrategy
|
||||
case "grid":
|
||||
return rectSortingStrategy
|
||||
case "vertical":
|
||||
default:
|
||||
return verticalListSortingStrategy
|
||||
}
|
||||
}
|
||||
|
||||
const itemIds = useMemo(() => value.map(getItemValue), [value, getItemValue])
|
||||
|
||||
const contextValue = useMemo(
|
||||
() => ({ activeId, modifiers }),
|
||||
[activeId, modifiers]
|
||||
)
|
||||
|
||||
const defaultProps = {
|
||||
"data-slot": "sortable",
|
||||
"data-dragging": activeId !== null,
|
||||
className: cn(activeId !== null && "cursor-grabbing!", className),
|
||||
children,
|
||||
}
|
||||
|
||||
// Find the active child for the overlay
|
||||
const overlayContent = useMemo(() => {
|
||||
if (!activeId) return null
|
||||
let result: ReactNode = null
|
||||
Children.forEach(children, (child) => {
|
||||
if (isValidElement(child) && (child.props as any).value === activeId) {
|
||||
result = cloneElement(child as ReactElement<any>, {
|
||||
...(child.props as any),
|
||||
className: cn((child.props as any).className, "z-50"),
|
||||
})
|
||||
}
|
||||
})
|
||||
return result
|
||||
}, [activeId, children])
|
||||
|
||||
return (
|
||||
<SortableInternalContext.Provider value={contextValue}>
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
modifiers={modifiers}
|
||||
measuring={{
|
||||
droppable: {
|
||||
strategy: MeasuringStrategy.Always,
|
||||
},
|
||||
}}
|
||||
onDragStart={handleDragStart}
|
||||
onDragEnd={handleDragEnd}
|
||||
onDragCancel={handleDragCancel}
|
||||
>
|
||||
<SortableContext items={itemIds} strategy={getStrategy()}>
|
||||
{useRender({
|
||||
defaultTagName: "div",
|
||||
render,
|
||||
props: mergeProps<"div">(defaultProps, props),
|
||||
})}
|
||||
</SortableContext>
|
||||
{mounted &&
|
||||
createPortal(
|
||||
<DragOverlay
|
||||
dropAnimation={dropAnimationConfig}
|
||||
modifiers={modifiers}
|
||||
className={cn("z-50", activeId && "cursor-grabbing")}
|
||||
>
|
||||
<IsOverlayContext.Provider value={true}>
|
||||
{overlayContent}
|
||||
</IsOverlayContext.Provider>
|
||||
</DragOverlay>,
|
||||
document.body
|
||||
)}
|
||||
</DndContext>
|
||||
</SortableInternalContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export interface SortableItemProps extends useRender.ComponentProps<"div"> {
|
||||
value: string
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
function SortableItem({
|
||||
value,
|
||||
className,
|
||||
render,
|
||||
disabled,
|
||||
...props
|
||||
}: SortableItemProps) {
|
||||
const isOverlay = useContext(IsOverlayContext)
|
||||
|
||||
const {
|
||||
setNodeRef,
|
||||
transform,
|
||||
transition,
|
||||
attributes,
|
||||
listeners,
|
||||
isDragging: isSortableDragging,
|
||||
} = useSortable({
|
||||
id: value,
|
||||
disabled: disabled || isOverlay,
|
||||
animateLayoutChanges,
|
||||
})
|
||||
|
||||
if (isOverlay) {
|
||||
const defaultProps = {
|
||||
"data-slot": "sortable-item",
|
||||
"data-value": value,
|
||||
"data-dragging": true,
|
||||
className: cn(className),
|
||||
children: props.children,
|
||||
}
|
||||
|
||||
return (
|
||||
<SortableItemContext.Provider
|
||||
value={{ listeners: undefined, isDragging: true, disabled: false }}
|
||||
>
|
||||
{useRender({
|
||||
defaultTagName: "div",
|
||||
render,
|
||||
props: mergeProps<"div">(defaultProps, props),
|
||||
})}
|
||||
</SortableItemContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
const style = {
|
||||
transition,
|
||||
transform: CSS.Transform.toString(transform),
|
||||
} as CSSProperties
|
||||
|
||||
const defaultProps = {
|
||||
"data-slot": "sortable-item",
|
||||
"data-value": value,
|
||||
"data-dragging": isSortableDragging,
|
||||
"data-disabled": disabled,
|
||||
ref: setNodeRef,
|
||||
style,
|
||||
...attributes,
|
||||
className: cn(
|
||||
isSortableDragging && "opacity-50 z-50",
|
||||
disabled && "opacity-50",
|
||||
className
|
||||
),
|
||||
children: props.children,
|
||||
}
|
||||
|
||||
return (
|
||||
<SortableItemContext.Provider
|
||||
value={{ listeners, isDragging: isSortableDragging, disabled }}
|
||||
>
|
||||
{useRender({
|
||||
defaultTagName: "div",
|
||||
render,
|
||||
props: mergeProps<"div">(defaultProps, props),
|
||||
})}
|
||||
</SortableItemContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export interface SortableItemHandleProps extends useRender.ComponentProps<"div"> {
|
||||
cursor?: boolean
|
||||
}
|
||||
|
||||
function SortableItemHandle({
|
||||
className,
|
||||
render,
|
||||
cursor = true,
|
||||
...props
|
||||
}: SortableItemHandleProps) {
|
||||
const { listeners, isDragging, disabled } = useContext(SortableItemContext)
|
||||
|
||||
const defaultProps = {
|
||||
"data-slot": "sortable-item-handle",
|
||||
"data-dragging": isDragging,
|
||||
"data-disabled": disabled,
|
||||
...listeners,
|
||||
className: cn(
|
||||
cursor && (isDragging ? "cursor-grabbing!" : "cursor-grab!"),
|
||||
className
|
||||
),
|
||||
children: props.children,
|
||||
}
|
||||
|
||||
return useRender({
|
||||
defaultTagName: "div",
|
||||
render,
|
||||
props: mergeProps<"div">(defaultProps, props),
|
||||
})
|
||||
}
|
||||
|
||||
export interface SortableOverlayProps extends Omit<
|
||||
React.ComponentProps<typeof DragOverlay>,
|
||||
"children"
|
||||
> {
|
||||
children?: ReactNode | ((params: { value: UniqueIdentifier }) => ReactNode)
|
||||
}
|
||||
|
||||
function SortableOverlay({
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: SortableOverlayProps) {
|
||||
const { activeId, modifiers } = useContext(SortableInternalContext)
|
||||
const [mounted, setMounted] = useState(false)
|
||||
|
||||
useLayoutEffect(() => setMounted(true), [])
|
||||
|
||||
const content =
|
||||
activeId && children
|
||||
? typeof children === "function"
|
||||
? children({ value: activeId })
|
||||
: children
|
||||
: null
|
||||
|
||||
if (!mounted) return null
|
||||
|
||||
return createPortal(
|
||||
<DragOverlay
|
||||
dropAnimation={dropAnimationConfig}
|
||||
modifiers={modifiers}
|
||||
className={cn("z-50", activeId && "cursor-grabbing", className)}
|
||||
{...props}
|
||||
>
|
||||
<IsOverlayContext.Provider value={true}>
|
||||
{content}
|
||||
</IsOverlayContext.Provider>
|
||||
</DragOverlay>,
|
||||
document.body
|
||||
)
|
||||
}
|
||||
|
||||
export { Sortable, SortableItem, SortableItemHandle, SortableOverlay }
|
||||
Reference in New Issue
Block a user