diff --git a/co-kkiri/src/assets/icons/reset.svg b/co-kkiri/src/assets/icons/reset.svg
new file mode 100644
index 00000000..433b5cc1
--- /dev/null
+++ b/co-kkiri/src/assets/icons/reset.svg
@@ -0,0 +1,7 @@
+
diff --git a/co-kkiri/src/components/commons/Chips/DefaultChip.tsx b/co-kkiri/src/components/commons/Chips/DefaultChip.tsx
index 85734a80..27795f53 100644
--- a/co-kkiri/src/components/commons/Chips/DefaultChip.tsx
+++ b/co-kkiri/src/components/commons/Chips/DefaultChip.tsx
@@ -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;
@@ -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 (
-
- {imgUrl && }
+
+ {imgUrl && }
{label}
{icon &&
}
@@ -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`
width: fit-content;
@@ -56,24 +72,21 @@ const Container = styled.div`
${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;
@@ -85,15 +98,21 @@ const Container = styled.div`
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;
+ }
+`;
diff --git a/co-kkiri/src/components/commons/Chips/DeleteStackChip.tsx b/co-kkiri/src/components/commons/Chips/DeleteStackChip.tsx
index 3f6d1e06..c41ce6f8 100644
--- a/co-kkiri/src/components/commons/Chips/DeleteStackChip.tsx
+++ b/co-kkiri/src/components/commons/Chips/DeleteStackChip.tsx
@@ -4,15 +4,18 @@ import { MouseEvent } from "react";
import { ICONS } from "@/constants/icons";
interface DeleteStackChipProps {
- label: string;
- onClick: (e: MouseEvent) => void;
+ label: string;
+ onDelete: (stack: string) => void;
}
-export default function DeleteStackChip({ label, onClick }: DeleteStackChipProps
-) {
- return ;
+export default function DeleteStackChip({ label, onDelete }: DeleteStackChipProps) {
+ const onClick = (e: MouseEvent) => {
+ onDelete(label);
+ };
+
+ return ;
}
const Container = styled(DefaultChip)`
- padding: 0.4rem .8rem .4rem 1.2rem;
-`
+ padding: 0.4rem 0.8rem 0.4rem 1.2rem;
+`;
diff --git a/co-kkiri/src/components/commons/Chips/StackChip.tsx b/co-kkiri/src/components/commons/Chips/StackChip.tsx
index ecdaaad6..28e5bc53 100644
--- a/co-kkiri/src/components/commons/Chips/StackChip.tsx
+++ b/co-kkiri/src/components/commons/Chips/StackChip.tsx
@@ -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) => void;
+ label: string;
+ imgUrl: string;
+ isSelected?: boolean;
+ onClick: (e: MouseEvent) => void;
}
-export default function StackChip({ label, imgUrl, isSelected, onClick }: StackChipProps
-) {
- return ;
+export default function StackChip({ label, imgUrl, isSelected, onClick }: StackChipProps) {
+ return ;
}
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};
+ `}
}
-
-`
\ No newline at end of file
+ }
+`;
diff --git a/co-kkiri/src/components/commons/DropDowns/FilterDropdown.tsx b/co-kkiri/src/components/commons/DropDowns/FilterDropdown.tsx
index 9e18f0aa..4ea18b9b 100644
--- a/co-kkiri/src/components/commons/DropDowns/FilterDropdown.tsx
+++ b/co-kkiri/src/components/commons/DropDowns/FilterDropdown.tsx
@@ -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";
diff --git a/co-kkiri/src/components/commons/DropDowns/commons/FilterDropButton.tsx b/co-kkiri/src/components/commons/DropDowns/commons/FilterButton.tsx
similarity index 77%
rename from co-kkiri/src/components/commons/DropDowns/commons/FilterDropButton.tsx
rename to co-kkiri/src/components/commons/DropDowns/commons/FilterButton.tsx
index a19da659..2c9ce154 100644
--- a/co-kkiri/src/components/commons/DropDowns/commons/FilterDropButton.tsx
+++ b/co-kkiri/src/components/commons/DropDowns/commons/FilterButton.tsx
@@ -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 (
{selectOption}
{isSelected ? (
-
+
) : (
)}
@@ -48,7 +48,15 @@ const Container = styled.button`
${typography.font12Semibold}
`;
-const Arrow = styled.img`
+interface Arrow {
+ $isSelected?: boolean;
+}
+
+const Arrow = styled.img`
width: 1.2rem;
height: 1.2rem;
+
+ ${({ $isSelected }) =>
+ $isSelected && `transform: rotate(180deg);`
+ }
`;
diff --git a/co-kkiri/src/components/commons/FilterList.tsx b/co-kkiri/src/components/commons/FilterList.tsx
new file mode 100644
index 00000000..8a2ac619
--- /dev/null
+++ b/co-kkiri/src/components/commons/FilterList.tsx
@@ -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) => {
+ const filter = e.currentTarget.textContent;
+ if (filter) {
+ onFilterClick(filter);
+ }
+ };
+
+ return (
+
+ {filters.map((filter) => (
+
+ {filter}
+
+ ))}
+
+ );
+}
+
+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`
+ ${({ $isSelected }) => $isSelected && `color: ${color.black[1]};`}
+
+ &:hover {
+ cursor: pointer;
+ ${({ $isSelected }) => !$isSelected && `color: ${color.black[3]};`}
+ }
+`;
diff --git a/co-kkiri/src/components/domains/ProjectDetailCard/ProjectDetailRow.tsx b/co-kkiri/src/components/commons/ProjectDetailCard/ProjectDetailRow.tsx
similarity index 100%
rename from co-kkiri/src/components/domains/ProjectDetailCard/ProjectDetailRow.tsx
rename to co-kkiri/src/components/commons/ProjectDetailCard/ProjectDetailRow.tsx
diff --git a/co-kkiri/src/components/domains/ProjectDetailCard/ProjectDetailTable.tsx b/co-kkiri/src/components/commons/ProjectDetailCard/ProjectDetailTable.tsx
similarity index 100%
rename from co-kkiri/src/components/domains/ProjectDetailCard/ProjectDetailTable.tsx
rename to co-kkiri/src/components/commons/ProjectDetailCard/ProjectDetailTable.tsx
diff --git a/co-kkiri/src/components/domains/ProjectDetailCard/index.tsx b/co-kkiri/src/components/commons/ProjectDetailCard/index.tsx
similarity index 100%
rename from co-kkiri/src/components/domains/ProjectDetailCard/index.tsx
rename to co-kkiri/src/components/commons/ProjectDetailCard/index.tsx
diff --git a/co-kkiri/src/components/domains/ProjectDetailCard/types.ts b/co-kkiri/src/components/commons/ProjectDetailCard/types.ts
similarity index 100%
rename from co-kkiri/src/components/domains/ProjectDetailCard/types.ts
rename to co-kkiri/src/components/commons/ProjectDetailCard/types.ts
diff --git a/co-kkiri/src/components/commons/Stack.tsx b/co-kkiri/src/components/commons/Stack.tsx
index 4d25e095..fd6e88c8 100644
--- a/co-kkiri/src/components/commons/Stack.tsx
+++ b/co-kkiri/src/components/commons/Stack.tsx
@@ -1,17 +1,26 @@
import styled from "styled-components";
import DESIGN_TOKEN from "@/styles/tokens";
import { ICONS } from "@/constants/icons";
-import { STACK_ICONS } from "@/constants/stackIcons";
+import { Stack } from "@/types/StackTypes";
+import { Image } from "@/types/ImageTypes";
interface StackProps {
- stack?: string;
+ stack?: Pick;
+ className?: string;
}
-export default function Stack({ stack }: StackProps) {
+export default function Stack({ stack, className }: StackProps) {
//임시
- const icon = stack && STACK_ICONS[stack] ? STACK_ICONS[stack] : ICONS.questionMark;
+ const icon: Image =
+ stack && stack.img
+ ? {
+ src: stack.img,
+ alt: stack.name,
+ }
+ : ICONS.questionMark;
+
return (
-
+
);
@@ -20,6 +29,8 @@ export default function Stack({ stack }: StackProps) {
const { color } = DESIGN_TOKEN;
const Background = styled.div`
+ padding: 0.8rem;
+
background-color: ${color.gray[3]};
border-radius: 50%;
width: 3.6rem;
@@ -31,7 +42,7 @@ const Background = styled.div`
`;
const Icon = styled.img`
- //임시
- max-width: 2.4rem;
- max-height: 2.4rem;
+ width: 100%; /* 컨테이너 너비에 맞춤 */
+ height: 100%; /* 컨테이너 높이에 맞춤 */
+ object-fit: contain;
`;
diff --git a/co-kkiri/src/components/commons/Stacks.tsx b/co-kkiri/src/components/commons/Stacks.tsx
index a77a1d0a..5a94bcae 100644
--- a/co-kkiri/src/components/commons/Stacks.tsx
+++ b/co-kkiri/src/components/commons/Stacks.tsx
@@ -1,5 +1,6 @@
import styled from "styled-components";
-import Stack from "./Stack";
+import StackComponent from "./Stack";
+import { STACKS } from "@/constants/stacks";
interface StacksProps {
stacks: string[];
@@ -7,7 +8,7 @@ interface StacksProps {
export default function Stacks({ stacks }: StacksProps) {
return (
- {stacks.length > 0 ? stacks.map((stack) => ) : }
+ {stacks.length > 0 ? stacks.map((stack) => ) : }
);
}
diff --git a/co-kkiri/src/components/domains/detail/StackPopover/DeleteStackChipList.tsx b/co-kkiri/src/components/domains/detail/StackPopover/DeleteStackChipList.tsx
new file mode 100644
index 00000000..7c3fd59f
--- /dev/null
+++ b/co-kkiri/src/components/domains/detail/StackPopover/DeleteStackChipList.tsx
@@ -0,0 +1,24 @@
+import DeleteStackChip from "@/components/commons/Chips/DeleteStackChip";
+import styled from "styled-components";
+
+interface DeleteStackChipListProps {
+ stacks: string[];
+ onDeleteStack: (stack: string) => void;
+ className?: string;
+}
+
+export default function DeleteStackChipList({ stacks, onDeleteStack, className }: DeleteStackChipListProps) {
+ return (
+
+ {stacks.map((stack) => (
+
+ ))}
+
+ );
+}
+
+const Container = styled.div`
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.6rem;
+`;
diff --git a/co-kkiri/src/components/domains/detail/StackPopover/ResetButton.tsx b/co-kkiri/src/components/domains/detail/StackPopover/ResetButton.tsx
new file mode 100644
index 00000000..33fe56bf
--- /dev/null
+++ b/co-kkiri/src/components/domains/detail/StackPopover/ResetButton.tsx
@@ -0,0 +1,53 @@
+import { ICONS } from "@/constants/icons";
+import DESIGN_TOKEN from "@/styles/tokens";
+import styled from "styled-components";
+
+interface ResetButtonProps {
+ onReset: () => void;
+ className?: string;
+}
+
+export default function ResetButton({ onReset, className }: ResetButtonProps) {
+ return (
+
+
+ 초기화
+
+ );
+}
+
+const { color, typography } = DESIGN_TOKEN;
+
+const Container = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 0.8rem;
+
+ color: ${color.black[3]};
+
+ ${typography.font12Semibold}
+
+ img {
+ width: 1.4rem;
+ height: 1.4rem;
+ fill: ${color.black[3]};
+ }
+
+ &:hover {
+ cursor: pointer;
+ color: ${color.black[1]};
+
+ img {
+ animation: rotateAnimation 1s ease-in-out infinite;
+ }
+ }
+
+ @keyframes rotateAnimation {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(-360deg);
+ }
+ }
+`;
diff --git a/co-kkiri/src/components/domains/detail/StackPopover/SelectLayout.tsx b/co-kkiri/src/components/domains/detail/StackPopover/SelectLayout.tsx
new file mode 100644
index 00000000..a27e9b35
--- /dev/null
+++ b/co-kkiri/src/components/domains/detail/StackPopover/SelectLayout.tsx
@@ -0,0 +1,119 @@
+import DESIGN_TOKEN from "@/styles/tokens";
+import styled from "styled-components";
+import DefaultFilterList from "../../../commons/FilterList";
+import DefaultResetButton from "./ResetButton";
+import DefaultStackChipList from "./StackChipList";
+import DefaultDeleteStackChipList from "./DeleteStackChipList";
+import { useEffect, useState } from "react";
+import { StackPositionFilter, getFilterKey, mappedFilter } from "./constants";
+
+interface SelectLayoutProps {
+ onStacksChange: (selectedStacks: string[]) => void;
+}
+
+export default function SelectLayout({ onStacksChange }: SelectLayoutProps) {
+ const [filter, setFilter] = useState("ALL");
+ const [selectedStacks, setSelectedStacks] = useState([]);
+
+ useEffect(() => {
+ onStacksChange(selectedStacks);
+ }, [selectedStacks, onStacksChange]);
+
+ return (
+
+ {
+ setFilter(mappedFilter[filter]);
+ }}
+ />
+ {
+ if (selectedStacks.includes(stack)) {
+ setSelectedStacks((prevStacks) => prevStacks.filter((prevStack) => prevStack !== stack));
+ } else {
+ setSelectedStacks((prevStacks) => [...prevStacks, stack]);
+ }
+ }}
+ />
+ {
+ setSelectedStacks([]);
+ }}
+ />
+ {selectedStacks.length !== 0 && (
+ {
+ setSelectedStacks((prevStacks) => prevStacks.filter((prevStack) => prevStack !== stack));
+ }}
+ />
+ )}
+
+ );
+}
+
+const { color, mediaQueries } = DESIGN_TOKEN;
+
+interface ContainerProps {
+ $isSelectedStacks: boolean;
+}
+const Container = styled.div`
+ height: fit-content;
+
+ padding: 3rem 4rem;
+
+ display: grid;
+ position: absolute;
+ top: 4.2rem;
+ z-index: 997;
+
+ border-radius: 2rem;
+ border: 0.1rem solid ${color.gray[2]};
+
+ background-color: ${color.white};
+
+ grid-template-areas:
+ "filterlist resetbutton"
+ "stackchips stackchips"
+ ${({ $isSelectedStacks }) => $isSelectedStacks && `"deletestackchips deletestackchips"`};
+
+ ${mediaQueries.mobile} {
+ width: 32rem;
+ padding: 2rem 3rem;
+
+ display: flex;
+ flex-direction: column;
+ gap: 2rem;
+ }
+
+ ${mediaQueries.tablet} {
+ width: 70rem;
+ gap: 3rem;
+ }
+
+ ${mediaQueries.desktop} {
+ width: 85.6rem;
+ gap: 3rem;
+ }
+`;
+
+const FilterList = styled(DefaultFilterList)`
+ grid-area: filterlist;
+`;
+
+const ResetButton = styled(DefaultResetButton)`
+ grid-area: resetbutton;
+ justify-self: end;
+`;
+
+const StackChipList = styled(DefaultStackChipList)`
+ grid-area: stackchips;
+`;
+
+const DeleteStackChipList = styled(DefaultDeleteStackChipList)`
+ grid-area: deletestackchips;
+`;
diff --git a/co-kkiri/src/components/domains/detail/StackPopover/StackChipList.tsx b/co-kkiri/src/components/domains/detail/StackPopover/StackChipList.tsx
new file mode 100644
index 00000000..d93898fa
--- /dev/null
+++ b/co-kkiri/src/components/domains/detail/StackPopover/StackChipList.tsx
@@ -0,0 +1,54 @@
+import StackChip from "@/components/commons/Chips/StackChip";
+import { STACKS } from "@/constants/stacks";
+import DESIGN_TOKEN from "@/styles/tokens";
+import styled from "styled-components";
+import { StackPositionFilter } from "./constants";
+
+interface StackChipGridProps {
+ selectedStacks: string[];
+ filter: StackPositionFilter;
+ onStackChipClick: (stack: string) => void;
+ className?: string;
+}
+
+export default function StackChipList({ selectedStacks, filter, onStackChipClick, className }: StackChipGridProps) {
+ const filteredStacks =
+ filter === "ALL"
+ ? Object.values(STACKS)
+ : Object.values(STACKS).filter((stack) => stack.relatedPosition.includes(filter));
+
+ return (
+
+ {filteredStacks.map((stack) => (
+ {
+ onStackChipClick(stack.name);
+ }}
+ isSelected={selectedStacks.includes(stack.name)}
+ />
+ ))}
+
+ );
+}
+
+const { mediaQueries } = DESIGN_TOKEN;
+const Container = styled.div`
+ width: fit-content;
+
+ display: grid;
+ grid-template-columns: repeat(10, 1fr);
+ gap: 0.6rem;
+
+ ${mediaQueries.mobile} {
+ width: 100%;
+ display: flex;
+ flex-wrap: wrap;
+ }
+
+ ${mediaQueries.tablet} {
+ grid-template-columns: repeat(8, 1fr);
+ }
+`;
diff --git a/co-kkiri/src/components/domains/detail/StackPopover/constants.ts b/co-kkiri/src/components/domains/detail/StackPopover/constants.ts
new file mode 100644
index 00000000..a9df8fdb
--- /dev/null
+++ b/co-kkiri/src/components/domains/detail/StackPopover/constants.ts
@@ -0,0 +1,15 @@
+import { StackPosition } from "@/types/StackTypes";
+
+export type StackPositionFilter = "ALL" | StackPosition;
+
+export const mappedFilter: Record = {
+ 전체: "ALL",
+ 프론트엔드: "FRONT_END",
+ 백엔드: "BACK_END",
+ 모바일: "MOBILE",
+ 기타: "OTHERS",
+};
+
+export const getFilterKey = (value: StackPositionFilter) => {
+ return Object.keys(mappedFilter).find((key) => mappedFilter[key] === value) || "ALL";
+};
diff --git a/co-kkiri/src/components/domains/detail/StackPopover/index.tsx b/co-kkiri/src/components/domains/detail/StackPopover/index.tsx
new file mode 100644
index 00000000..777da74f
--- /dev/null
+++ b/co-kkiri/src/components/domains/detail/StackPopover/index.tsx
@@ -0,0 +1,23 @@
+import FilterDropdown from "@/components/commons/DropDowns/commons/FilterButton";
+import SelectLayout from "./SelectLayout";
+import styled from "styled-components";
+import useOpenToggle from "@/hooks/useOpenToggle";
+
+interface StacksPopoverProps {
+ onStacksChange: (stacks: string[]) => void;
+}
+
+export default function StacksPopover({ onStacksChange }: StacksPopoverProps) {
+ const {isOpen, openToggle} = useOpenToggle();
+
+ return (
+
+
+ {isOpen && }
+
+ );
+}
+
+const Container = styled.div`
+ position: relative;
+`;
diff --git a/co-kkiri/src/constants/icons.ts b/co-kkiri/src/constants/icons.ts
index 1b4c4930..2446598c 100644
--- a/co-kkiri/src/constants/icons.ts
+++ b/co-kkiri/src/constants/icons.ts
@@ -16,8 +16,10 @@ import post from "@/assets/icons/post.svg";
import questionMark from "@/assets/icons/question-mark.svg";
import categorySelected from "@/assets/icons/category_selected.svg";
import calendar from "@/assets/icons/calendar.svg";
+import reset from "@/assets/icons/reset.svg";
+import { Images } from "@/types/ImageTypes";
-export const ICONS = {
+export const ICONS: Images = {
arrowRight: {
src: arrowRight,
alt: "다음",
@@ -90,4 +92,8 @@ export const ICONS = {
src: calendar,
alt: "날짜선택 달력",
},
+ reset: {
+ src: reset,
+ alt: "초기화"
+ }
};
diff --git a/co-kkiri/src/constants/stackIcons.ts b/co-kkiri/src/constants/stackIcons.ts
deleted file mode 100644
index d37266da..00000000
--- a/co-kkiri/src/constants/stackIcons.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-interface StackIcons {
- [key: string]: {
- src: string;
- alt: string;
- };
-}
-
-export const STACK_ICONS: StackIcons = { HTML: { src: "https://simpleicons.org/icons/html5.svg", alt: "HTML 아이콘" } };
diff --git a/co-kkiri/src/constants/stacks.ts b/co-kkiri/src/constants/stacks.ts
new file mode 100644
index 00000000..51c5c180
--- /dev/null
+++ b/co-kkiri/src/constants/stacks.ts
@@ -0,0 +1,36 @@
+import { Stacks } from "@/types/StackTypes";
+
+export const STACKS: Stacks = {
+ "JavaScript": { name: "JavaScript", img: "https://cdn.svgporn.com/logos/javascript.svg", relatedPosition: ["FRONT_END", "BACK_END"] },
+ "TypeScript": { name: "TypeScript", img: "https://cdn.svgporn.com/logos/typescript-icon.svg", relatedPosition: ["FRONT_END", "BACK_END"] },
+ "React": { name: "React", img: "https://cdn.svgporn.com/logos/react.svg", relatedPosition: ["FRONT_END"] },
+ "Vue.js": { name: "Vue.js", img: "https://cdn.svgporn.com/logos/vue.svg", relatedPosition: ["FRONT_END"] },
+ "Angular": { name: "Angular", img: "https://cdn.svgporn.com/logos/angular-icon.svg", relatedPosition: ["FRONT_END"] },
+ "React Query": { name: "React Query", img: "https://cdn.svgporn.com/logos/react-query-icon.svg", relatedPosition: ["FRONT_END"] },
+ "Redux": { name: "Redux", img: "https://cdn.svgporn.com/logos/redux.svg", relatedPosition: ["FRONT_END"] },
+ "Node.js": { name: "Node.js", img: "https://cdn.svgporn.com/logos/nodejs-icon.svg", relatedPosition: ["BACK_END"] },
+ "Spring": { name: "Spring", img: "https://cdn.svgporn.com/logos/spring-icon.svg", relatedPosition: ["BACK_END"] },
+ "Django": { name: "Django", img: "https://cdn.svgporn.com/logos/django-icon.svg", relatedPosition: ["BACK_END"] },
+ "Python": { name: "Python", img: "https://cdn.svgporn.com/logos/python.svg", relatedPosition: ["BACK_END", "OTHERS"] }, // AI 포함
+ "Java": { name: "Java", img: "https://cdn.svgporn.com/logos/java.svg", relatedPosition: ["BACK_END", "MOBILE"] },
+ "MySQL": { name: "MySQL", img: "https://cdn.svgporn.com/logos/mysql-icon.svg", relatedPosition: ["BACK_END"] }, // 데이터베이스
+ "MongoDB": { name: "MongoDB", img: "https://cdn.svgporn.com/logos/mongodb-icon.svg", relatedPosition: ["BACK_END"] }, // 데이터베이스
+ "PostgreSQL": { name: "PostgreSQL", img: "https://cdn.svgporn.com/logos/postgresql.svg", relatedPosition: ["BACK_END"] }, // 데이터베이스
+ "Redis": { name: "Redis", img: "https://cdn.svgporn.com/logos/redis.svg", relatedPosition: ["BACK_END"] }, // 데이터베이스
+ "GraphQL": { name: "GraphQL", img: "https://cdn.svgporn.com/logos/graphql.svg", relatedPosition: ["BACK_END", "FRONT_END"] }, // API
+ "Kotlin": { name: "Kotlin", img: "https://cdn.svgporn.com/logos/kotlin-icon.svg", relatedPosition: ["BACK_END", "MOBILE"] },
+ "Swift": { name: "Swift", img: "https://cdn.svgporn.com/logos/swift.svg", relatedPosition: ["MOBILE"] },
+ "Flutter": { name: "Flutter", img: "https://cdn.svgporn.com/logos/flutter.svg", relatedPosition: ["MOBILE"] },
+ "React Native": { name: "React Native", img: "https://cdn.svgporn.com/logos/react.svg", relatedPosition: ["MOBILE"] },
+ "Figma": { name: "Figma", img: "https://cdn.svgporn.com/logos/figma.svg", relatedPosition: ["OTHERS"] }, // 디자인
+ "Adobe XD": { name: "Adobe XD", img: "https://cdn.svgporn.com/logos/adobe-xd.svg", relatedPosition: ["OTHERS"] }, // 디자인
+ "AWS": { name: "AWS", img: "https://cdn.svgporn.com/logos/aws.svg", relatedPosition: ["OTHERS"] }, // 클라우드 서비스
+ "Docker": { name: "Docker", img: "https://cdn.svgporn.com/logos/docker-icon.svg", relatedPosition: ["OTHERS"] }, // 데브옵스
+ "Kubernetes": { name: "Kubernetes", img: "https://cdn.svgporn.com/logos/kubernetes.svg", relatedPosition: ["OTHERS"] }, // 데브옵스
+ "Git": { name: "Git", img: "https://cdn.svgporn.com/logos/git-icon.svg", relatedPosition: ["OTHERS"] }, // 버전 관리
+ "Jira": { name: "Jira", img: "https://cdn.svgporn.com/logos/jira.svg", relatedPosition: ["OTHERS"] }, // 프로젝트 관리
+ "TensorFlow": { name: "TensorFlow", img: "https://cdn.svgporn.com/logos/tensorflow.svg", relatedPosition: ["OTHERS"] }, // AI
+ "PyTorch": { name: "PyTorch", img: "https://cdn.svgporn.com/logos/pytorch-icon.svg", relatedPosition: ["OTHERS"] }, // AI
+} as const;
+
+export const STACK_NAMES = Object.keys(STACKS);
diff --git a/co-kkiri/src/styles/tokens.ts b/co-kkiri/src/styles/tokens.ts
index f12e4d00..246b144d 100644
--- a/co-kkiri/src/styles/tokens.ts
+++ b/co-kkiri/src/styles/tokens.ts
@@ -26,6 +26,7 @@ const DESIGN_TOKEN = {
secondary: "#FF9B52",
},
typography: {
+ font11Regular: "font-size: 1.1rem; line-height: normal; font-weight: 400;",
font12Regular: "font-size: 1.2rem; line-height: normal; font-weight: 400;",
font12Semibold: "font-size: 1.2rem; line-height: normal; font-weight: 600;",
font14Regular: "font-size: 1.4rem; line-height: 150%; font-weight: 400;",
diff --git a/co-kkiri/src/types/ImageTypes.ts b/co-kkiri/src/types/ImageTypes.ts
new file mode 100644
index 00000000..59c98527
--- /dev/null
+++ b/co-kkiri/src/types/ImageTypes.ts
@@ -0,0 +1,8 @@
+export type Image = {
+ src: string;
+ alt: string;
+}
+
+export type Images = {
+ [key: string]: Image;
+}
diff --git a/co-kkiri/src/types/StackTypes.ts b/co-kkiri/src/types/StackTypes.ts
new file mode 100644
index 00000000..f5bb9d2d
--- /dev/null
+++ b/co-kkiri/src/types/StackTypes.ts
@@ -0,0 +1,11 @@
+export type StackPosition = "FRONT_END" | "BACK_END" | "MOBILE" | "OTHERS"
+
+export type Stack = {
+ name: string;
+ img: string;
+ relatedPosition: Array;
+};
+
+export type Stacks = {
+ [key: string]: Stack;
+};