Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"@hookform/resolvers": "^3.9.0",
"@tailwindcss/typography": "^0.5.15",
"@tanstack/react-query": "^5.51.23",
"@tippyjs/react": "^4.2.6",
"apexcharts": "^4.1.0",
"axios": "^1.7.7",
"date-fns": "^4.1.0",
Expand All @@ -47,6 +48,7 @@
"react-icons": "^5.3.0",
"react-router-dom": "^6.26.1",
"swiper": "^11.1.14",
"tippy.js": "^6.3.7",
"zod": "^3.23.8"
},
"devDependencies": {
Expand Down
26 changes: 18 additions & 8 deletions src/components/calendar/Calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import koLocale from '@fullcalendar/core/locales/ko';
import dayGridPlugin from '@fullcalendar/daygrid';
import FullCalendar from '@fullcalendar/react';
import rrulePlugin from '@fullcalendar/rrule';
import tippy from 'tippy.js';
import 'tippy.js/dist/tippy.css';

import SmallCalendarBottomEvent from '@/components/calendar/SmallCalendarBottomEvent';
import LoopLoading from '@/components/common/LoopLoading';
Expand Down Expand Up @@ -89,6 +91,12 @@ export default function Calendar({
dayCellContent={({ dayNumberText }) =>
`${dayNumberText.slice(0, -1)}`
}
eventMouseEnter={({ el, event }) => {
tippy(el, {
content: event.title,
placement: 'top',
});
}}
eventClick={event => {
event.jsEvent.preventDefault();
navigate(event.event.url);
Expand Down Expand Up @@ -119,14 +127,16 @@ export default function Calendar({
((sideViewEvents as FullCalendarEvent[]).length !== 0 ? (
<ul className="flex w-full flex-col justify-center gap-2 px-2 pt-2">
{(sideViewEvents as FullCalendarEvent[]).map(
({ id, start, end, title }) => (
<SmallCalendarBottomEvent
key={id}
date={formatDate(start, 'MM.dd')}
time={`${formatDate(start, 'HH:mm')} ~ ${formatDate(end, 'HH:mm')}`}
title={title}
/>
),
({ id, start, end, title, url }) =>
url && (
<SmallCalendarBottomEvent
key={id}
date={formatDate(start, 'MM.dd')}
time={`${formatDate(start, 'HH:mm')} ~ ${formatDate(end, 'HH:mm')}`}
title={title}
to={url}
/>
),
)}
</ul>
) : (
Expand Down
13 changes: 11 additions & 2 deletions src/components/calendar/SmallCalendarBottomEvent.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,33 @@
import { Link } from 'react-router-dom';

interface SmallCalendarBottomEventProps {
title: string;
date: string;
time: string;
to: string;
}

export default function SmallCalendarBottomEvent({
date,
title,
time,
to,
}: SmallCalendarBottomEventProps) {
return (
<li className="line-clamp-1 flex w-full items-center py-1">
<Link to={to} className="group relative flex w-full items-center py-1">
<div className="min-w-[154px]">
<span className="inline-block w-[50px] text-justify text-mainGray-active">
{date}
</span>
<span className="text-mainGray-active">{time}</span>
</div>

<span className="ml-1.5 mr-2 text-mainGray-active">|</span>
<span className="truncate tracking-tight">{title}</span>
</li>

<div className="absolute -bottom-8 right-0 z-40 hidden rounded-lg bg-black px-3 py-1 group-hover:block">
<span className="text-white">{title}</span>
</div>
</Link>
);
}
24 changes: 24 additions & 0 deletions src/components/common/button/ChevronButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import Icon from '@/components/common/Icon';

interface ChevronButtonProps {
direction: 'ChevronRight' | 'ChevronLeft';
handleClose: () => void;
className?: string;
}

export default function ChevronButton({
direction,
handleClose,
className,
}: ChevronButtonProps) {
return (
<button
type="button"
aria-label={`${direction === 'ChevronRight' ? '접어두기' : '펼치기'} `}
className={`flex size-10 items-center justify-center rounded-lg border border-[#E9E9E9] bg-white text-mainGray ${className}`}
onClick={handleClose}
>
<Icon name={direction} className="fill-darkGray" />
</button>
);
}
2 changes: 1 addition & 1 deletion src/components/common/button/FavoriteButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default function FavoriteButton({
onClick,
disabled,
}: FavoriteButtonProps) {
const iconClassName = 'cursor-pointer text-mainGreen';
const iconClassName = 'cursor-pointer mt-0.5 text-mainGreen';

return (
<button type="button" onClick={onClick} disabled={disabled}>
Expand Down
27 changes: 11 additions & 16 deletions src/components/common/container/CollapsibleSideView.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Icon from '@/components/common/Icon';
import ChevronButton from '@/components/common/button/ChevronButton';

interface CollapsibleSideViewProps {
sideViewOpen: boolean;
Expand All @@ -22,23 +22,18 @@ export default function CollapsibleSideView({
className={`z-10 flex flex-col transition-all duration-200 ease-in-out ${sideViewOpen ? 'w-full translate-x-0' : 'w-0 translate-x-full'} ${className}`}
>
{sideViewOpen && (
<header className="flex justify-between font-semibold text-mainGray-active">
{!hideButton && (
<button
type="button"
aria-label="접어두기"
className="mb-10 mt-auto flex size-10 items-center justify-center rounded-lg bg-white text-sm"
onClick={onClose}
>
<Icon name="ChevronRight" className="size-6 fill-darkerGray" />
</button>
)}
<>
<header className="flex justify-between pr-5 font-semibold text-mainGray-active">
{!hideButton && (
<ChevronButton direction="ChevronRight" handleClose={onClose} />
)}

{headerContent}
</header>
)}
{headerContent}
</header>

{mainContent}
{mainContent}
</>
)}
</section>
);
}
13 changes: 8 additions & 5 deletions src/components/common/dropdown/MultiSelectDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export default function MultiSelectDropdown({
}: MultiSelectDropdownProps) {
const [open, setOpen] = useState(false);

const activeOptions = options.filter(({ isDisabled }) => !isDisabled);
const selectedOptions = options.filter(({ id }) => value?.includes(id));

const checkIsSelected = useCallback(
Expand All @@ -79,9 +80,10 @@ export default function MultiSelectDropdown({

const handleFullCheck = useCallback(() => {
const updatedOptions =
selectedOptions.length !== options.length ? options : [];
selectedOptions.length !== activeOptions.length ? activeOptions : [];

return onChangeValue(updatedOptions);
}, [onChangeValue, options, selectedOptions.length]);
}, [activeOptions, onChangeValue, selectedOptions.length]);

const onResetClick = useCallback(() => {
onChangeValue([]);
Expand Down Expand Up @@ -120,14 +122,14 @@ export default function MultiSelectDropdown({
{hasFullCheck && (
<label
className={`mt-2 flex cursor-pointer items-center rounded-lg px-3 py-1.5 hover:bg-lightGray ${
selectedOptions.length === options.length
? 'text-mainGray'
selectedOptions.length === activeOptions.length
? 'text-darkGray-hover'
: 'text-black'
} ${optionClassName}`}
>
<input
type="checkbox"
checked={selectedOptions.length === options.length}
checked={selectedOptions.length === activeOptions.length}
onChange={handleFullCheck}
className="mr-3 size-4 rounded border-gray-300"
/>
Expand All @@ -142,6 +144,7 @@ export default function MultiSelectDropdown({
onOptionClick={handleCheckboxChange}
isMultiSelectOption
className={optionClassName}
disabled={option.isDisabled}
/>
))}
</ContainerComponent>
Expand Down
28 changes: 16 additions & 12 deletions src/components/common/dropdown/TechStackDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ interface MultiSelectDropdownProps {
isMarkTechStackList?: boolean;
boxShape?: SelectBoxShape;
selectBoxClassName?: string;
hasUnlimitOption?: boolean;
}

/**
Expand All @@ -53,6 +54,7 @@ const TechStackDropdown = memo(function TechStackDropdown({
isMarkTechStackList = false,
boxShape = 'inputShape',
selectBoxClassName = '',
hasUnlimitOption,
}: MultiSelectDropdownProps) {
const [tabValue, setTabValue] = useState(defaultTabValue);
const [open, setOpen] = useState(false);
Expand Down Expand Up @@ -112,6 +114,8 @@ const TechStackDropdown = memo(function TechStackDropdown({

const handleSelectBoxClick = () => setOpen(prev => !prev);

const unlimitOption = { id: 0, name: '제한 없음' };

return (
<>
{isMarkTechStackList && value.length !== 0 && (
Expand Down Expand Up @@ -149,29 +153,29 @@ const TechStackDropdown = memo(function TechStackDropdown({
tabClassName="pb-[7px] px-3"
className="px-5"
/>

<ul className="flex flex-wrap gap-2.5 p-4">
{filteredOptionsByTab.map(option => (
<TechStackOption
key={option.id}
option={option}
onChangeValue={handleSelectOptionChange}
isSelected={checkIsSelected(option)}
// disabled={disabled}
/>
))}
</ul>

<label className="mb-4 ml-6 flex w-fit cursor-pointer items-center gap-2">
<input
type="checkbox"
checked={checkIsSelected({ id: 0, name: '제한 없음' })}
onChange={() =>
handleSelectOptionChange({ id: 0, name: '제한 없음' })
}
className="size-4 rounded border-darkGray"
/>
<span>제한 없음</span>
</label>
{hasUnlimitOption && (
<label className="mb-4 ml-5 flex w-fit cursor-pointer items-center gap-2">
<input
type="checkbox"
checked={checkIsSelected(unlimitOption)}
onChange={() => handleSelectOptionChange(unlimitOption)}
className="size-4 cursor-pointer rounded border-darkGray"
/>
<span>제한 없음</span>
</label>
)}
</SelectBox>
</>
);
Expand Down
12 changes: 9 additions & 3 deletions src/components/common/dropdown/option/SelectOption.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ interface SelectOptionProps {
onOptionClick: (option: Option) => void;
isMultiSelectOption?: boolean;
className?: string;
disabled?: boolean;
}

/**
Expand All @@ -21,20 +22,25 @@ export default function SelectOption({
onOptionClick,
isMultiSelectOption = false,
className = '',
disabled,
}: SelectOptionProps) {
return (
<li key={option.id} className="rounded-lg first:mt-2 last:mb-2">
{isMultiSelectOption ? (
<label
className={`flex cursor-pointer items-center rounded-lg px-3 py-1.5 text-start hover:bg-lightGray-active ${isSelected ? 'text-mainGray' : 'text-black'} ${className}`}
className={`flex items-center rounded-lg px-3 py-1.5 text-start hover:bg-lightGray-active ${disabled ? '' : 'cursor-pointer'} ${className}`}
>
<input
type="checkbox"
checked={isSelected}
onChange={() => onOptionClick(option)}
className="mr-3 size-4 min-h-4 min-w-4 rounded border-darkGray"
className={`mr-3 size-4 min-h-4 min-w-4 ${disabled ? 'appearance-none rounded-[3px] border border-mainGray-active' : ''}`}
/>
<span>{option.name}</span>
<span
className={`${disabled ? 'text-mainGray' : ''} ${isSelected ? 'text-darkGray-hover' : 'text-black'}`}
>
{option.name}
</span>
</label>
) : (
<button
Expand Down
Loading
Loading