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

[FE] 사용성 개선 - 지출 내역 입력 아이템 구현 #357

Merged
merged 7 commits into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
4 changes: 2 additions & 2 deletions HDesign/package-lock.json

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

2 changes: 1 addition & 1 deletion HDesign/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "haengdong-design",
"version": "0.1.69",
"version": "0.1.73",
"description": "",
"main": "./dist/index.js",
"module": "./dist/index.js",
Expand Down
75 changes: 75 additions & 0 deletions HDesign/src/components/EditableItem/EditableItem.Input.style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import {css} from '@emotion/react';

import {TextSize} from '@components/Text/Text.type';

import {Theme} from '@theme/theme.type';

import TYPOGRAPHY from '@token/typography';

interface InputWrapperStyleProps {
theme: Theme;
hasFocus: boolean;
hasError: boolean;
}

interface InputStyleProps {
theme: Theme;
textSize: TextSize;
}

interface InputSizeStyleProps {
textSize: TextSize;
}

interface InputBaseStyleProps {
theme: Theme;
}

export const inputWrapperStyle = ({theme, hasFocus, hasError}: InputWrapperStyleProps) =>
css({
position: 'relative',
display: 'inline-block',

'&::after': {
content: '""',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹시 이건 무슨 뜻인가요!?

position: 'absolute',
left: 0,
right: 0,
bottom: 0,
height: '0.125rem',
backgroundColor: hasFocus ? theme.colors.primary : hasError ? theme.colors.error : 'transparent',
transition: 'background-color 0.2s',
transitionTimingFunction: 'cubic-bezier(0.7, 0.62, 0.62, 1.16)',
},
});

export const inputStyle = ({theme, textSize}: InputStyleProps) => [inputSizeStyle({textSize}), inputBaseStyle({theme})];

const inputSizeStyle = ({textSize}: InputSizeStyleProps) => {
const style = {
head: css(TYPOGRAPHY.head),
title: css(TYPOGRAPHY.title),
subTitle: css(TYPOGRAPHY.subTitle),
bodyBold: css(TYPOGRAPHY.bodyBold),
body: css(TYPOGRAPHY.body),
smallBodyBold: css(TYPOGRAPHY.smallBodyBold),
smallBody: css(TYPOGRAPHY.smallBody),
captionBold: css(TYPOGRAPHY.captionBold),
caption: css(TYPOGRAPHY.caption),
tiny: css(TYPOGRAPHY.tiny),
};

return [style[textSize]];
};

const inputBaseStyle = ({theme}: InputBaseStyleProps) =>
css({
border: 'none',
outline: 'none',
paddingBottom: '0.125rem',

color: theme.colors.black,
'&:placeholder': {
color: theme.colors.gray,
},
});
27 changes: 27 additions & 0 deletions HDesign/src/components/EditableItem/EditableItem.Input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/** @jsxImportSource @emotion/react */
import React, {forwardRef, useEffect, useImperativeHandle, useRef, useState} from 'react';

import {InputProps} from '@components/EditableItem/EditableItem.Input.type';

import {useTheme} from '@theme/HDesignProvider';

import {inputStyle, inputWrapperStyle} from './EditableItem.Input.style';
import useEditableItemInput from './useEditableItemInput';

export const EditableItemInput: React.FC<InputProps> = forwardRef<HTMLInputElement, InputProps>(function Input(
{textSize = 'body', hasError = false, ...htmlProps},
ref,
) {
const {theme} = useTheme();
const inputRef = useRef<HTMLInputElement>(null);
const {hasFocus} = useEditableItemInput({inputRef});
useImperativeHandle(ref, () => inputRef.current!);

return (
<div css={inputWrapperStyle({theme, hasFocus, hasError})}>
<input css={inputStyle({theme, textSize})} ref={inputRef} {...htmlProps} />
</div>
);
});

export default EditableItemInput;
18 changes: 18 additions & 0 deletions HDesign/src/components/EditableItem/EditableItem.Input.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {TextSize} from '@components/Text/Text.type';

import {Theme} from '@theme/theme.type';

export interface InputStyleProps {
hasError?: boolean;
textSize?: TextSize;
}

export interface InputCustomProps {}

export interface InputStylePropsWithTheme extends InputStyleProps {
theme: Theme;
}

export type InputOptionProps = InputStyleProps & InputCustomProps;

export type InputProps = React.ComponentProps<'input'> & InputOptionProps;
23 changes: 23 additions & 0 deletions HDesign/src/components/EditableItem/EditableItem.context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/** @jsxImportSource @emotion/react */
import {createContext, PropsWithChildren, useContext, useState} from 'react';

interface EditableItemContextProps {
hasAnyFocus: boolean;
setHasAnyFocus: React.Dispatch<React.SetStateAction<boolean>>;
}

const EditableItemContext = createContext<EditableItemContextProps | null>(null);

export const useEditableItemContext = () => {
const context = useContext(EditableItemContext);
if (!context) {
throw new Error('useEditableItemContext must be used within an EditableItemProvider');
}
return context;
};

export const EditableItemProvider: React.FC<PropsWithChildren> = ({children}: React.PropsWithChildren) => {
const [hasAnyFocus, setHasAnyFocus] = useState(false);

return <EditableItemContext.Provider value={{hasAnyFocus, setHasAnyFocus}}>{children}</EditableItemContext.Provider>;
};
44 changes: 44 additions & 0 deletions HDesign/src/components/EditableItem/EditableItem.input.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/** @jsxImportSource @emotion/react */
import type {Meta, StoryObj} from '@storybook/react';

import EditableItemInput from '@components/EditableItem/EditableItem.Input';

import EditableItem from './EditableItem';
import {EditableItemProvider} from './EditableItem.context';

const meta = {
Copy link
Contributor

@pakxe pakxe Aug 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

스토리북까지 아주 꼼꼼하게 만들어주어서 컴포넌트가 새로 생겨도 빠르게 이해할 수 있었슴다 👍

title: 'Components/EditableItemInput',
component: EditableItemInput,
tags: ['autodocs'],
parameters: {},
argTypes: {
textSize: {
description: '',
control: {type: 'select'},
},
hasError: {
description: '',
control: {type: 'boolean'},
},
},
args: {
placeholder: '지출 내역',
textSize: 'body',
hasError: false,
autoFocus: true,
},
} satisfies Meta<typeof EditableItemInput>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Playground: Story = {
render: ({...args}) => {
return (
<EditableItemProvider>
<EditableItem.Input {...args} />
</EditableItemProvider>
);
},
};
44 changes: 44 additions & 0 deletions HDesign/src/components/EditableItem/EditableItem.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/** @jsxImportSource @emotion/react */
import type {Meta, StoryObj} from '@storybook/react';

import EditableItem from '@components/EditableItem/EditableItem';
import Flex from '@components/Flex/Flex';
import Text from '@components/Text/Text';

const meta = {
title: 'Components/EditableItem',
component: EditableItem,
tags: ['autodocs'],
parameters: {},
argTypes: {
backgroundColor: {
description: '',
control: {type: 'select'},
},
},
args: {
backgroundColor: 'lightGrayContainer',
},
} satisfies Meta<typeof EditableItem>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Playground: Story = {
render: ({...args}) => {
return (
<EditableItem
backgroundColor={args.backgroundColor}
onFocus={() => console.log('focus')}
onBlur={() => console.log('blur')}
>
<EditableItem.Input placeholder="지출 내역" textSize="bodyBold"></EditableItem.Input>
<Flex gap="0.25rem" alignItems="center">
<EditableItem.Input placeholder="0" type="number" style={{textAlign: 'right'}}></EditableItem.Input>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

type이 number일 때, toLocalString('ko-kr')을 같이 해주면 좋을 것 같아요!
onChange를 하면서 입력된 값에 맞춰 toLocalString('ko-kr')가 되면 더 좋을 것 같구요~

<Text size="caption">원</Text>
</Flex>
</EditableItem>
);
},
};
14 changes: 14 additions & 0 deletions HDesign/src/components/EditableItem/EditableItem.style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {css} from '@emotion/react';

import {Theme} from '@theme/theme.type';

import {ColorKeys} from '@token/colors';

export const editableItemStyle = (theme: Theme, backgroundColor: ColorKeys) =>
css({
display: 'flex',
justifyContent: 'space-between',
padding: '0.5rem',
borderRadius: '0.5rem',
backgroundColor: theme.colors[backgroundColor],
});
40 changes: 40 additions & 0 deletions HDesign/src/components/EditableItem/EditableItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/** @jsxImportSource @emotion/react */
import React, {useEffect} from 'react';

import {useTheme} from '@theme/HDesignProvider';

import {editableItemStyle} from './EditableItem.style';
import EditableItemInput from './EditableItem.Input';
import {EditableItemProps} from './EditableItem.type';
import {EditableItemProvider} from './EditableItem.context';
import useEditableItem from './useEditableItem';

const EditableItemBase = ({
onInputFocus,
onInputBlur,
backgroundColor = 'white',
children,
...htmlProps
}: EditableItemProps) => {
const {theme} = useTheme();

useEditableItem({onInputFocus, onInputBlur});

return (
<div css={editableItemStyle(theme, backgroundColor)} {...htmlProps}>
{children}
</div>
);
};

export const EditableItem = (props: EditableItemProps) => {
return (
<EditableItemProvider>
<EditableItemBase {...props} />
</EditableItemProvider>
);
};

EditableItem.Input = EditableItemInput;

export default EditableItem;
20 changes: 20 additions & 0 deletions HDesign/src/components/EditableItem/EditableItem.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {Theme} from '@theme/theme.type';

import {ColorKeys} from '@token/colors';

export interface EditableItemStyleProps {
backgroundColor: ColorKeys;
}

export interface EditableItemCustomProps {
onInputFocus?: () => void;
onInputBlur?: () => void;
}

export interface EditableItemStylePropsWithTheme extends EditableItemStyleProps {
theme: Theme;
}

export type EditableItemOptionProps = EditableItemStyleProps & EditableItemCustomProps;

export type EditableItemProps = React.ComponentProps<'div'> & EditableItemOptionProps;
23 changes: 23 additions & 0 deletions HDesign/src/components/EditableItem/useEditableItem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {useEffect} from 'react';

import {useEditableItemContext} from './EditableItem.context';

interface UseEditableItemProps {
onInputFocus?: () => void;
onInputBlur?: () => void;
}

const useEditableItem = ({onInputFocus, onInputBlur}: UseEditableItemProps) => {
const {hasAnyFocus} = useEditableItemContext();

useEffect(() => {
if (hasAnyFocus && onInputFocus) {
onInputFocus();
}
if (!hasAnyFocus && onInputBlur) {
onInputBlur();
}
}, [hasAnyFocus, onInputFocus, onInputBlur]);
};
Comment on lines +13 to +21
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

focus가 풀리거나 blur될 때 api가 실행되도록 하는 확장성 좋아요!


export default useEditableItem;
Loading
Loading