Skip to content

Commit

Permalink
Feat: 기술스택 팝오버 구현 (#80)
Browse files Browse the repository at this point in the history
* Chore: STACKS 상수 배열 생성

* Feat: Stack 및 Stack을 사용하는 Position Type 구현

* Fix: StackType 수정

* Feat: Image관련 타입 구현

* Chore: Stack 데이터 변경 및 STACK_NAMES만 모은 변수 생성

Stack_names는 stack 이름이 모두 저장된 배열, mock 데이터같이 쓸 수 있음

* Refactor: ICONS 변수 Images 타입 할당

* Chore: stackIcons.ts 삭제

* Fix: Stack에 맞게 icon 출력 수정 및 style

* Fix: 변경된 Stack에 따라 Stacks 컴포넌트 수정

* Fix: FilterButton 클릭시 화살표 방향 뒤집어짐

* Feat: FilterList 컴포넌트 디자인 기본 구현

* Feat: FilterList내 개별 Filter 디자인 추가

* Feat: SelectLayout 컴포넌트 기본 디자인 세팅

* Feat: StackPopover 컴포넌트 기본 구현

* Chore: reset.svg 추가

* Feat: ResetButton 구현

* Chore: 코드 여백 수정

* Fix: 스타일드 상속을 위해 className 프롭 생성

* Fix: StackPopover 컴포넌트 position 변경

* Fix: DefaultChip 이미지부분 Stack 컴포넌트로 대체

* Fix: Stack 컴포넌트 prop 변경

* Feat: StackChipGrid 구현

* Fix: FilterList filter 외부에서 관리

* Fix: StackChip 잘못된 너비 수정

* Feat: DefaultChip 디자인 수정

* Feat: StackChip 디자인 수정

* Feat: Stack 컴포넌트 확장 가능하게 변경

* Fix: FilterList 범용성있게 원복

* Fix: Chip overflow 담당 변경

* Fix: DeleteStackChip onClick으로 prop넘긴 오류 수정

* Feat: DeleteStackChipList 컴포넌트 구현

* Chore: StackChipGrid -> StackChipList 이름 변경

* Refactor: StacksPopover toogle로 상태 관리

* Chore: StacksPopover에 사용되는 constant 파일 생성

* Chore: StackPopover 폴더 위치 변경

* Fix: DefaultChip title 툴팁 추가

* Chore: DetailCard, FilterDropButton 폴더 위치 및 파일 이름 변경

* Refactor: StackPopover 컴포넌트 변경된 하위 컴포넌트 이름 반영

* Feat: FilterValue에서 FilterKey가지고 오는 함수 구현

* Chore: Design_Token 값 추가 및 반영

* Refactor: filter key가지고 오는 메소드 적용 및 css 조정

* Fix: SelectLayout width css 수정

* Feat: StackPopover 폴더 위치 변경 후 값 변경, getFilterKey Util함수 생성
  • Loading branch information
nakyoung98 authored Mar 16, 2024
1 parent 9aea4eb commit 966a43b
Show file tree
Hide file tree
Showing 27 changed files with 580 additions and 85 deletions.
7 changes: 7 additions & 0 deletions co-kkiri/src/assets/icons/reset.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
57 changes: 38 additions & 19 deletions co-kkiri/src/components/commons/Chips/DefaultChip.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { MouseEvent } from "react";
import DESIGN_TOKEN from "@/styles/tokens";
import styled from "styled-components";
import Stack from "../Stack";

interface Icon {
src: string;
Expand All @@ -19,10 +20,25 @@ interface DefaultChipProps {
className?: string;
}

export default function DefaultChip({ label, imgUrl, icon, isSelected, onClick, onIconClick, isVertical, className }: DefaultChipProps) {
export default function DefaultChip({
label,
imgUrl,
icon,
isSelected,
onClick,
onIconClick,
isVertical,
className,
}: DefaultChipProps) {
return (
<Container className={className} $isVertical={isVertical} $isSelected={isSelected} $isClickable={(onClick || onIconClick) ? true : false} onClick={!icon ? onClick : undefined}>
{imgUrl && <div className="image-container"><img className="image" src={imgUrl} alt={label} /></div>}
<Container
title={label}
className={className}
$isVertical={isVertical}
$isSelected={isSelected}
$isClickable={onClick || onIconClick ? true : false}
onClick={!icon ? onClick : undefined}>
{imgUrl && <Image stack={{ name: label, img: imgUrl }} />}
<span className="label">{label}</span>
{icon && <img className="icon" src={icon.src} alt={icon.alt} onClick={onIconClick} />}
</Container>
Expand All @@ -37,10 +53,10 @@ export default function DefaultChip({ label, imgUrl, icon, isSelected, onClick,
export interface DefaultChipContainerStyleProps {
$isVertical?: boolean;
$isSelected?: boolean;
$isClickable?: boolean
$isClickable?: boolean;
}

const { color, typography } = DESIGN_TOKEN;
const { color, typography, mediaQueries } = DESIGN_TOKEN;

const Container = styled.div<DefaultChipContainerStyleProps>`
width: fit-content;
Expand All @@ -56,24 +72,21 @@ const Container = styled.div<DefaultChipContainerStyleProps>`
${typography.font12Semibold}
${({$isClickable}) => $isClickable && `cursor: pointer;`}
${({ $isClickable }) => $isClickable && `cursor: pointer;`}
& .image-container{
width: 3.6rem;
height: 3.6rem;
& > span {
text-align: center;
}
& .image{
object-fit: cover;
}
& .icon{
& .icon {
width: 1.4rem;
height: 1.4rem;
${({$isClickable}) => $isClickable && `cursor: pointer;`}
${({ $isClickable }) => $isClickable && `cursor: pointer;`}
}
${({ $isVertical }) => $isVertical ? `
${({ $isVertical }) =>
$isVertical
? `
flex-direction: column;
border-radius: 1rem;
align-items: center;
Expand All @@ -85,15 +98,21 @@ const Container = styled.div<DefaultChipContainerStyleProps>`
top: .4rem;
right: .4rem;
}
` : `
`
: `
flex-direction: row;
align-items: center;
gap: 1.2rem;
`
}
`}
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
`;

const Image = styled(Stack)`
${mediaQueries.mobile} {
display: none;
}
`;
17 changes: 10 additions & 7 deletions co-kkiri/src/components/commons/Chips/DeleteStackChip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@ import { MouseEvent } from "react";
import { ICONS } from "@/constants/icons";

interface DeleteStackChipProps {
label: string;
onClick: (e: MouseEvent<HTMLDivElement>) => void;
label: string;
onDelete: (stack: string) => void;
}

export default function DeleteStackChip({ label, onClick }: DeleteStackChipProps
) {
return <Container label={label} icon={ICONS.deleted} onClick={onClick} />;
export default function DeleteStackChip({ label, onDelete }: DeleteStackChipProps) {
const onClick = (e: MouseEvent<HTMLDivElement>) => {
onDelete(label);
};

return <Container label={label} icon={ICONS.deleted} onIconClick={onClick} />;
}

const Container = styled(DefaultChip)`
padding: 0.4rem .8rem .4rem 1.2rem;
`
padding: 0.4rem 0.8rem 0.4rem 1.2rem;
`;
87 changes: 54 additions & 33 deletions co-kkiri/src/components/commons/Chips/StackChip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,54 +4,75 @@ import DefaultChip from "./DefaultChip";
import DESIGN_TOKEN from "@/styles/tokens";

interface StackChipProps {
label: string;
imgUrl: string;
isSelected?: boolean;
onClick: (e: MouseEvent<HTMLDivElement>) => void;
label: string;
imgUrl: string;
isSelected?: boolean;
onClick: (e: MouseEvent<HTMLDivElement>) => void;
}

export default function StackChip({ label, imgUrl, isSelected, onClick }: StackChipProps
) {
return <Container label={label} imgUrl={imgUrl} isSelected={isSelected} onClick={onClick} isVertical />;
export default function StackChip({ label, imgUrl, isSelected, onClick }: StackChipProps) {
return <Container label={label} imgUrl={imgUrl} isSelected={isSelected} onClick={onClick} isVertical />;
}

const { color, typography, mediaQueries } = DESIGN_TOKEN;
const Container = styled(DefaultChip)`
width: 7rem;
height: 7.2rem;
width: 7.2rem;
height: 7rem;
padding: .8rem;
gap: .4rem;
padding: 0.8rem;
gap: 0.4rem;
background-color: ${color.white};
border: .1rem solid ${color.gray[2]};
border-radius: 1rem;
color: ${color.black[1]};
background-color: ${color.white};
border: 0.1rem solid ${color.gray[2]};
border-radius: 1rem;
color: ${color.black[1]};
${({ isSelected }) => !isSelected && `opacity: .4;`}
${({ isSelected }) => !isSelected && `opacity: .4;`}
&:hover {
${({ isSelected }) => !isSelected && `opacity: 1;`}
}
${typography.font12Regular}
& > span {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
}
${typography.font11Regular}
${mediaQueries.mobile}{
width: fit-content;
height: fit-content;
${mediaQueries.mobile} {
width: fit-content;
height: fit-content;
padding: .3rem 1.2rem;
padding: 0.3rem 1.2rem;
border-radius: 9999rem;
border-radius: 9999rem;
& .image-container{
display: none;
}
opacity: 1;
${({ isSelected }) => isSelected && `
background-color: ${color.white};
color: ${color.secondary};
& .image-container {
display: none;
}
${({ isSelected }) =>
isSelected &&
`
background-color: ${color.white};
color: ${color.secondary};
border-color: ${color.secondary};
`}
&:hover {
${({ isSelected }) =>
!isSelected &&
`
background-color: ${color.white};
color: ${color.secondary};
border-color: ${color.secondary};
`}
border-color: ${color.secondary};
`}
}
`
}
`;
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState } from "react";
import styled from "styled-components";
import FilterButton from "./commons/FilterDropButton";
import FilterButton from "./commons/FilterButton";
import useOpenToggle from "@/hooks/useOpenToggle";
import DropMenu from "./commons/DropMenu";
import { DROPDOWN_INFO } from "@/constants/dropDown";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ interface FilterButtonProps {

const { popover, popoverSelected } = ICONS;

export default function FilterDropButton({ selectOption, onClick, isSelected }: FilterButtonProps) {
export default function FilterButton({ selectOption, onClick, isSelected }: FilterButtonProps) {
return (
<Container $isSelected={isSelected} onClick={onClick}>
{selectOption}
{isSelected ? (
<Arrow src={popoverSelected.src} alt={popoverSelected.alt} />
<Arrow $isSelected src={popoverSelected.src} alt={popoverSelected.alt} />
) : (
<Arrow src={popover.src} alt={popover.alt} />
)}
Expand Down Expand Up @@ -48,7 +48,15 @@ const Container = styled.button<Container>`
${typography.font12Semibold}
`;

const Arrow = styled.img`
interface Arrow {
$isSelected?: boolean;
}

const Arrow = styled.img<Arrow>`
width: 1.2rem;
height: 1.2rem;
${({ $isSelected }) =>
$isSelected && `transform: rotate(180deg);`
}
`;
60 changes: 60 additions & 0 deletions co-kkiri/src/components/commons/FilterList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import DESIGN_TOKEN from "@/styles/tokens";
import { MouseEvent } from "react";
import styled from "styled-components";

interface FilterListProps {
currentFilter: string
filters: string[];
onFilterClick(filter: string): void;
className?: string;
}

export default function FilterList({ currentFilter, filters, onFilterClick, className }: FilterListProps) {

const handleFilterClick = (e: MouseEvent<HTMLDivElement>) => {
const filter = e.currentTarget.textContent;
if (filter) {
onFilterClick(filter);
}
};

return (
<Container className={className}>
{filters.map((filter) => (
<Box key={filter} $isSelected={currentFilter === filter} onClick={handleFilterClick}>
{filter}
</Box>
))}
</Container>
);
}

const { color, typography, mediaQueries } = DESIGN_TOKEN;

const Container = styled.div`
display: flex;
gap: 2rem;
color: ${color.gray[1]};
${typography.font16Bold}
${mediaQueries.mobile} {
gap: 1.6rem;
${typography.font14Bold}
}
`;

interface BoxProps {
$isSelected?: boolean;
}

const Box = styled.div<BoxProps>`
${({ $isSelected }) => $isSelected && `color: ${color.black[1]};`}
&:hover {
cursor: pointer;
${({ $isSelected }) => !$isSelected && `color: ${color.black[3]};`}
}
`;
Loading

0 comments on commit 966a43b

Please sign in to comment.