@mbao01/common 0.0.45 → 0.0.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/types/components/Chart/helpers.d.ts +3 -1
- package/dist/types/components/Chart/stories/args/index.d.ts +63 -0
- package/dist/types/components/DragAndDrop/Draggable/Draggable.d.ts +29 -0
- package/dist/types/components/DragAndDrop/Draggable/Draggable.example.d.ts +24 -0
- package/dist/types/components/DragAndDrop/Draggable/constants.d.ts +9 -0
- package/dist/types/components/DragAndDrop/Draggable/index.d.ts +1 -0
- package/dist/types/components/DragAndDrop/Draggable/types.d.ts +41 -0
- package/dist/types/components/DragAndDrop/Droppable/Droppable.d.ts +2 -0
- package/dist/types/components/DragAndDrop/Droppable/Droppable.example.d.ts +10 -0
- package/dist/types/components/DragAndDrop/Droppable/constants.d.ts +5 -0
- package/dist/types/components/DragAndDrop/Droppable/index.d.ts +1 -0
- package/dist/types/components/DragAndDrop/Droppable/types.d.ts +4 -0
- package/dist/types/components/DragAndDrop/Sortable/Sortable.d.ts +2 -0
- package/dist/types/components/DragAndDrop/Sortable/Sortable.example.d.ts +6 -0
- package/dist/types/components/DragAndDrop/Sortable/constants.d.ts +4 -0
- package/dist/types/components/DragAndDrop/Sortable/index.d.ts +1 -0
- package/dist/types/components/DragAndDrop/Sortable/types.d.ts +7 -0
- package/dist/types/components/DragAndDrop/index.d.ts +3 -0
- package/dist/types/components/Form/DatetimeInput/DatetimeInput.d.ts +1 -1
- package/dist/types/components/Menu/Menubar/Menubar.d.ts +42 -0
- package/dist/types/components/Menu/Menubar/types.d.ts +1 -0
- package/dist/types/components/Widget/InternalWidgetsContext.d.ts +2 -0
- package/dist/types/components/Widget/Widget.d.ts +2 -0
- package/dist/types/components/Widget/Widgets.example.d.ts +1 -0
- package/dist/types/components/Widget/WidgetsContext.d.ts +2 -0
- package/dist/types/components/Widget/hooks/index.d.ts +2 -0
- package/dist/types/components/Widget/hooks/useWidget/index.d.ts +1 -0
- package/dist/types/components/Widget/hooks/useWidget/useWidget.d.ts +7 -0
- package/dist/types/components/Widget/hooks/useWidgets/index.d.ts +1 -0
- package/dist/types/components/Widget/hooks/useWidgets/useWidgets.d.ts +1 -0
- package/dist/types/components/Widget/index.d.ts +3 -0
- package/dist/types/components/Widget/modifiers/index.d.ts +1 -0
- package/dist/types/components/Widget/modifiers/restrictToElement.d.ts +2 -0
- package/dist/types/components/Widget/types.d.ts +28 -0
- package/dist/types/index.d.ts +2 -0
- package/package.json +63 -59
- package/src/components/Chart/helpers.ts +14 -8
- package/src/components/Chart/stories/args/index.ts +12 -12
- package/src/components/DragAndDrop/Draggable/Draggable.example.tsx +147 -0
- package/src/components/DragAndDrop/Draggable/Draggable.tsx +161 -0
- package/src/components/DragAndDrop/Draggable/constants.ts +47 -0
- package/src/components/DragAndDrop/Draggable/index.ts +1 -0
- package/src/components/DragAndDrop/Draggable/types.ts +56 -0
- package/src/components/DragAndDrop/Droppable/Droppable.example.tsx +86 -0
- package/src/components/DragAndDrop/Droppable/Droppable.tsx +38 -0
- package/src/components/DragAndDrop/Droppable/constants.ts +15 -0
- package/src/components/DragAndDrop/Droppable/index.ts +1 -0
- package/src/components/DragAndDrop/Droppable/types.ts +7 -0
- package/src/components/DragAndDrop/Sortable/Sortable.example.tsx +61 -0
- package/src/components/DragAndDrop/Sortable/Sortable.tsx +65 -0
- package/src/components/DragAndDrop/Sortable/constants.ts +12 -0
- package/src/components/DragAndDrop/Sortable/index.ts +1 -0
- package/src/components/DragAndDrop/Sortable/types.ts +11 -0
- package/src/components/DragAndDrop/index.ts +3 -0
- package/src/components/Menu/Menubar/Menubar.tsx +5 -1
- package/src/components/Menu/Menubar/types.ts +2 -0
- package/src/components/Widget/InternalWidgetsContext.tsx +4 -0
- package/src/components/Widget/Widget.tsx +17 -0
- package/src/components/Widget/Widgets.example.tsx +118 -0
- package/src/components/Widget/WidgetsContext.tsx +97 -0
- package/src/components/Widget/hooks/index.ts +2 -0
- package/src/components/Widget/hooks/useWidget/index.ts +1 -0
- package/src/components/Widget/hooks/useWidget/useWidget.ts +21 -0
- package/src/components/Widget/hooks/useWidgets/index.ts +1 -0
- package/src/components/Widget/hooks/useWidgets/useWidgets.ts +12 -0
- package/src/components/Widget/index.ts +3 -0
- package/src/components/Widget/modifiers/index.ts +1 -0
- package/src/components/Widget/modifiers/restrictToElement.ts +8 -0
- package/src/components/Widget/types.ts +30 -0
- package/src/index.ts +2 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { UniqueIdentifier } from "@dnd-kit/core";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { DndContext } from "@dnd-kit/core";
|
|
4
|
+
import { arrayMove, SortableContext } from "@dnd-kit/sortable";
|
|
5
|
+
import { cn } from "../../../utilities";
|
|
6
|
+
import { Draggable } from "../Draggable";
|
|
7
|
+
import { Sortable } from "./Sortable";
|
|
8
|
+
import { type SortableProps } from "./types";
|
|
9
|
+
|
|
10
|
+
type SortableExampleProps = Partial<SortableProps> & { hasDraggableOverlay?: boolean };
|
|
11
|
+
|
|
12
|
+
export const SortableExample = ({ hasDraggableOverlay, ...props }: SortableExampleProps) => {
|
|
13
|
+
const [activeId, setActiveId] = useState<UniqueIdentifier | null>(null);
|
|
14
|
+
const [items, setItems] = useState<UniqueIdentifier[]>(["1", "2", "3", "4", "5", "6", "7", "8"]);
|
|
15
|
+
|
|
16
|
+
const activeIndex = activeId ? items.indexOf(activeId) : -1;
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<DndContext
|
|
20
|
+
onDragStart={({ active }) => {
|
|
21
|
+
if (!active) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
setActiveId(active.id);
|
|
26
|
+
}}
|
|
27
|
+
onDragEnd={({ over }) => {
|
|
28
|
+
setActiveId(null);
|
|
29
|
+
|
|
30
|
+
if (over) {
|
|
31
|
+
const overIndex = items.indexOf(over.id);
|
|
32
|
+
if (activeIndex !== overIndex) {
|
|
33
|
+
setItems((items) => arrayMove(items, activeIndex, overIndex));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}}
|
|
37
|
+
onDragCancel={() => setActiveId(null)}
|
|
38
|
+
>
|
|
39
|
+
<SortableContext items={items}>
|
|
40
|
+
<div className="grid grid-cols-3 gap-4">
|
|
41
|
+
{items.map((item) => (
|
|
42
|
+
<Sortable
|
|
43
|
+
{...props}
|
|
44
|
+
key={item}
|
|
45
|
+
id={item}
|
|
46
|
+
className={cn(
|
|
47
|
+
"flex items-center justify-center w-32 h-32 bg-base-100 border border-primary-content/30 rounded-md text-sm p-2 data-[draggable]:shadow data-[draggable-active]:z-20",
|
|
48
|
+
{
|
|
49
|
+
"data-[draggable-active]:opacity-30": hasDraggableOverlay,
|
|
50
|
+
}
|
|
51
|
+
)}
|
|
52
|
+
>
|
|
53
|
+
{item}
|
|
54
|
+
</Sortable>
|
|
55
|
+
))}
|
|
56
|
+
{hasDraggableOverlay ? <Draggable.Overlay className="bg-base-100 shadow-lg" /> : null}
|
|
57
|
+
</div>
|
|
58
|
+
</SortableContext>
|
|
59
|
+
</DndContext>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { useSortable } from "@dnd-kit/sortable";
|
|
2
|
+
import { CSS } from "@dnd-kit/utilities";
|
|
3
|
+
import { cn } from "../../../utilities";
|
|
4
|
+
import { Draggable } from "../Draggable";
|
|
5
|
+
import { getSortableClasses } from "./constants";
|
|
6
|
+
import { type SortableProps } from "./types";
|
|
7
|
+
|
|
8
|
+
export const Sortable = ({
|
|
9
|
+
id,
|
|
10
|
+
data,
|
|
11
|
+
style,
|
|
12
|
+
className,
|
|
13
|
+
children,
|
|
14
|
+
disabled,
|
|
15
|
+
attributes,
|
|
16
|
+
resizeObserverConfig,
|
|
17
|
+
animateLayoutChanges,
|
|
18
|
+
getNewIndex,
|
|
19
|
+
strategy,
|
|
20
|
+
transition,
|
|
21
|
+
...props
|
|
22
|
+
}: SortableProps) => {
|
|
23
|
+
const {
|
|
24
|
+
attributes: sortableAttributes,
|
|
25
|
+
isDragging,
|
|
26
|
+
isSorting,
|
|
27
|
+
listeners,
|
|
28
|
+
setNodeRef,
|
|
29
|
+
setActivatorNodeRef,
|
|
30
|
+
transform,
|
|
31
|
+
transition: sortableTransition,
|
|
32
|
+
} = useSortable({
|
|
33
|
+
id,
|
|
34
|
+
data,
|
|
35
|
+
disabled,
|
|
36
|
+
attributes,
|
|
37
|
+
transition,
|
|
38
|
+
resizeObserverConfig,
|
|
39
|
+
animateLayoutChanges,
|
|
40
|
+
getNewIndex,
|
|
41
|
+
strategy,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<Draggable.Root
|
|
46
|
+
{...props}
|
|
47
|
+
{...sortableAttributes}
|
|
48
|
+
ref={setNodeRef}
|
|
49
|
+
activatorNodeRef={setActivatorNodeRef}
|
|
50
|
+
isDragging={isDragging}
|
|
51
|
+
listeners={listeners}
|
|
52
|
+
transform={transform}
|
|
53
|
+
style={{
|
|
54
|
+
...style,
|
|
55
|
+
transform: CSS.Transform.toString(transform),
|
|
56
|
+
transition: sortableTransition,
|
|
57
|
+
}}
|
|
58
|
+
className={cn(className, getSortableClasses({ isDragging, isSorting }))}
|
|
59
|
+
data-draggable={true}
|
|
60
|
+
data-draggable-active={isDragging || undefined}
|
|
61
|
+
>
|
|
62
|
+
{children}
|
|
63
|
+
</Draggable.Root>
|
|
64
|
+
);
|
|
65
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Sortable } from "./Sortable";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { UseSortableArguments } from "@dnd-kit/sortable";
|
|
2
|
+
import type { CSSProperties } from "react";
|
|
3
|
+
import type { DraggableAxis, DraggableRootProps } from "../Draggable/types";
|
|
4
|
+
|
|
5
|
+
export type SortableProps = Omit<React.HTMLAttributes<HTMLDivElement>, "id"> &
|
|
6
|
+
UseSortableArguments &
|
|
7
|
+
Pick<DraggableRootProps, "handle" | "actions"> &
|
|
8
|
+
Partial<{
|
|
9
|
+
axis: DraggableAxis;
|
|
10
|
+
style: CSSProperties;
|
|
11
|
+
}>;
|
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
MenubarContentProps,
|
|
9
9
|
MenubarItemProps,
|
|
10
10
|
MenubarLabelProps,
|
|
11
|
+
MenubarMenuProps,
|
|
11
12
|
MenubarProps,
|
|
12
13
|
MenubarRadioItemProps,
|
|
13
14
|
MenubarSeparatorProps,
|
|
@@ -36,6 +37,9 @@ const Menubar = ({ className, ...props }: MenubarProps) => (
|
|
|
36
37
|
);
|
|
37
38
|
Menubar.displayName = MenubarPrimitive.Root.displayName;
|
|
38
39
|
|
|
40
|
+
const MenubarMenu = (props: MenubarMenuProps) => <MenubarPrimitive.Menu {...props} />;
|
|
41
|
+
MenubarMenu.displayName = MenubarPrimitive.Menu.displayName;
|
|
42
|
+
|
|
39
43
|
const MenubarTrigger = forwardRef<
|
|
40
44
|
React.ElementRef<typeof MenubarPrimitive.Trigger>,
|
|
41
45
|
MenubarTriggerProps
|
|
@@ -169,7 +173,7 @@ const MenubarShortcut = ({ className, ...props }: MenubarShortcutProps) => {
|
|
|
169
173
|
};
|
|
170
174
|
MenubarShortcut.displayname = "MenubarShortcut";
|
|
171
175
|
|
|
172
|
-
Menubar.Menu =
|
|
176
|
+
Menubar.Menu = MenubarMenu;
|
|
173
177
|
Menubar.Group = MenubarPrimitive.Group;
|
|
174
178
|
Menubar.Portal = MenubarPrimitive.Portal;
|
|
175
179
|
Menubar.Sub = MenubarPrimitive.Sub;
|
|
@@ -15,6 +15,8 @@ import {
|
|
|
15
15
|
|
|
16
16
|
export type MenubarProps = React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root>;
|
|
17
17
|
|
|
18
|
+
export type MenubarMenuProps = React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Menu>;
|
|
19
|
+
|
|
18
20
|
export type MenubarTriggerProps = React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Trigger> &
|
|
19
21
|
VariantProps<typeof getMenubarTriggerClasses>;
|
|
20
22
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Sortable } from "../DragAndDrop";
|
|
4
|
+
import { useWidget } from "./hooks";
|
|
5
|
+
import { WidgetProps } from "./types";
|
|
6
|
+
|
|
7
|
+
export const Widget = ({ id, children, actions, ...props }: WidgetProps) => {
|
|
8
|
+
const { widget, deleteWidget } = useWidget(id);
|
|
9
|
+
|
|
10
|
+
if (!widget) return null;
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<Sortable id={id} actions={(args) => actions?.({ deleteWidget, ...args })} {...props}>
|
|
14
|
+
{children}
|
|
15
|
+
</Sortable>
|
|
16
|
+
);
|
|
17
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { MoveIcon, TrashIcon } from "@radix-ui/react-icons";
|
|
3
|
+
import { Draggable } from "../DragAndDrop";
|
|
4
|
+
import { useWidgets } from "./hooks/useWidgets/useWidgets";
|
|
5
|
+
import { Widget } from "./Widget";
|
|
6
|
+
import { WidgetsContext } from "./WidgetsContext";
|
|
7
|
+
|
|
8
|
+
export const WidgetsExample = () => {
|
|
9
|
+
return (
|
|
10
|
+
<WidgetsContext
|
|
11
|
+
initialWidgets={[
|
|
12
|
+
{ id: "A" },
|
|
13
|
+
{ id: "B" },
|
|
14
|
+
{ id: "C" },
|
|
15
|
+
{ id: "D" },
|
|
16
|
+
{ id: "E" },
|
|
17
|
+
{ id: "F" },
|
|
18
|
+
{ id: "G" },
|
|
19
|
+
]}
|
|
20
|
+
>
|
|
21
|
+
<Widgets />
|
|
22
|
+
</WidgetsContext>
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const Widgets = () => {
|
|
27
|
+
const [widgetId, setWidgetId] = useState<string>("");
|
|
28
|
+
const [insertionIndex, setInsertionIndex] = useState<number>();
|
|
29
|
+
const { widgets, addWidget, addWidgets, deleteWidget, deleteWidgets, resetWidgets } =
|
|
30
|
+
useWidgets();
|
|
31
|
+
|
|
32
|
+
const widgetIds = widgetId.split(",").map((id) => ({
|
|
33
|
+
id,
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div className="relative h-[calc(100dvh-50px)] border border-base-200 rounded-md p-6 overflow-y-auto">
|
|
38
|
+
<div className="flex flex-col gap-2 mb-6">
|
|
39
|
+
<span className="text-sm">Controls:</span>
|
|
40
|
+
<div className="flex gap-2">
|
|
41
|
+
<input
|
|
42
|
+
type="text"
|
|
43
|
+
placeholder="Enter Widget ID"
|
|
44
|
+
className="input input-xs input-bordered rounded-md"
|
|
45
|
+
value={widgetId}
|
|
46
|
+
onChange={(e) => setWidgetId(e.target.value)}
|
|
47
|
+
/>
|
|
48
|
+
<input
|
|
49
|
+
type="number"
|
|
50
|
+
placeholder="Enter Insertion Index"
|
|
51
|
+
className="input input-xs input-bordered rounded-md"
|
|
52
|
+
value={insertionIndex}
|
|
53
|
+
onChange={(e) => setInsertionIndex(Number(e.target.value))}
|
|
54
|
+
/>
|
|
55
|
+
</div>
|
|
56
|
+
<div className="flex gap-2">
|
|
57
|
+
<button
|
|
58
|
+
className="btn btn-outline btn-xs btn-success"
|
|
59
|
+
onClick={() => addWidget(widgetIds[0], insertionIndex)}
|
|
60
|
+
>
|
|
61
|
+
Add widget
|
|
62
|
+
</button>
|
|
63
|
+
<button
|
|
64
|
+
className="btn btn-outline btn-xs btn-info"
|
|
65
|
+
onClick={() => addWidgets(widgetIds, insertionIndex)}
|
|
66
|
+
>
|
|
67
|
+
Add widgets
|
|
68
|
+
</button>
|
|
69
|
+
<button
|
|
70
|
+
className="btn btn-outline btn-xs btn-warning"
|
|
71
|
+
onClick={() => deleteWidget(widgetIds[0])}
|
|
72
|
+
>
|
|
73
|
+
Delete widget
|
|
74
|
+
</button>
|
|
75
|
+
<button
|
|
76
|
+
className="btn btn-outline btn-xs btn-error"
|
|
77
|
+
onClick={() => deleteWidgets(widgetIds)}
|
|
78
|
+
>
|
|
79
|
+
Delete widgets
|
|
80
|
+
</button>
|
|
81
|
+
<button className="btn btn-outline btn-xs" onClick={() => resetWidgets()}>
|
|
82
|
+
Reset widgets
|
|
83
|
+
</button>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
<div className="grid gap-4 grid-cols-3 w-fit h-fit">
|
|
87
|
+
{widgets.map((widget) => (
|
|
88
|
+
<Widget
|
|
89
|
+
key={widget.id}
|
|
90
|
+
id={widget.id}
|
|
91
|
+
actions={({ draggable, deleteWidget }) => (
|
|
92
|
+
<div className="absolute top-1 right-1 opacity-0 flex gap-1 [&>*]:cursor-pointer [&_svg]:size-4 pointer-events-none transition-opacity duration-300 hover:opacity-100 group-hover:pointer-events-auto">
|
|
93
|
+
<Draggable.Action
|
|
94
|
+
ref={draggable?.ref}
|
|
95
|
+
{...draggable?.listeners}
|
|
96
|
+
aria-label={`Drag widget ${widget.id}`}
|
|
97
|
+
className="transition-all hover:text-primary active:cursor-grabbing"
|
|
98
|
+
>
|
|
99
|
+
<MoveIcon />
|
|
100
|
+
</Draggable.Action>
|
|
101
|
+
<Draggable.Action
|
|
102
|
+
onClick={deleteWidget}
|
|
103
|
+
aria-label={`Delete widget ${widget.id}`}
|
|
104
|
+
className="transition-all hover:text-error"
|
|
105
|
+
>
|
|
106
|
+
<TrashIcon />
|
|
107
|
+
</Draggable.Action>
|
|
108
|
+
</div>
|
|
109
|
+
)}
|
|
110
|
+
className="group flex items-center justify-center w-32 h-32 bg-base-100 border border-primary-content/30 rounded-md text-sm p-2 cursor-default data-[draggable]:shadow data-[draggable-active]:z-20"
|
|
111
|
+
>
|
|
112
|
+
{widget.id}
|
|
113
|
+
</Widget>
|
|
114
|
+
))}
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
);
|
|
118
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { useRef, useState } from "react";
|
|
2
|
+
import { closestCenter, DndContext } from "@dnd-kit/core";
|
|
3
|
+
import { arrayMove, SortableContext } from "@dnd-kit/sortable";
|
|
4
|
+
import { InternalWidgetsContext } from "./InternalWidgetsContext";
|
|
5
|
+
import { restrictToElement } from "./modifiers";
|
|
6
|
+
import { Widget, WidgetsContextProps } from "./types";
|
|
7
|
+
|
|
8
|
+
export const WidgetsContext = ({ children, initialWidgets }: WidgetsContextProps) => {
|
|
9
|
+
const [activeId, setActiveId] = useState<Widget["id"] | null>(null);
|
|
10
|
+
const [widgets, setWidgets] = useState<Widget[]>(initialWidgets ?? []);
|
|
11
|
+
const boundingRef = useRef<HTMLDivElement>(null);
|
|
12
|
+
const boundingRect = boundingRef.current?.getBoundingClientRect();
|
|
13
|
+
|
|
14
|
+
const activeIndex = activeId ? widgets.findIndex((item) => item.id === activeId) : -1;
|
|
15
|
+
|
|
16
|
+
const addWidget = (widget: Widget, insertIndex?: number) => {
|
|
17
|
+
const isValid = Boolean(widget.id);
|
|
18
|
+
const isExist = widgets.some((w) => w.id === widget.id);
|
|
19
|
+
|
|
20
|
+
if (!isExist && isValid) {
|
|
21
|
+
// if there's no existing widget in the list, then add it at the given index or at the end of the list
|
|
22
|
+
setWidgets((widgets) => widgets.toSpliced(insertIndex ?? Infinity, 0, widget));
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const addWidgets = (_widgets: Widget[], startIndex?: number) => {
|
|
27
|
+
setWidgets((widgets) => {
|
|
28
|
+
// filter widgets that do NOT already exist do avoid duplicate IDs
|
|
29
|
+
const newWidgets = _widgets.filter(
|
|
30
|
+
(widget) => widget.id && !widgets.some((w) => w.id === widget.id)
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
// insert new widgets at the given start index or at the end of the list
|
|
34
|
+
return widgets.toSpliced(startIndex ?? Infinity, 0, ...newWidgets);
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const deleteWidget = (widget: Widget) => {
|
|
39
|
+
const widgetIndex = widgets.findIndex((w) => w.id === widget.id);
|
|
40
|
+
|
|
41
|
+
if (widgetIndex >= 0) {
|
|
42
|
+
// if the widget to be deleted exists in the list, then delete it.
|
|
43
|
+
setWidgets((widgets) => widgets.toSpliced(widgetIndex, 1));
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const deleteWidgets = (_widgets: Widget[]) => {
|
|
48
|
+
setWidgets((widgets) => {
|
|
49
|
+
// filter widgets that do NOT exist in the deletion list
|
|
50
|
+
return widgets.filter((widget) => !_widgets.some((w) => w.id === widget.id));
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const resetWidgets = () => {
|
|
55
|
+
setWidgets(initialWidgets ?? []);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<DndContext
|
|
60
|
+
modifiers={[restrictToElement(boundingRect)]}
|
|
61
|
+
collisionDetection={closestCenter}
|
|
62
|
+
onDragStart={({ active }) => {
|
|
63
|
+
if (!active) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
setActiveId(active.id);
|
|
68
|
+
}}
|
|
69
|
+
onDragEnd={({ over }) => {
|
|
70
|
+
setActiveId(null);
|
|
71
|
+
|
|
72
|
+
if (over) {
|
|
73
|
+
const overIndex = widgets.findIndex((item) => item.id === over.id);
|
|
74
|
+
if (activeIndex !== overIndex) {
|
|
75
|
+
setWidgets((w) => arrayMove(w, activeIndex, overIndex));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}}
|
|
79
|
+
onDragCancel={() => setActiveId(null)}
|
|
80
|
+
>
|
|
81
|
+
<SortableContext items={widgets}>
|
|
82
|
+
<InternalWidgetsContext.Provider
|
|
83
|
+
value={{
|
|
84
|
+
widgets,
|
|
85
|
+
addWidget,
|
|
86
|
+
addWidgets,
|
|
87
|
+
deleteWidget,
|
|
88
|
+
deleteWidgets,
|
|
89
|
+
resetWidgets,
|
|
90
|
+
}}
|
|
91
|
+
>
|
|
92
|
+
<div ref={boundingRef}>{children}</div>
|
|
93
|
+
</InternalWidgetsContext.Provider>
|
|
94
|
+
</SortableContext>
|
|
95
|
+
</DndContext>
|
|
96
|
+
);
|
|
97
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useWidget } from "./useWidget";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { useContext } from "react";
|
|
2
|
+
import { UniqueIdentifier } from "@dnd-kit/core";
|
|
3
|
+
import { InternalWidgetsContext } from "../../InternalWidgetsContext";
|
|
4
|
+
|
|
5
|
+
export const useWidget = (id: UniqueIdentifier) => {
|
|
6
|
+
const context = useContext(InternalWidgetsContext);
|
|
7
|
+
|
|
8
|
+
if (!context) {
|
|
9
|
+
throw new Error("useWidget must be used within a <WidgetsContext />");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const widget = context.widgets.find((widget) => widget.id === id);
|
|
13
|
+
|
|
14
|
+
const deleteWidget = () => {
|
|
15
|
+
if (widget) {
|
|
16
|
+
context.deleteWidget(widget);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
return { widget, deleteWidget };
|
|
21
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useWidgets } from "./useWidgets";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useContext } from "react";
|
|
2
|
+
import { InternalWidgetsContext } from "../../InternalWidgetsContext";
|
|
3
|
+
|
|
4
|
+
export const useWidgets = () => {
|
|
5
|
+
const context = useContext(InternalWidgetsContext);
|
|
6
|
+
|
|
7
|
+
if (!context) {
|
|
8
|
+
throw new Error("useWidgets must be used within a <WidgetsContext />");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return context;
|
|
12
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { restrictToElement } from "./restrictToElement";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type Modifier } from "@dnd-kit/core";
|
|
2
|
+
import { restrictToParentElement } from "@dnd-kit/modifiers";
|
|
3
|
+
|
|
4
|
+
export const restrictToElement = (rect: DOMRect | null | undefined) => {
|
|
5
|
+
const modifier: Modifier = (args) =>
|
|
6
|
+
restrictToParentElement({ ...args, containerNodeRect: rect ?? null });
|
|
7
|
+
return modifier;
|
|
8
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Data, UniqueIdentifier } from "@dnd-kit/core";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
import { type DraggableActionsArgs } from "../DragAndDrop/Draggable/types";
|
|
4
|
+
import { type SortableProps } from "../DragAndDrop/Sortable/types";
|
|
5
|
+
|
|
6
|
+
export type Widget = Data<{ id: UniqueIdentifier }>;
|
|
7
|
+
|
|
8
|
+
type WidgetActions = (
|
|
9
|
+
args: Partial<DraggableActionsArgs> & {
|
|
10
|
+
deleteWidget: () => void;
|
|
11
|
+
}
|
|
12
|
+
) => ReactNode;
|
|
13
|
+
|
|
14
|
+
export type WidgetProps = Omit<SortableProps, "actions"> & {
|
|
15
|
+
actions?: WidgetActions;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type WidgetsContextProps = {
|
|
19
|
+
children?: ReactNode;
|
|
20
|
+
initialWidgets?: Widget[];
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type InternalWidgetsContextProps = {
|
|
24
|
+
widgets: Data<{ id: UniqueIdentifier }>[];
|
|
25
|
+
addWidget: (widget: Widget, insertIndex?: number) => void;
|
|
26
|
+
addWidgets: (widgets: Widget[], startIndex?: number) => void;
|
|
27
|
+
deleteWidget: (widget: Widget) => void;
|
|
28
|
+
deleteWidgets: (widgets: Widget[]) => void;
|
|
29
|
+
resetWidgets: () => void;
|
|
30
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -39,8 +39,10 @@ export * from "./components/Tooltip";
|
|
|
39
39
|
|
|
40
40
|
/** composable */
|
|
41
41
|
export * from "./components/AlertDialog";
|
|
42
|
+
export * from "./components/Chart";
|
|
42
43
|
export * from "./components/Command";
|
|
43
44
|
export * from "./components/Dialog";
|
|
44
45
|
export * from "./components/HoverCard";
|
|
45
46
|
export * from "./components/Popover";
|
|
46
47
|
export * from "./components/Slot";
|
|
48
|
+
export * from "./components/Widget";
|