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 &&
{label}
} + + {imgUrl && } {label} {icon && {icon.alt}} @@ -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 ( + + {ICONS.reset.alt} +

초기화

+
+ ); +} + +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; +};