Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[노철] - 태그 컴포넌트 수정 #414

Merged
merged 11 commits into from
Jan 23, 2024
7 changes: 7 additions & 0 deletions src/app/(header)/plans/edit/[planId]/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@
display: flex;
flex-direction: column;
gap: 0.5em;
&--input {
& .counter {
margin-left: 0.5rem;
vertical-align: bottom;
}
}

&--tags {
display: flex;
gap: 0.5rem;
Expand Down
23 changes: 18 additions & 5 deletions src/app/(header)/plans/edit/[planId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
import {
AjajaButton,
Button,
DeletableTag,
IconSwitchButton,
InputTag,
Modal,
ModalSelectIcon,
PlanInput,
Tag,
} from '@/components';
import HelpButton from '@/components/HelpButton/HelpButton';
import TagInput from '@/components/TagInput/TagInput';
import { ajajaToast } from '@/components/Toaster/customToast';
import { planIcons } from '@/constants/planIcons';
import { useEditPlanMutation } from '@/hooks/apis/useEditPlanMutation';
Expand Down Expand Up @@ -113,16 +113,29 @@ export default function EditPage({ params }: { params: { planId: string } }) {
</div>

<div className={classNames('edit-plan-content__tag')}>
<InputTag onSubmit={handleAddTag} />
<div className="edit-plan-content__tag--input">
<TagInput
disabled={planContent.tags.length === 5}
onSubmit={handleAddTag}
placeholder="태그를 입력해주세요"
/>
<span
className={classNames(
'counter',
'font-size-xs',
planContent.tags.length === 5 && 'color-origin-primary',
)}>{`(${planContent.tags.length}/5)`}</span>
</div>

<div className="edit-plan-content__tag--tags">
{planContent.tags.map((tag, index) => (
<Tag
<DeletableTag
key={index}
onClick={() => {
handleRemoveTag(tag);
}}>
{tag}
</Tag>
</DeletableTag>
))}
</div>
<AjajaButton
Expand Down
26 changes: 21 additions & 5 deletions src/components/CreatePlanContent/CreatePlanContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useScroll } from '@/hooks/useScroll';
import { PlanContentType } from '@/types/Plan';
import classNames from 'classnames';
import { useEffect, useRef } from 'react';
import { IconSwitchButton, InputTag, PlanInput, Tag } from '..';
import { DeletableTag, IconSwitchButton, PlanInput, TagInput } from '..';
import HelpButton from '../HelpButton/HelpButton';
import { useSessionStorage } from './../../hooks/useSessionStorage';
import './index.scss';
Expand Down Expand Up @@ -63,7 +63,11 @@ export default function CreatePlanContent({

const handleAddTag = (text: string) => {
const trimedTags = text.trim();
if (planContent.tags.includes(trimedTags) || planContent.tags.length >= 5) {
if (
planContent.tags.includes(trimedTags) ||
planContent.tags.length >= 5 ||
trimedTags.length === 0
) {
return;
}
const newTagList = [...planContent.tags, trimedTags];
Expand Down Expand Up @@ -106,16 +110,28 @@ export default function CreatePlanContent({
/>

<div className={classNames('create-plan-content__tag')}>
<InputTag onSubmit={handleAddTag} />
<div className="create-plan-content__tag--input">
<TagInput
disabled={planContent.tags.length === 5}
onSubmit={handleAddTag}
placeholder="태그를 입력해주세요"
/>
<span
className={classNames(
'counter',
'font-size-xs',
planContent.tags.length === 5 && 'color-origin-primary',
)}>{`(${planContent.tags.length}/5)`}</span>
</div>
<div className="create-plan-content__tag--tags">
{planContent.tags.map((tag, index) => (
<Tag
<DeletableTag
key={index}
onClick={() => {
handleRemoveTag(tag);
}}>
{tag}
</Tag>
</DeletableTag>
))}
</div>
</div>
Expand Down
6 changes: 6 additions & 0 deletions src/components/CreatePlanContent/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@
display: flex;
flex-direction: column;
gap: 0.5rem;
&--input {
& .counter {
margin-left: 0.5rem;
vertical-align: bottom;
}
}

&--tags {
display: flex;
Expand Down
24 changes: 24 additions & 0 deletions src/components/DeletableTag/DeletableTag.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Icon, Tag } from '@/components/';
import classNames from 'classnames';
import './index.scss';

interface DeletableTagProps extends React.HTMLAttributes<HTMLDivElement> {
children: React.ReactNode;
classNameList?: string[];
}
export default function DeletableTag({
children,
onClick,
classNameList = [],
}: DeletableTagProps) {
return (
<Tag
onClick={onClick}
className={classNames('deletableTag', 'font-size-sm', classNameList)}>
<>
{children}
<Icon size="xs" color="primary" name="CANCEL" isFilled />
</>
</Tag>
);
}
12 changes: 12 additions & 0 deletions src/components/DeletableTag/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.deletableTag {
cursor: pointer;
position: relative;
display: inline-block;
padding-right: 0.75rem;

& .icon {
position: absolute;
top: 0;
right: 0;
}
}
1 change: 1 addition & 0 deletions src/components/Icon/Icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const ICON_NAME_MAP = {
ARROW_UP: 'arrow_upward',
COPY: 'content_copy',
ARROW_RIGHT: 'subdirectory_arrow_right',
CANCEL: 'cancel',
};

interface IconProps {
Expand Down
62 changes: 62 additions & 0 deletions src/components/TagInput/TagInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
'use client';

import classNames from 'classnames';
import { FormEvent, useState } from 'react';
import './index.scss';

interface TagInputProps {
onSubmit: (text: string) => void;
disabled?: boolean;
classNameList?: string[];
placeholder?: string;
}
export default function TagInput({
onSubmit,
classNameList = [],
placeholder = '',
disabled = false,
}: TagInputProps) {
const [inputValue, setInputValue] = useState<string>('');
const handleChangeValue = (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.value.length > 10) return;
setInputValue(event.target.value);
};
const handleInputSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
const trimed = inputValue.trim();
onSubmit(trimed);
setInputValue('');
};

return (
<form
className={classNames(classNameList, 'tagInput')}
onSubmit={handleInputSubmit}>
<input
id="tagInput__input"
className="tagInput__input font-size-sm"
value={inputValue}
placeholder=""
disabled={disabled}
onChange={handleChangeValue}
maxLength={10}
/>
{placeholder && (
<label
className={classNames('tagInput__label')}
htmlFor="tagInput__input">
<>
{disabled ? (
<span className="color-origin-primary">
태그는 최대 5개까지 입니다.
</span>
) : (
placeholder
)}
<span className="tagInput__label--focus">{`(${inputValue.length}/10)`}</span>
</>
</label>
)}
</form>
);
}
71 changes: 71 additions & 0 deletions src/components/TagInput/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
.tagInput {
position: relative;
display: inline-block;
width: 50%;
&__input:not(:placeholder-shown) + &__label {
top: 0;
font-size: 0.75rem;
& .tagInput__label--focus {
display: inline;
}
}

&:focus-within {
& > label {
top: 0;
font-size: 0.75rem;
& .tagInput__label--focus {
display: inline;
}
}
&::after {
content: '엔터 입력시 태그를 등록 할 수 있습니다.';
position: absolute;
color: var(--origin-white-200);
white-space: pre;
background-color: var(--origin-gray-200);
line-height: 1.5rem;
left: 0;
bottom: 3rem;
display: block;
border-radius: var(--border-radius);
padding: 0.25rem 0.875rem;
z-index: 1;
}
}
&__input {
padding: 0.25rem 0.875rem;
vertical-align: middle;
margin: 0;
width: 100%;
display: inline-block;
border: 1px solid var(--origin-primary);
border-radius: var(--border-radius);

&:focus {
outline: none;
overflow: visible;
border: 1px solid var(--origin-primary);
}
}
&__label {
position: absolute;
left: 1rem;
white-space: nowrap;
overflow: hidden;
max-width: calc(100% - 1rem);
text-overflow: ellipsis;
transform: translateY(-50%);
font-size: 0.75rem;
color: var(--origin-gray-100);
letter-spacing: 1px;
transition: 0.3s;
pointer-events: none;
top: 50%;
background-color: var(--origin-white-200);

&--focus {
display: none;
}
}
}
2 changes: 2 additions & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,5 @@ export { default as WritableRemindItem } from '@components/RemindItem/WritableRe
export { default as ReadOnlyPlan } from '@components/ReadOnlyPlan/ReadOnlyPlan';
export { default as ErrorToast } from '@components/Svg/ErrorToast';
export { default as SuccessToast } from '@components/Svg/SuccessToast';
export { default as DeletableTag } from '@components/DeletableTag/DeletableTag';
export { default as TagInput } from '@components/TagInput/TagInput';
6 changes: 5 additions & 1 deletion src/hooks/useWritablePlan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@ export const useWritablePlan = (planData: PlanData) => {

const handleAddTag = (text: string) => {
const trimedTags = text.trim();
if (planContent.tags.includes(trimedTags) || planContent.tags.length >= 5) {
if (
planContent.tags.includes(trimedTags) ||
planContent.tags.length >= 5 ||
trimedTags.length === 0
) {
return;
}
const newTagList = [...planContent.tags, trimedTags];
Expand Down
3 changes: 2 additions & 1 deletion src/types/IconName.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@ export type IconName =
| 'ARROW_UP'
| 'COPY'
| 'HELP'
| 'ARROW_RIGHT';
| 'ARROW_RIGHT'
| 'CANCEL';