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
23 changes: 22 additions & 1 deletion src/page/main/main-page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,25 @@
import BottomSheet from '@widgets/main/bottom-sheet/bottom-sheet';
import { RadioContent } from '@widgets/main/bottom-sheet/contents/radio/radio-content';
import { useState } from 'react';

export type SortType = 'latest' | 'near';

const MainPage = () => {
return <div>Mainpage</div>;
const [isOpen, setIsOpen] = useState(true);
const [sortType, setSortType] = useState<SortType>('latest');

return (
<div>
<BottomSheet.Root isOpen={isOpen} onClose={() => setIsOpen(false)}>
<BottomSheet.Overlay />
<BottomSheet.Container size="sm">
<BottomSheet.Header title="정렬" />
<BottomSheet.Content>
<RadioContent value={sortType} onChange={setSortType} />
</BottomSheet.Content>
</BottomSheet.Container>
</BottomSheet.Root>
</div>
);
};
export default MainPage;
3 changes: 3 additions & 0 deletions src/shared/assets/icon/Radio.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file removed src/shared/hooks/.gitkeep
Empty file.
15 changes: 15 additions & 0 deletions src/shared/hooks/use-bottom-sheet-context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { createContext, useContext } from 'react';

interface BottomSheetContextValue {
onClose: () => void;
}

const BottomSheetContext = createContext<BottomSheetContextValue | null>(null);

export function useBottomSheetContext() {
const ctx = useContext(BottomSheetContext);
if (!ctx) throw new Error('BottomSheet 내부에서만 사용 가능');
return ctx;
}

export { BottomSheetContext };
File renamed without changes.
File renamed without changes.
Empty file removed src/widgets/.gitkeep
Empty file.
93 changes: 93 additions & 0 deletions src/widgets/main/bottom-sheet/bottom-sheet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import {
BottomSheetContext,
useBottomSheetContext,
} from '@shared/hooks/use-bottom-sheet-context';
import type { ReactNode } from 'react';
import { cva } from 'class-variance-authority';
import { cn } from '@shared/utils/cn';

interface BottomSheetProps {
isOpen: boolean;
onClose: () => void;
children: ReactNode;
}

function Overlay() {
const { onClose } = useBottomSheetContext();

return (
<div
onClick={onClose}
className="fixed inset-0 bg-black/30 backdrop-blur"
/>
);
}

function Root({ isOpen, onClose, children }: BottomSheetProps) {
if (!isOpen) return null;

return (
<BottomSheetContext.Provider value={{ onClose }}>
<div className="fixed inset-0 z-40">{children}</div>
</BottomSheetContext.Provider>
);
}

const containerVariants = cva(
'fixed bottom-0 left-0 right-0 bg-white rounded-t-[24px] z-50',
{
variants: {
size: {
sm: 'h-[19.8rem]',
md: 'h-[40rem]',
},
},
},
);

function Container({
size,
children,
}: {
size?: 'sm' | 'md';
children: ReactNode;
}) {
return <div className={cn(containerVariants({ size }))}>{children}</div>;
}

function Header({
title,
rightAction,
}: {
title: string;
rightAction?: React.ReactNode;
}) {
return (
<div className="flex flex-col">
<div className="flex items-center h-[2.4rem] justify-center">
<div className="w-[4rem] h-[0.4rem] rounded-full bg-gray-200" />
</div>
<div className="flex h-[3rem] items-center justify-center px-[1rem] py-[0.4rem] relative">
<p className="typo-h3">{title}</p>

{rightAction && (
<button className="absolute typo-body3 text-gray-400 right-[2.4rem] w-[2.4rem] h-[2.1rem]">
{rightAction}
</button>
)}
</div>
</div>
);
}
function Content({ children }: { children: ReactNode }) {
return <div>{children}</div>;
}
const BottomSheet = {
Root,
Overlay,
Container,
Header,
Content,
};

export default BottomSheet;
24 changes: 24 additions & 0 deletions src/widgets/main/bottom-sheet/contents/map/location-search.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { FloatingActionButton } from '@shared/ui/floatingActionButton';
import Input from '@shared/ui/input';
import LocationIcon from '@shared/assets/icon/material-symbols_my-location-outline-rounded.svg?react';

interface LocationSearchProps {
value: string;
onChange: (value: string) => void;
}

export function LocationSearch({ value, onChange }: LocationSearchProps) {
return (
<div className="flex items-center justify-center h-[9.2rem] [gap-[1.6rem]">
<Input
inputSize={'sm'}
value={value}
onChange={(e) => onChange(e.target.value)}
placeholder="동을 입력해주세요. 예) 역삼동"
/>
<FloatingActionButton
icon={<LocationIcon width={'1.83rem'} height={'1.83rem'} />}
/>
</div>
);
}
23 changes: 23 additions & 0 deletions src/widgets/main/bottom-sheet/contents/radio/radio-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import RadioIcon from '@shared/assets/icon/Radio.svg?react';

interface RadioButtonProps {
label: string;
checked: boolean;
onClick: () => void;
}

export function RadioButton({ label, checked, onClick }: RadioButtonProps) {
return (
<button
className="flex items-center justify-between w-full h-[5.6rem] px-[2.4rem]"
onClick={onClick}
>
<span className="typo-body1">{label}</span>
<RadioIcon
width={'2rem'}
height={'2rem'}
className={checked ? 'text-primary' : 'text-gray-300'}
/>
</button>
);
}
26 changes: 26 additions & 0 deletions src/widgets/main/bottom-sheet/contents/radio/radio-content.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { RadioButton } from '@widgets/main/bottom-sheet/contents/radio/radio-button';

type SortType = 'latest' | 'near';

interface RadioContentProps {
value: SortType;
onChange: (value: SortType) => void;
}

export function RadioContent({ value, onChange }: RadioContentProps) {
return (
<div className="flex flex-col">
<RadioButton
label="최신순"
checked={value === 'latest'}
onClick={() => onChange('latest')}
/>

<RadioButton
label="현 위치와 가까운 순"
checked={value === 'near'}
onClick={() => onChange('near')}
/>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CardInfo } from '@page/main/component/card/card-info';
import { CardTitle } from '@page/main/component/card/card-title';
import { CardInfo } from '@widgets/main/card/card-info';
import { CardTitle } from '@widgets/main/card/card-title';

interface CardProps {
image?: string;
Expand Down
1 change: 1 addition & 0 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export default defineConfig({
icon: true, // viewBox 유지 + size 제어 쉬움
replaceAttrValues: {
black: 'currentColor',
'#B0B0B0': 'currentColor',
},
},
}),
Expand Down