Skip to content

Commit

Permalink
Merge pull request #201 from MovieReviewComment/feature/issue-162/imp…
Browse files Browse the repository at this point in the history
…rove-userchip

[#162] Improve UserChip
  • Loading branch information
2wheeh authored Feb 25, 2024
2 parents e6cd3d3 + a150bcc commit 00bfe66
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 48 deletions.
39 changes: 39 additions & 0 deletions ui/src/components/atomic/chip-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
'use client';

import clsx from 'clsx';
import { ReactNode } from 'react';

// TODO: make it generic
export default function ChipButton({
onClick,
Text,
Icon,
rounded,
type = 'button',
width,
}: {
Text: ReactNode;
Icon?: ReactNode;
onClick?: () => void;
rounded: 'full' | 'lg';
type?: 'button' | 'submit';
width: 'fit' | 'full';
}) {
const roundClass = clsx({ 'rounded-lg': rounded === 'lg', 'rounded-full': rounded === 'full' });
const widthClass = clsx({ 'w-full': width === 'full', 'w-fit': width === 'fit' });

return (
<button
className={clsx(
'flex items-center space-x-1 px-2 py-1 hover:bg-gray-100',
roundClass,
widthClass
)}
onClick={onClick}
type={type}
>
{Icon}
{Text}
</button>
);
}
2 changes: 1 addition & 1 deletion ui/src/components/review-board-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Link from 'next/link';
import { useState } from 'react';

export function ReviewBoardHeader() {
const [isSearching, setIsSearching] = useState(false);
const [isSearching, setIsSearching] = useState(false); // TODO: initialize to true when query is set

return (
<BoardHeader>
Expand Down
39 changes: 2 additions & 37 deletions ui/src/components/search-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,49 +7,14 @@ import {
ArrowsRightLeftIcon,
} from '@heroicons/react/24/outline';

import { ChangeEventHandler, ReactNode } from 'react';
import clsx from 'clsx';
import { ChangeEventHandler } from 'react';

import Text from '@/components/atomic/text';
import { useSearchFormState } from '@/hooks/use-search-form-state';
import { useDropdown } from '@/hooks/use-dropdown';
import { FiltersProvider, useFilters } from '@/context/filters-context';
import { useSearchParams } from 'next/navigation';

// TODO: make it generic and seperate
function ChipButton({
onClick,
Text,
Icon,
rounded,
type = 'button',
width,
}: {
Text: ReactNode;
Icon?: ReactNode;
onClick?: () => void;
rounded: 'full' | 'lg';
type?: 'button' | 'submit';
width: 'fit' | 'full';
}) {
const roundClass = clsx({ 'rounded-lg': rounded === 'lg', 'rounded-full': rounded === 'full' });
const widthClass = clsx({ 'w-full': width === 'full', 'w-fit': width === 'fit' });

return (
<button
className={clsx(
'flex items-center space-x-1 px-2 py-1 hover:bg-gray-100',
roundClass,
widthClass
)}
onClick={onClick}
type={type}
>
{Icon}
{Text}
</button>
);
}
import ChipButton from '@/components/atomic/chip-button';

function FilterInput({ filter }: { filter: string }) {
const {
Expand Down
39 changes: 33 additions & 6 deletions ui/src/components/user-chip.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
import { within, userEvent, expect } from '@storybook/test';

import UserChip from '@/components/user-chip';
import { action } from '@storybook/addon-actions';

const meta = {
title: 'Common/UserChip',
Expand All @@ -16,14 +17,30 @@ export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
parameters: {
nextjs: {
appDirectory: true,
navigation: {
push(...args: string[]) {
alert(`router pushes to: ${args[0]}`);

// eslint-disable-next-line @typescript-eslint/no-unsafe-call
action('nextNavigation.push')(...args);

return Promise.resolve(true);
},
},
},
},

args: {
nickname: '신비로운 평론가 붉은 여우',
tag: '#MQ3B',
},

decorators: [
(Story) => (
<div className="flex items-center justify-center p-10">
<div className="flex items-center justify-center p-14">
<div className="fixed inset-0 bg-emerald-100 text-center hover:bg-emerald-200">outside</div>
<Story />
</div>
Expand All @@ -33,23 +50,33 @@ export const Default: Story = {
play: async ({ canvasElement, step }) => {
const canvas = within(canvasElement);
const element = canvas.getByTestId('user-chip');
let more: HTMLElement;
let moreReview: HTMLElement;
let moreComment: HTMLElement;
let outside: HTMLElement;

await expect(element).toBeInTheDocument();

await step('should open dropdown on click element', async () => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
await userEvent.click(element);
more = canvas.getByText('작성글 보기');
await expect(more).toBeInTheDocument();

moreReview = canvas.getByText('작성 리뷰');
await expect(moreReview).toBeInTheDocument();

moreComment = canvas.getByText('작성 코멘트');
await expect(moreComment).toBeInTheDocument();
});

await step('shoulc close dropdown on click outside', async () => {
// TODO: test actions programatically - 현재 storybook의 first class 지원은 없음
// https://stackoverflow.com/questions/77240899/how-can-i-assert-that-a-storybook-action-was-fired

await step('should close dropdown on click outside', async () => {
outside = canvas.getByText('outside');
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
await userEvent.click(outside);
await expect(more).not.toBeInTheDocument();

await expect(moreReview).not.toBeInTheDocument();
await expect(moreComment).not.toBeInTheDocument();
});
},
};
21 changes: 17 additions & 4 deletions ui/src/components/user-chip.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
'use client';

import ChipButton from '@/components/atomic/chip-button';
import Text from '@/components/atomic/text';
import { useDropdown } from '@/hooks/use-dropdown';
import { useSearchMore } from '@/hooks/use-search-more';

export default function UserChip({ nickname, tag }: { nickname: string; tag: string }) {
const { targetRef, toggleDropdown, isDropdownOpen } = useDropdown<HTMLDivElement>();

const { saerchMoreReview, saerchMoreComment } = useSearchMore(nickname);

return (
<div ref={targetRef} className="relative">
<div
Expand All @@ -22,10 +26,19 @@ export default function UserChip({ nickname, tag }: { nickname: string; tag: str
</div>
{isDropdownOpen && (
// TODO: replace with a normalized Dropdown with a proper event handler
<div className="absolute right-0 top-7 flex items-center rounded-lg border bg-white px-2 py-1 opacity-70 hover:bg-gray-200">
<Text color="black" size="sm">
작성글 보기
</Text>
<div className="absolute left-0 top-7 z-10 flex-col items-center space-y-1 rounded-lg border bg-white p-2">
<ChipButton
onClick={saerchMoreReview}
Text={<Text size="sm">작성 리뷰</Text>}
rounded="lg"
width="full"
/>
<ChipButton
onClick={saerchMoreComment}
Text={<Text size="sm">작성 코멘트</Text>}
rounded="lg"
width="full"
/>
</div>
)}
</div>
Expand Down
16 changes: 16 additions & 0 deletions ui/src/hooks/use-search-more.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useRouter } from 'next/navigation';

// TODO: consider to make it more generic by using useSearchParams
export function useSearchMore(nickname: string) {
const params = new URLSearchParams();
params.set('nickname', nickname);

const createURL = (pathname: 'review' | 'comment') => `/${pathname}?${params.toString()}`;

const router = useRouter();

const saerchMoreReview = () => router.push(createURL('review'));
const saerchMoreComment = () => router.push(createURL('comment'));

return { saerchMoreReview, saerchMoreComment };
}

0 comments on commit 00bfe66

Please sign in to comment.