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
575 changes: 575 additions & 0 deletions .pnp.cjs

Large diffs are not rendered by default.

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
"cypress": "cypress open"
},
"dependencies": {
"@radix-ui/react-dialog": "^1.1.5",
"@radix-ui/react-visually-hidden": "^1.1.1",
"@tanstack/react-query": "^5.64.2",
"classnames": "^2.5.1",
"react": "^18.3.1",
Expand Down
8 changes: 6 additions & 2 deletions src/components/ReceiptEdit/ReceiptEdit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@ const ReceiptEdit = () => {
const [placeName, setPlaceName] = useState("청담커피 앤 토스트");
const [foodName, setFoodName] = useState("카야토스트+음료세트");

const { isFocus: isPlaceFocus, onFocus: handlePlaceFocus, onBlur: handlePlaceBlur } = useFocus();
const { isFocus: isFoodFocus, onFocus: handleFoodFocus, onBlur: handleFoodBlur } = useFocus();
const {
isFocus: isPlaceFocus,
onFocus: handlePlaceFocus,
onBlur: handlePlaceBlur,
} = useFocus({});
const { isFocus: isFoodFocus, onFocus: handleFoodFocus, onBlur: handleFoodBlur } = useFocus({});

return (
<div className={styles.ReceiptEdit}>
Expand Down
20 changes: 14 additions & 6 deletions src/components/SelectTag/SelectTag.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useState } from "react";

import styles from "@/components/SelectTag/SelectTag.module.scss";
import TagSheet from "@/components/SelectTag/TagSheet/TagSheet";
import Button from "@/components/ui/Button/Button";
import Tag from "@/components/ui/Tag/Tag";
import Text from "@/components/ui/Text/Text";
Expand All @@ -22,15 +23,20 @@ const TAG_LIST = [

const SelectTag = () => {
const [selectedTagList, setSelectedTagList] = useState<string[]>([]);
const [isBottomSheetOpen, setIsBottomSheetOpen] = useState(false);

const handleTagClick = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
const tag = e.currentTarget.textContent || "";

if (selectedTagList.includes(tag)) {
setSelectedTagList(selectedTagList.filter((selectedTag) => selectedTag !== tag));
} else {
setSelectedTagList([...selectedTagList, tag]);
}
setSelectedTagList((prevSelectedTags) =>
prevSelectedTags.includes(tag)
? prevSelectedTags.filter((selectedTag) => selectedTag !== tag)
: [...prevSelectedTags, tag],
);
};

const handleSheetClose = () => {
setIsBottomSheetOpen(false);
};

return (
Expand All @@ -53,13 +59,15 @@ const SelectTag = () => {
isSelect={selectedTagList.includes(tag)}
/>
))}
<Tag variant="add" />
<Tag variant="add" onClick={() => setIsBottomSheetOpen(true)} />
</div>
</div>

<div className={styles.Bottom}>
<Button text="다음" />
</div>

<TagSheet isOpen={isBottomSheetOpen} handleClose={handleSheetClose} />
</div>
);
};
Expand Down
74 changes: 74 additions & 0 deletions src/components/SelectTag/TagSheet/TagSheet.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
.DialogOverlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.3);
z-index: 2;
max-width: 37.5rem;
margin: 0 auto;
}

.BottomSheet {
position: fixed;
bottom: 0;
width: 100%;
max-width: 37.5rem;
height: 80%;
background: var(--color-white);
border-radius: 1.25rem 1.25rem 0 0;
padding: 1.25rem;
z-index: 3;
display: flex;
flex-direction: column;
justify-content: space-between;
transition: transform 0.3s ease-out;

&.Open {
animation: slide-in-from-bottom 0.3s ease-out forwards;
}

&.Closed {
animation: slide-out-to-bottom 0.3s ease-in forwards;
}

& > h1 {
margin-top: 2rem;
}

& > button {
margin-top: auto;
}
}

.IconBox {
display: flex;
justify-content: flex-end;
}

.Input {
margin-top: 1.5rem;
}

.LengthText {
display: flex;
margin-top: 0.625rem;
}

@keyframes slide-in-from-bottom {
0% {
transform: translateY(100%);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}

@keyframes slide-out-to-bottom {
0% {
transform: translateY(0);
}
100% {
transform: translateY(100%);
}
}
92 changes: 92 additions & 0 deletions src/components/SelectTag/TagSheet/TagSheet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { useState } from "react";

import * as Dialog from "@radix-ui/react-dialog";
import * as VisuallyHidden from "@radix-ui/react-visually-hidden";
import classNames from "classnames";

import styles from "@/components/SelectTag/TagSheet/TagSheet.module.scss";
import Button from "@/components/ui/Button/Button";
import Icon from "@/components/ui/Icon/Icon";
import Input from "@/components/ui/Input/Input";
import Text from "@/components/ui/Text/Text";

import { useFocus } from "@/hooks/common/useFocus";

interface TagSheetProps {
isOpen: boolean;
handleClose: () => void;
}

const TagSheet = ({ isOpen, handleClose }: TagSheetProps) => {
const { isFocus, onFocus, onBlur } = useFocus({ defaultFocus: true });

const [newTag, setNewTag] = useState("");

const isInputError = newTag.length > 20;
const isInputEmpty = newTag.length === 0;

const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setNewTag(e.target.value);
};

return (
<Dialog.Root open={isOpen}>
<Dialog.Portal>
<Dialog.Overlay className={styles.DialogOverlay} />
<Dialog.Content
className={classNames(styles.BottomSheet, {
[styles.Open]: isOpen,
[styles.Closed]: !isOpen,
})}
>
{/* 미사용 코드 콘솔 warning 제거용 */}
<VisuallyHidden.Root>
<Dialog.Title>Title</Dialog.Title>
<Dialog.Description>Description</Dialog.Description>
</VisuallyHidden.Root>

<div className={styles.IconBox} onClick={handleClose}>
<Icon name="close" />
</div>
<Text variant="titleM" color="primary">
더 넣고 싶은 내용이 있나요?
</Text>
<Input
variant="secondary"
placeholder="의견을 입력해주세요."
className={styles.Input}
value={newTag}
onChange={handleInputChange}
isFocus={isFocus}
onFocus={onFocus}
onBlur={onBlur}
isError={isInputError}
/>
<div className={styles.LengthText}>
{isInputError ? (
<Text variant="bodyXsm" color="error">
*20글자 이내로 입력할 수 있어요.
</Text>
) : (
<>
<Text variant="bodyXsm" color="tertiary">
*
</Text>
<Text variant="bodyXsm" color="secondary">
{newTag.length}
</Text>
<Text variant="bodyXsm" color="tertiary">
/20
</Text>
</>
)}
</div>

<Button text="추가하기" disabled={isInputError || isInputEmpty} />
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
};

export default TagSheet;
6 changes: 5 additions & 1 deletion src/components/ui/Input/Input.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
width: 100%;
height: 3rem;
border-radius: 0.875rem;
box-shadow: 0 0.125rem 1rem rgba(0, 0, 0, 0.04);
padding: 0.75rem 0.875rem;
border: 1px solid transparent;
display: flex;
Expand All @@ -13,6 +12,7 @@

&.style-primary {
background-color: var(--color-white);
box-shadow: 0 0.125rem 1rem rgba(0, 0, 0, 0.04);

& > input {
width: calc(100% - 2.25rem);
Expand All @@ -31,6 +31,10 @@
border-color: var(--color-primary-purple);
}

&.Error {
border-color: var(--color-text-error);
}

.Input {
@include bodyM;
color: var(--color-text-primary);
Expand Down
16 changes: 11 additions & 5 deletions src/components/ui/Input/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,20 @@ import styles from "@/components/ui/Input/Input.module.scss";
import type { InputProps } from "@/components/ui/Input/Input.types";

const Input = forwardRef<HTMLInputElement, InputProps>(
({ className, type, variant = "primary", isFocus, ...props }, ref) => {
({ className, type, variant = "primary", isFocus, isError, ...props }, ref) => {
return (
<div
className={classNames(styles.InputWrapper, styles[`style-${variant}`], {
[styles.Focused]: isFocus,
})}
className={classNames(
styles.InputWrapper,
styles[`style-${variant}`],
{
[styles.Focused]: isFocus,
[styles.Error]: isError,
},
className,
)}
>
<input type={type} ref={ref} className={classNames(styles.Input, className)} {...props} />
<input type={type} ref={ref} className={styles.Input} {...props} />
{variant === "primary" && <button>수정</button>}
</div>
);
Expand Down
1 change: 1 addition & 0 deletions src/components/ui/Input/Input.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ type InputFieldVariant = "primary" | "secondary";
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
variant?: InputFieldVariant;
isFocus?: boolean;
isError?: boolean;
}
2 changes: 1 addition & 1 deletion src/components/ui/Text/Text.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
line-height: 1.5;

@each $color-name
in ("white", "black", "primary", "secondary", "tertiary", "quarternary", "gradient")
in ("white", "black", "primary", "secondary", "tertiary", "quarternary", "gradient", "error")
{
@if $color-name == "gradient" {
&.color-#{$color-name} {
Expand Down
3 changes: 3 additions & 0 deletions src/components/ui/Text/Text.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ export const ColorPorps: StoryObj<typeof Text> = {
<div className={styles.Wrapper}>
<Text color="gradient" children="gradient" />
</div>
<div className={styles.Wrapper}>
<Text color="error" children="error" />
</div>
</div>
),
};
3 changes: 2 additions & 1 deletion src/components/ui/Text/Text.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ type TextColor =
| "secondary"
| "tertiary"
| "quarternary"
| "gradient";
| "gradient"
| "error";

export interface TextProps extends React.HTMLAttributes<HTMLSpanElement> {
as?: React.ElementType;
Expand Down
4 changes: 2 additions & 2 deletions src/hooks/common/useFocus.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useState } from "react";

export const useFocus = () => {
const [isFocus, setIsFocus] = useState(false);
export const useFocus = ({ defaultFocus }: { defaultFocus?: boolean }) => {
const [isFocus, setIsFocus] = useState(defaultFocus);

const onFocus = () => {
setIsFocus(true);
Expand Down
2 changes: 1 addition & 1 deletion src/styles/_variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
var(--color-primary-pink),
var(--color-primary-purple)
);
--color-text-error: #d45085;

--color-white: #ffffff;
--color-black: #000000;
Expand All @@ -35,7 +36,6 @@
--color-gray600: #363642;
--color-gray700: #161636;
--color-bg-gradient: linear-gradient(180deg, #ffffff, #dcdce8);
--color-bg-error: #d45085;
--color-primary-purple: #443fb6;
--color-primary-pink: #d444ba;
}
10 changes: 10 additions & 0 deletions src/styles/global.scss
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,13 @@ textarea {
outline: none;
background-color: transparent;
}

body[data-scroll-locked] {
overflow: visible !important;
padding: 0 !important;
position: static !important;
}

html body[data-scroll-locked] {
margin: 0 auto !important;
}
Loading