{content.title} {content.subtitle}
@@ -136,4 +112,4 @@ const Index: React.FC
= ({
);
};
-export default Index;
+export default Page;
diff --git a/src/components/ContentView/DescriptionView/index.tsx b/src/components/ContentView/DescriptionView/index.tsx
index 573bee7..b11b1da 100644
--- a/src/components/ContentView/DescriptionView/index.tsx
+++ b/src/components/ContentView/DescriptionView/index.tsx
@@ -1,4 +1,6 @@
-import React, { FC } from 'react';
+'use client';
+
+import { FC } from 'react';
import ReactMarkdown from 'react-markdown';
import styled from '@emotion/styled';
diff --git a/src/components/ContentView/ExternalLinkView/index.tsx b/src/components/ContentView/ExternalLinkView/index.tsx
index 922ad78..7950dfc 100644
--- a/src/components/ContentView/ExternalLinkView/index.tsx
+++ b/src/components/ContentView/ExternalLinkView/index.tsx
@@ -1,4 +1,6 @@
-import React, { FC } from 'react';
+'use client';
+
+import { FC } from 'react';
import styled from '@emotion/styled';
import { ExternalLink } from '../../Link/ExternalLink';
import { SchemaType } from '../../../types/type-util';
diff --git a/src/components/ContentView/ImageView/index.tsx b/src/components/ContentView/ImageView/index.tsx
index ed03990..c6b8e38 100644
--- a/src/components/ContentView/ImageView/index.tsx
+++ b/src/components/ContentView/ImageView/index.tsx
@@ -1,4 +1,6 @@
-import React, { FC, useState } from 'react';
+'use client';
+
+import { FC, useState } from 'react';
import styled from '@emotion/styled';
import Image from 'next/image';
import { useDisclosure } from '@chakra-ui/react';
diff --git a/src/components/Dialogs/DeleteModal.tsx b/src/components/Dialogs/DeleteModal.tsx
index ab1e3a1..37ca588 100644
--- a/src/components/Dialogs/DeleteModal.tsx
+++ b/src/components/Dialogs/DeleteModal.tsx
@@ -1,3 +1,5 @@
+'use client';
+
import React, { PropsWithChildren } from 'react';
import {
Button,
diff --git a/src/components/Dialogs/ImageDetailModal.tsx b/src/components/Dialogs/ImageDetailModal.tsx
index 42ed859..7e34563 100644
--- a/src/components/Dialogs/ImageDetailModal.tsx
+++ b/src/components/Dialogs/ImageDetailModal.tsx
@@ -1,5 +1,7 @@
+'use client';
+
import React from 'react';
-import { Modal, ModalBody, ModalCloseButton, ModalContent, ModalOverlay, useDisclosure } from '@chakra-ui/react';
+import { Modal, ModalBody, ModalContent, ModalOverlay, useDisclosure } from '@chakra-ui/react';
import Image from 'next/image';
import styled from '@emotion/styled';
import Colors from '../../styles/Colors';
@@ -9,11 +11,21 @@ interface ImageDetailModalProps {
readonly image: { url: string; alt: string };
}
+const StyledModalContent = styled(ModalContent)`
+ position: relative;
+ background-color: transparent;
+ box-shadow: none;
+`;
+
+const StyledImage = styled(Image)`
+ border-radius: 6px;
+`;
+
const ImageLabel = styled.p`
- font-size: 14px;
+ font-size: 16px;
text-align: center;
- padding: 8px 0;
- color: ${Colors.GRAY_DARKEN};
+ padding: 10px 0;
+ color: ${Colors.SECONDARY};
`;
const ImageDetailModal: React.FC = ({ modalController, image }) => {
@@ -22,13 +34,12 @@ const ImageDetailModal: React.FC = ({ modalController, im
return (
-
-
+
-
+
{image.alt}
-
+
);
};
diff --git a/src/components/Dialogs/ImageModal.tsx b/src/components/Dialogs/ImageModal.tsx
index 4f1b17b..c2f02fb 100644
--- a/src/components/Dialogs/ImageModal.tsx
+++ b/src/components/Dialogs/ImageModal.tsx
@@ -1,4 +1,6 @@
-import React, { useEffect, useState } from 'react';
+'use client';
+
+import React, { FC, useCallback, useEffect, useState } from 'react';
import {
Button,
Divider,
@@ -16,14 +18,15 @@ import {
} from '@chakra-ui/react';
import { SubmitHandler, useForm } from 'react-hook-form';
import styled from '@emotion/styled';
-
import { RiArrowRightLine } from '@remixicon/react';
-import { GridContextProvider, GridDropZone, GridItem, swap } from 'react-grid-dnd';
import Image from 'next/image';
+import { arrayMove, rectSortingStrategy, SortableContext, useSortable } from '@dnd-kit/sortable';
+import { CSS } from '@dnd-kit/utilities';
+import { closestCenter, DndContext, DragEndEvent } from '@dnd-kit/core';
import { SchemaType } from '../../types/type-util';
-import { useSupabase } from '../../utils/supabase';
import { Space } from '../Space';
import Colors from '../../styles/Colors';
+import { createSupabaseClient } from '../../utils/supabase/client';
const InputContainer = styled.div`
display: flex;
@@ -37,16 +40,10 @@ const StyledFileInput = styled(Input)`
padding-top: 5px;
`;
-const DropzoneContainer = styled.div`
- display: flex;
- touch-action: none;
- width: 100%;
- height: 100%;
-`;
-
-const StyledDropzone = styled(GridDropZone)`
- flex: 1;
- height: 300px;
+const GridContainer = styled.div`
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 6px;
`;
const StyledImage = styled(Image)`
@@ -77,23 +74,55 @@ interface AddForm {
readonly alt: string;
}
-interface LinkModalProps {
+interface ImageModalProps {
readonly modalController: ReturnType;
readonly dataId: number;
}
-const ImageModal: React.FC = ({ modalController, dataId }) => {
+interface ImageItemProps {
+ readonly onDeleteClick: (id: number) => void;
+ readonly imageId: number;
+ readonly imageUrl: string;
+ readonly alt: string;
+}
+
+const ImageItem: FC = ({ onDeleteClick, imageId, imageUrl, alt }) => {
+ const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id: imageId });
+
+ const style = {
+ transform: CSS.Transform.toString(transform),
+ transition
+ };
+
+ return (
+ {
+ e.preventDefault();
+ onDeleteClick(imageId);
+ }}
+ {...attributes}
+ {...listeners}
+ >
+ e.preventDefault()} />
+
+ );
+};
+
+const ImageModal: React.FC = ({ modalController, dataId }) => {
const [data, setData] = useState[]>([]);
const { isOpen, onClose } = modalController;
const { register, handleSubmit, reset } = useForm();
- const supabase = useSupabase();
+ const supabase = createSupabaseClient();
const toast = useToast({
isClosable: true,
position: 'top-left'
});
- const fetchData = async (id: number) => {
- if (id > 0) {
+ const fetchData = useCallback(
+ async (id: number) => {
+ if (id <= 0) return;
const { data: contents, error } = await supabase.from('contents').select('*, images(*)').match({ id });
if (error !== null) {
toast({
@@ -107,12 +136,17 @@ const ImageModal: React.FC = ({ modalController, dataId }) => {
const content = contents[0];
setData(content.images.sort((a, b) => a.order - b.order));
}
- }
- };
+ },
+ [supabase]
+ );
+
+ const onChangeData = ({ active, over }: DragEndEvent) => {
+ if (!over || active.id === over.id) return;
+
+ const oldIndex = data.findIndex((item) => item.id === active.id);
+ const newIndex = data.findIndex((item) => item.id === over.id);
- const onChangeData = (sourceId: string, sourceIndex: number, targetIndex: number) => {
- const nextState = swap(data, sourceIndex, targetIndex);
- setData(nextState);
+ setData((prev) => arrayMove(prev, oldIndex, newIndex));
};
const onAddClick: SubmitHandler = async ({ file, alt }) => {
@@ -164,7 +198,7 @@ const ImageModal: React.FC = ({ modalController, dataId }) => {
useEffect(() => {
fetchData(dataId).then();
- }, [dataId]);
+ }, [fetchData, dataId]);
return (
@@ -187,29 +221,15 @@ const ImageModal: React.FC = ({ modalController, dataId }) => {
-
-
-
- {data.map((image) => (
- {
- e.preventDefault();
- onDeleteClick(image.id);
- }}
- >
- e.preventDefault()}
- />
-
+
+
+
+ {data.map(({ id, image_url: imageUrl, alt }) => (
+
))}
-
-
-
+
+
+