-
Notifications
You must be signed in to change notification settings - Fork 2
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
base: petit-project/team3
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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({}); |
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(); | ||
|
||
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', | ||
}); |
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={{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 부분은 관심사 분리 측면에서 css 속성에 대한 내용은 하위 컴포넌트(View 역할 하는 컴포넌트)에서만 처리해주게 수정을 해 주어도 좋을 것 같습니다 ㅎㅎ There was a problem hiding this comment. Choose a reason for hiding this commentThe 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', | ||
}); |
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} | ||
/> | ||
); | ||
}; |
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)', | ||
}); |
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 = [ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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', { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. styles 값들은 styles.ts 로 별도로 빼도 좋을 것 같아요 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)', | ||
}); |
There was a problem hiding this comment.
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 파일을 봐 왔었어서 신기합니다 :)