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

[TEAM3] Petit Project 개인 프로필 화면 구현 #19

Open
wants to merge 3 commits into
base: petit-project/team3
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions PetitProjects/Team3/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"@storybook/react": "^6.5.7",
"@storybook/testing-library": "^0.0.11",
"@testing-library/react": "^13.3.0",
"@testing-library/react-hooks": "^8.0.0",
"@types/animejs": "^3.1.4",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
Expand Down
14 changes: 14 additions & 0 deletions PetitProjects/Team3/src/components/Akalee/Akalee.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Akalee from './Akalee';
import type { ComponentStory, ComponentMeta } from '@storybook/react';

export default {
title: 'TEAM3/Akalee',
component: Akalee,
parameters: {
layout: 'fullscreen',
},
} as ComponentMeta<typeof Akalee>;

const Template: ComponentStory<typeof Akalee> = (args) => <Akalee {...args} />;

export const Default = Template.bind({});
57 changes: 57 additions & 0 deletions PetitProjects/Team3/src/components/Akalee/Akalee.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import * as React from 'react';
import { styled, globalCss } from '@stitches/react';
import Clock from './Clock';
import { useMediaQuery, useMediaValue } from './hooks';

const Akalee: React.FC = () => {
globalStyles();
Copy link
Member

Choose a reason for hiding this comment

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

stitiches 라이브러리에서 전역 css를 이렇게 처리하는 군요! 저는 일반적으로 global.css 파일에 전역 css 파일을 봐 왔었어서 신기합니다 :)


const value = useMediaValue(
320,
[320, 360, 480, 560, 720],
useMediaQuery('(min-width: 320px) and (min-height: 320px)'),
useMediaQuery('(min-width: 360px) and (min-height: 360px)'),
useMediaQuery('(min-width: 480px) and (min-height: 480px)'),
useMediaQuery('(min-width: 560px) and (min-height: 560px)'),
useMediaQuery('(min-width: 720px) and (min-height: 720px)'),
);

return (
<Box>
<Clock size={value} />
</Box>
);
};

export default Akalee;

const globalStyles = globalCss({
':root': {
'--color-white': '#ffffff',
'--color-black': '#000000',
'--color-transparent': 'transparent',
},
'*': {
margin: 0,
padding: 0,
boxSizing: 'border-box',
},
'html, body': {
width: '100%',
height: '100%',
overflow: 'hidden',
backgroundColor: 'var(--color-black)',
},
'#root': {
isolation: 'isolate',
width: '100%',
height: '100%',
},
});

const Box = styled('div', {
widht: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
});
76 changes: 76 additions & 0 deletions PetitProjects/Team3/src/components/Akalee/Clock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import * as React from 'react';
import { styled } from '@stitches/react';
import ClockCore from './ClockCore';
import ClockCoreFrame from './ClockCoreFrame';
import { useClockState } from './hooks';

interface ClockProps {
size: number;
}

const Clock: React.FC<ClockProps> = ({ size }) => {
const { hour, minute, second } = useClockState();

const ref = React.useRef<HTMLDivElement | null>(null);

React.useEffect(() => {
const el = ref.current;
if (el === null) return;

const listener = (e: PointerEvent) => {
const offsetWidth = window.document.body.offsetWidth;
const offsetHeight = window.document.body.offsetHeight;

const x = e.pageX - (offsetWidth - size) / 2;
const y = e.pageY - (offsetHeight - size) / 2;

el.style.setProperty('--x', `${x}px`);
el.style.setProperty('--y', `${y}px`);
};

el.addEventListener('pointermove', listener, {
passive: true,
});

return () => {
el.removeEventListener('pointermove', listener);
};
}, [size]);

return (
<AspectRatioBox style={{ width: size, paddingTop: size }}>
<ClockBox
style={{
Copy link
Member

Choose a reason for hiding this comment

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

이 부분은 관심사 분리 측면에서 css 속성에 대한 내용은 하위 컴포넌트(View 역할 하는 컴포넌트)에서만 처리해주게 수정을 해 주어도 좋을 것 같습니다 ㅎㅎ

Copy link
Author

Choose a reason for hiding this comment

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

상위 이벤트의 값을 하위 component로 넘겨줄 경우 컴포넌트의 recomposition 비용이 크다 판단해서 react 렌더링과 연관 없는 css variables을 사용해서 진행하였는데, 보다 좋은 방법이 있을까요?

'--size-pos': `${size * 0.375}px`,
'--size-neg': `-${size * 0.375}px`,
} as React.CSSProperties}
ref={ref}
>
<ClockCore
size={size}
hour={hour}
minute={minute}
second={second}
/>
<ClockCoreFrame />
</ClockBox>
</AspectRatioBox>
);
};

export default Clock;

const AspectRatioBox = styled('div', {
position: 'relative',
margin: 'auto',
});

const ClockBox = styled('div', {
position: 'absolute',
top: 0,
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
});
61 changes: 61 additions & 0 deletions PetitProjects/Team3/src/components/Akalee/ClockCore.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import ClockCore from './ClockCore';
import { useClockState } from './hooks';
import type { ComponentStory, ComponentMeta } from '@storybook/react';

export default {
title: 'TEAM3/Akalee/ClockCore',
component: ClockCore,
argTypes: {
hour: {
control: {
type: 'range',
min: 0,
max: 23,
step: 1,
},
},
minute: {
control: {
type: 'range',
min: 0,
max: 59,
step: 1,
},
},
second: {
control: {
type: 'range',
min: 0,
max: 59,
step: 1,
},
},
},
parameters: {
layout: 'fullscreen',
},
} as ComponentMeta<typeof ClockCore>;

const Template: ComponentStory<typeof ClockCore> =
(args) => <ClockCore {...args} />;

export const Default = Template.bind({});
Default.args = {
size: 480,
hour: 0,
minute: 0,
second: 0,
};

export const WithHook = () => {
const { hour, minute, second } = useClockState();

return (
<ClockCore
size={480}
hour={hour}
minute={minute}
second={second}
/>
);
};
70 changes: 70 additions & 0 deletions PetitProjects/Team3/src/components/Akalee/ClockCore.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import * as React from 'react';
import { styled } from '@stitches/react';
import ClockHand from './ClockHand';

interface ClockCoreProps {
hour: number;
minute: number;
second: number;
size: number;
}

const ClockCore: React.FC<ClockCoreProps> = ({
hour,
minute,
second,
size,
}) => {
return (
<Box
style={{
'--clock-size': `${size}px`,
} as React.CSSProperties}
>
<ClockHand
time={second}
min={0}
max={59}
size={size * RELATIVE_SECOND_HAND_SIZE}
/>
<ClockHand
time={minute}
min={0}
max={59}
size={size * RELATIVE_MINUTE_HAND_SIZE}
/>
<ClockHand
time={(hour >= 12 ? hour - 12 : hour)}
min={0}
max={11}
size={size * RELATIVE_HOUR_HAND_SIZE}
/>
<HandDot />
</Box>
);
};

export default ClockCore;

const RELATIVE_HOUR_HAND_SIZE = 0.1875;
const RELATIVE_MINUTE_HAND_SIZE = 0.25;
const RELATIVE_SECOND_HAND_SIZE = 0.375;

const Box = styled('div', {
position: 'relative',
width: 'var(--clock-size)',
height: 'var(--clock-size)',
borderRadius: '50%',
});

const HandDot = styled('div', {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 16,
height: 16,
borderRadius: 8,
background: '#000000',
border: '2px solid var(--color-white)',
});
80 changes: 80 additions & 0 deletions PetitProjects/Team3/src/components/Akalee/ClockCoreFrame.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import * as React from 'react';
import { styled } from '@stitches/react';
import MakerLabel from './MakerLabel';

const ClockCoreFrame: React.FC = () => {
return (
<Box>
<ClockFrame>
{TIME_PIVOT_ITEMS.map(({ text, deg }) => (
<TimePivot
style={{
transform: `rotate(${deg}deg)`,
}}
key={text}
>
<TimePivotText>{text}</TimePivotText>
</TimePivot>
))}
</ClockFrame>
<MakerLabel />
</Box>
);
};

export default ClockCoreFrame;

const TIME_PIVOT_ITEMS = [
Copy link
Member

Choose a reason for hiding this comment

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

상수 값들은 constants.ts 등의 파일로 별도로 빼도 좋을 것 같아요

{ text: 'I', deg: 30 },
{ text: 'II', deg: 60 },
{ text: 'III', deg: 90 },
{ text: 'IV', deg: 120 },
{ text: 'V', deg: 150 },
{ text: 'VI', deg: 180 },
{ text: 'VII', deg: 210 },
{ text: 'VIII', deg: 240 },
{ text: 'IX', deg: 270 },
{ text: 'X', deg: 300 },
{ text: 'XI', deg: 330 },
{ text: 'XII', deg: 360 },
] as const;

const Box = styled('div', {
Copy link
Member

Choose a reason for hiding this comment

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

styles 값들은 styles.ts 로 별도로 빼도 좋을 것 같아요

Copy link
Author

Choose a reason for hiding this comment

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

요 부분은 스타일 차이가 많이 갈릴 것 같은데요, 저는 개인적으로 해당 컴포넌트에서 사용하기 위한 style 정의 또한 컴포넌트 정의 파일의 context에 묶여있다 생각해서 파일을 분리하는 것 보다 하단으로 분리하는게 명시적이라 생각합니다. 어떻게 생각하시나요?

position: 'absolute',
top: 0,
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: '50%',
border: '2px dashed rgba(255, 255, 255, 0.48)',
overflow: 'hidden',
'-webkit-mask-image': `radial-gradient(
circle var(--size-pos) at var(--x, var(--size-neg)) var(--y, var(--size-neg)),
black 36%,
transparent
)`,
});

const ClockFrame = styled('div', {
width: '100%',
height: '100%',
position: 'relative',
});

const TimePivot = styled('div', {
position: 'absolute',
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'flex-start',
justifyContent: 'center',
paddingTop: 16,
});

const TimePivotText = styled('span', {
fontSize: '1.25rem',
fontWeight: 700,
color: 'rgba(255, 255, 255, 0.48)',
});
Loading