Skip to content

Commit

Permalink
Chakra UIをtailwindcssにリプレース (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
kyonenya authored Jun 17, 2023
1 parent 22f33fe commit a5b16fb
Show file tree
Hide file tree
Showing 27 changed files with 2,823 additions and 3,006 deletions.
90 changes: 39 additions & 51 deletions components/ArticleHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
import {
ArrowBackIcon,
ArrowLeftIcon,
ChevronDownIcon,
CheckIcon,
DeleteIcon,
} from '@chakra-ui/icons';
import {
Button,
IconButton,
Input,
Spinner,
useDisclosure,
} from '@chakra-ui/react';
TrashIcon,
} from '@heroicons/react/24/solid';
import Router from 'next/router';
import { useForm, useWatch } from 'react-hook-form';
import { Entry } from '../domain/Entry';
import dayjs from '../infra/dayjs';
import { Button } from './Button';
import { CustomAlertDialog } from './CustomAlertDialog';
import { CustomPopover } from './CustomPopover';
import { CustomSelect } from './CustomSelect';
import { HeaderContainer } from './HeaderContainer';
import { IconButton } from './IconButton';
import { Spinner } from './Spinner';

type Form = {
createdAt: string;
Expand All @@ -39,7 +35,6 @@ export const ArticleHeader = (props: {
},
});
const tags = useWatch({ name: 'tags', control });
const { isOpen, onOpen, onClose } = useDisclosure();

return (
<form
Expand All @@ -51,54 +46,47 @@ export const ArticleHeader = (props: {
})}
>
<HeaderContainer>
<IconButton
icon={<ArrowBackIcon />}
aria-label={'Back to Top'}
onClick={() => Router.push('/')}
size="md"
/>
<IconButton ariaLabel="Back to Top" onClick={() => Router.push('/')}>
<ArrowLeftIcon />
</IconButton>

<CustomPopover
triggerButton={
<Button rightIcon={<ChevronDownIcon />} fontWeight="normal">
<Button rightIcon={<ChevronDownIcon />}>
{dayjs(props.entry.createdAt).format('YYYY-MM-DD')}
</Button>
}
placement="bottom"
>
<Input type="datetime-local" {...register('createdAt')} />

<CustomSelect
value={tags}
onSelect={(tags) => setValue('tags', tags)}
options={props.tagList}
{...register('tags')}
/>

<Button onClick={onOpen} leftIcon={<DeleteIcon />} color="red.500">
Delete
</Button>
<CustomAlertDialog
isOpen={isOpen}
headerText="Delete Entry"
onClose={onClose}
onSubmit={() => {
props.onDelete();
onClose();
}}
/>
<div className="flex min-w-[300px] flex-col space-y-4">
<input
type="datetime-local"
{...register('createdAt')}
className="mx-auto h-10 w-full rounded-md border border-gray-300 p-2 dark:border-gray-700 dark:bg-gray-700 dark:text-gray-300"
/>
<CustomSelect
value={tags}
onSelect={(tags) => setValue('tags', tags)}
options={props.tagList}
{...register('tags')}
/>
<CustomAlertDialog
triggerButton={
<Button
leftIcon={<TrashIcon />}
className="font-semibold text-red-500 dark:text-rose-500"
>
Delete
</Button>
}
headerText="Delete Entry"
onSubmit={() => props.onDelete()}
/>
</div>
</CustomPopover>

<IconButton
type="submit"
icon={
props.isLoading ? (
<Spinner emptyColor="gray.300" speed="0.65s" />
) : (
<CheckIcon />
)
}
aria-label="更新"
/>
<IconButton type="submit" ariaLabel="更新">
{props.isLoading ? <Spinner /> : <CheckIcon />}
</IconButton>
</HeaderContainer>
</form>
);
Expand Down
35 changes: 10 additions & 25 deletions components/ArticlePage.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
import {
Box,
Container,
Stack,
Text,
useColorModeValue,
} from '@chakra-ui/react';
import dayjs from 'dayjs';
import { Entry } from '../domain/Entry';
import { ArticleHeader } from './ArticleHeader';
Expand All @@ -19,7 +12,7 @@ export const ArticlePage = (props: {
onUpdate: (props: { createdAt: string; tags: string[] }) => void;
}) => {
return (
<Box display="flex" flexDirection="column" minHeight="100vh">
<div className="flex min-h-screen flex-col">
<ArticleHeader
entry={props.entry}
tagList={props.tagList}
Expand All @@ -28,25 +21,17 @@ export const ArticlePage = (props: {
onDelete={props.onDelete}
/>

<Container maxW="3xl" px={0} py={4} flex={1}>
<Stack
bg={useColorModeValue('white', 'gray.800')}
boxShadow={'lg'}
p={6}
rounded={{ base: 'none', md: 'xl' }}
align={'left'}
pos={'relative'}
as="li"
>
<div className="container mx-auto max-w-3xl flex-1 px-0 py-4">
<div className="rounded-none bg-white p-6 text-gray-700 shadow-lg dark:bg-gray-800 dark:text-gray-300 md:rounded-xl">
<MarkdownText>{props.entry.text}</MarkdownText>
<Stack direction={'row'} spacing={3}>
<Text color="gray.500">
<div className="mt-3 flex flex-row space-x-4">
<p className="text-gray-600 dark:text-gray-400">
{dayjs(props.entry.createdAt).format('YYYY-MM-DD')}
</Text>
</p>
<Tags tags={props.entry.tags} />
</Stack>
</Stack>
</Container>
</Box>
</div>
</div>
</div>
</div>
);
};
50 changes: 50 additions & 0 deletions components/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import {
ButtonHTMLAttributes,
cloneElement,
forwardRef,
ReactElement,
ReactNode,
Ref,
} from 'react';
import { twMerge } from 'tailwind-merge';

/**
* Chakra UI の `Button` の移植
*
* @url https://chakra-ui.com/docs/components/button#button-with-icon
*/
const ButtonComponent = (
props: {
leftIcon?: ReactElement;
rightIcon?: ReactElement;
type?: ButtonHTMLAttributes<HTMLButtonElement>['type'];
className?: string;
onClick?: () => void | Promise<void>;
children?: ReactNode;
},
ref: Ref<HTMLButtonElement>
) => {
return (
<button
type={props.type ?? 'button'}
onClick={props.onClick}
className={twMerge(
'flex w-full items-center justify-center rounded-md bg-[#edf2f7] p-2 transition-colors duration-150 ease-in-out hover:bg-gray-200 focus:outline-none focus:ring-2 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600',
props.className
)}
ref={ref}
>
{props.leftIcon &&
cloneElement(props.leftIcon, {
className: 'mr-2 h-5 w-5',
})}
{props.children}
{props.rightIcon &&
cloneElement(props.rightIcon, {
className: 'ml-2 h-5 w-5',
})}
</button>
);
};

export const Button = forwardRef(ButtonComponent);
14 changes: 0 additions & 14 deletions components/ColorModeButton.tsx

This file was deleted.

74 changes: 39 additions & 35 deletions components/CustomAlertDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,52 @@
import {
AlertDialog,
AlertDialogBody,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogContent,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogAction,
AlertDialogOverlay,
Button,
} from '@chakra-ui/react';
import { useRef } from 'react';
AlertDialogPortal,
AlertDialogTrigger,
AlertDialogCancel,
} from '@radix-ui/react-alert-dialog';
import { ReactElement } from 'react';
import { Button } from './Button';

export const CustomAlertDialog = (props: {
isOpen: boolean;
headerText: string;
onClose: () => void;
triggerButton: ReactElement;
onSubmit: () => void;
}) => {
const cancelRef = useRef(null);

return (
<AlertDialog
isOpen={props.isOpen}
leastDestructiveRef={cancelRef}
onClose={props.onClose}
>
<AlertDialogOverlay>
<AlertDialogContent>
<AlertDialogHeader fontSize="lg" fontWeight="bold">
{props.headerText}
</AlertDialogHeader>

<AlertDialogBody>
Are you sure? You can not undo this action afterwards.
</AlertDialogBody>

<AlertDialogFooter>
<Button ref={cancelRef} onClick={props.onClose}>
Cancel
</Button>
<Button colorScheme="red" onClick={props.onSubmit} ml={3}>
Delete
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogOverlay>
<AlertDialog>
<AlertDialogTrigger asChild>{props.triggerButton}</AlertDialogTrigger>
<AlertDialogPortal>
<AlertDialogOverlay className="fixed inset-0 z-40 bg-black bg-opacity-50">
<AlertDialogContent className="z-50 mx-auto my-10 max-w-md rounded-md bg-white p-6 shadow-md dark:bg-gray-700">
<AlertDialogTitle className="mb-4 text-xl font-bold dark:text-gray-300">
{props.headerText}
</AlertDialogTitle>
<AlertDialogDescription className="mb-6 text-gray-600 dark:text-gray-300">
Are you sure? You cannot undo this action afterwards.
</AlertDialogDescription>
<div className="mt-4 flex justify-end space-x-4">
<AlertDialogCancel>
<Button className="px-4 dark:bg-gray-600 dark:hover:bg-gray-500">
Cancel
</Button>
</AlertDialogCancel>
<AlertDialogAction>
<Button
onClick={props.onSubmit}
className="bg-red-500 px-4 text-white hover:bg-red-600 dark:bg-red-600 dark:hover:bg-red-500"
>
Delete
</Button>
</AlertDialogAction>
</div>
</AlertDialogContent>
</AlertDialogOverlay>
</AlertDialogPortal>
</AlertDialog>
);
};
58 changes: 29 additions & 29 deletions components/CustomPopover.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
import {
Popover,
PopoverTrigger,
PopoverContent,
PopoverBody,
PopoverArrow,
PopoverCloseButton,
Placement,
Stack,
} from '@chakra-ui/react';
import { ReactElement, ReactNode } from 'react';
import { XMarkIcon } from '@heroicons/react/24/solid';
import { Root, Trigger, Content, Close } from '@radix-ui/react-popover';
import { IconButton } from './IconButton';

export const CustomPopover = (props: {
triggerButton: ReactElement;
children: ReactNode;
type Placement = 'top' | 'right' | 'bottom' | 'left';

type CustomPopoverProps = {
triggerButton: React.ReactNode;
children: React.ReactNode;
placement?: Placement;
}) => {
return (
<Popover placement={props.placement}>
<PopoverTrigger>{props.triggerButton}</PopoverTrigger>
<PopoverContent w={{ base: '90vw', md: 'sm' }}>
<PopoverArrow />
<PopoverCloseButton />
<PopoverBody>
<Stack direction="column" spacing={4} px={2} py={4}>
{props.children}
</Stack>
</PopoverBody>
</PopoverContent>
</Popover>
);
};

export const CustomPopover = ({
triggerButton,
children,
placement,
}: CustomPopoverProps) => (
<Root>
<Trigger className="cursor-pointer">{triggerButton}</Trigger>
<Content
side={placement}
className="transform-gpu rounded border border-gray-200 bg-white p-1 shadow-lg transition duration-200 ease-in-out dark:border-gray-600 dark:bg-gray-800"
>
<Close className="absolute right-0.5 top-0.5 cursor-pointer">
<IconButton className="h-6 w-6 rounded-full" iconClassName="w-5">
<XMarkIcon />
</IconButton>
</Close>
<div className="space-y-4 p-5">{children}</div>
</Content>
</Root>
);
Loading

1 comment on commit a5b16fb

@vercel
Copy link

@vercel vercel bot commented on a5b16fb Jun 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

manuscript – ./

manuscript-kyonenya.vercel.app
manuscript.vercel.app
manuscript-git-main-kyonenya.vercel.app

Please sign in to comment.