containerRef.current,
+ }}
+ >
+ {
+ setSelectedItem(null);
+ }}
+ >
+ {layouts.map((layout) => (
+ {
+ showShadow({
+ x,
+ y,
+ w,
+ h,
+ });
+ document.body.style.userSelect = 'none';
+ }}
+ onLayoutChangeEnd={(newLayout) => {
+ hideShadow();
+ onLayoutsChange?.(
+ layouts.map((layout) => (layout.id === newLayout.id ? newLayout : layout))
+ );
+ document.body.style.userSelect = 'auto';
+ }}
+ selected={selectedItem === layout.id}
+ onSelectedChange={(selected) => {
+ if (selected) return setSelectedItem(layout.id);
+
+ setSelectedItem(null);
+ }}
+ onDeleted={removeSelectedItem}
+ />
+ ))}
+
+
+
+ );
+};
diff --git a/src/components/LayoutItemComponent.tsx b/src/components/LayoutItemComponent.tsx
new file mode 100644
index 0000000..e5f0fd1
--- /dev/null
+++ b/src/components/LayoutItemComponent.tsx
@@ -0,0 +1,248 @@
+import React, {
+ FC,
+ ReactElement,
+ Dispatch,
+ PropsWithChildren,
+ SetStateAction,
+ cloneElement,
+ createContext,
+ useContext,
+ useEffect,
+ useId,
+ useMemo,
+ useRef,
+ useState,
+} from 'react';
+import { useMemoizedFn } from 'ahooks';
+import styled from '@emotion/styled';
+import { CloseButton, IconButton } from '@chakra-ui/react';
+import { BsTrash3Fill } from 'react-icons/bs';
+import { GridLayoutContext, Layout, mutateLayout } from '@/components/GridLayout';
+
+interface LayoutItemComponentProps {
+ layout: Layout;
+ render: (layout: Layout) => ReactElement;
+ draggable?: boolean;
+ resizable?: boolean;
+ onLayoutChange?: (newLayout: Layout) => void;
+ onLayoutChangeEnd?: (newLayout: Layout) => void;
+ selected?: boolean;
+ onSelectedChange?: (selected: boolean) => void;
+ onDeleted?: () => void;
+}
+
+const LayoutWrapper = styled('div', {
+ shouldForwardProp: (propName) => !['draggable', 'layout'].includes(propName),
+})<{
+ layout: Layout;
+ draggable: boolean;
+ selected?: boolean;
+}>`
+ position: relative;
+ grid-area: ${({ layout }) =>
+ `${layout.y + 1}/${layout.x + 1}/${layout.y + layout.h + 1}/${layout.x + layout.w + 1}`};
+ cursor: ${({ draggable, selected }) => (draggable && selected ? 'move' : 'inherit')};
+ // ${({ selected }) => selected && 'filter: drop-shadow(#0bc5ea 0px 0px 1px);'};
+ ${({ selected }) => selected && 'box-shadow: 1px 1px 1px #0bc5ea, -1px -1px 1px #0bc5ea;'};
+`;
+
+const DeleteButtonAnchor = styled.div`
+ position: absolute;
+ right: 0;
+ top: 0;
+ width: 32px;
+ height: 32px;
+ z-index: 1;
+`;
+
+const ResizeHandle = styled.div`
+ position: absolute;
+ right: 0px;
+ bottom: 0px;
+ border: 0px solid black;
+ border-right-width: 5px;
+ border-bottom-width: 5px;
+ width: 15px;
+ height: 15px;
+ cursor: se-resize;
+`;
+
+const LayoutItemComponent: FC