-
Notifications
You must be signed in to change notification settings - Fork 1
feat: 플로팅 버튼 개발 #108
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
feat: 플로팅 버튼 개발 #108
Changes from all commits
d8b34fe
89211d1
d593258
db5605e
b29b7f5
e4a4c48
754e11b
a18e8df
5063936
f5e7c4e
ca2f1ac
f47bbce
79f6334
2fc8d52
315681d
62eace4
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,3 @@ | ||||||||||||||||||||||||||||||||
| export default function AIChartAnalyze() { | ||||||||||||||||||||||||||||||||
| return <div>AIChartAnalyze</div>; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
Comment on lines
+1
to
+3
Contributor
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. 🛠️ Refactor suggestion Component implementation is a placeholder This component is currently just a placeholder with static text. Since this is part of the main AI feature set (차트해석/Chart Analysis), it should contain the actual implementation for chart analysis functionality. Consider implementing the actual AI chart analysis content with proper UI components: -export default function AIChartAnalyze() {
- return <div>AIChartAnalyze</div>;
+export default function AIChartAnalyze() {
+ return (
+ <div className="ai-chart-analyze">
+ <h3>AI 차트해석</h3>
+ <div className="chart-analysis-content">
+ {/* Implement the actual chart analysis UI here */}
+ <p>선택된 코인의 차트를 AI가 분석 중입니다...</p>
+ {/* Add loading state, analysis results, etc. */}
+ </div>
+ </div>
+ );
}📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,3 @@ | ||||||||||||||||||||||||||||||||
| export default function AIChartRecommendation() { | ||||||||||||||||||||||||||||||||
| return <div>AIChartRecommendation</div>; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
Comment on lines
+1
to
+3
Contributor
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. 🛠️ Refactor suggestion Component implementation is a placeholder This component is currently just a placeholder with static text. Since this is part of the main AI feature set (AI 추천판단/Chart Recommendation), it should contain the actual implementation for AI-based recommendation functionality. Consider implementing the actual AI recommendation content: -export default function AIChartRecommendation() {
- return <div>AIChartRecommendation</div>;
+export default function AIChartRecommendation() {
+ return (
+ <div className="ai-chart-recommendation">
+ <h3>AI 추천판단</h3>
+ <div className="recommendation-content">
+ {/* Implement the actual recommendation UI here */}
+ <p>현재 코인 시장에 대한 AI 추천을 분석 중입니다...</p>
+ {/* Add recommendation cards, analysis results, etc. */}
+ </div>
+ </div>
+ );
}📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import IconFloatingOpen from "@/assets/IconFloatingOpen.svg?react"; | ||
| import IconFloatingClose from "@/assets/IconFloatingClose.svg?react"; | ||
|
|
||
| interface FloatingButtonProps { | ||
| isOpen: boolean; | ||
| onClick: () => void; | ||
| } | ||
|
|
||
| export default function FloatingButton({ | ||
| isOpen, | ||
| onClick, | ||
| }: FloatingButtonProps) { | ||
| return ( | ||
| <button onClick={onClick}> | ||
| {isOpen ? <IconFloatingClose /> : <IconFloatingOpen />} | ||
| </button> | ||
| ); | ||
| } | ||
|
klmhyeonwoo marked this conversation as resolved.
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| import IconAIChartAnalyzeInactive from "@/assets/IconAIChartAnalyzeInactive.svg?react"; | ||
| import IconAIChartAnalyzeActive from "@/assets/IconAIChartAnalyzeActive.svg?react"; | ||
| import IconAIChartRecommendInactive from "@/assets/IconAIChartRecommendInactive.svg?react"; | ||
| import IconAIChartRecommendActive from "@/assets/IconAIChartRecommendActive.svg?react"; | ||
| import IconFunctionHelpInactive from "@/assets/IconFunctionHelpInactive.svg?react"; | ||
| import IconFunctionHelpActive from "@/assets/IconFunctionHelpActive.svg?react"; | ||
| import FloatingPanelLayout from "./FloatingPanelLayout"; | ||
| import { useTab } from "@/hooks/useTab"; | ||
| import AIChartAnalyze from "./AIChartAnalyze"; | ||
| import AIChartRecommendation from "./AIChartRecommendation"; | ||
| import FunctionHelp from "./FunctionHelp"; | ||
|
|
||
| export default function FloatingPanel() { | ||
| const TabKey = { | ||
| CHART_ANALYZE: "CHART_ANALYZE", | ||
| CHART_RECOMMENDATION: "CHART_RECOMMENDATION", | ||
| FUNCTION_HELP: "FUNCTION_HELP", | ||
| } as const; | ||
|
|
||
|
Collaborator
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. const tabItems = [
{
key: TabKey.CHART_ANALYZE,
label: "AI 차트해석",
activeIcon: <IconAIChartAnalyzeActive />,
inactiveIcon: <IconAIChartAnalyzeInactive />,
content: <AIChartAnalyze />,
},
{
key: TabKey.CHART_RECOMMENDATION,
label: "AI 추천판단",
activeIcon: <IconAIChartRecommendActive />,
inactiveIcon: <IconAIChartRecommendInactive />,
content: <AIChartRecommendation />,
},
...
];이렇게 위와 같이 중복적으로 사용되는 값들을 변수로 구조화하게 된다면 추후 수정 사항이 발생했을 때 변수 구조 부분만 살짝 바꾸면서 관련 컴포넌트들을 Map 형태로 쉽게 나타낼 수 있을 것 같다는 생각이 드는데 어떻게 생각하시나영! <FloatingPanelLayout>
<Tabs>
<Tabs.List>
{tabItems.map(({ key, label, activeIcon, inactiveIcon }) => (
<Tabs.Trigger
key={key}
value={key}
activeIcon={activeIcon}
inactiveIcon={inactiveIcon}>
{label}
</Tabs.Trigger>
))}
</Tabs.List>
<Tabs.ContentWrapper>
{tabItems.map(({ key, content }) => (
<Tabs.Content key={key} value={key}>
{content}
</Tabs.Content>
))}
</Tabs.ContentWrapper>
</Tabs>
</FloatingPanelLayout>
Member
Author
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. 넵! 말씀하신 코드 리뷰를 바탕으로 제가 작성한 코드를 다시 보니, 새로운 탭에 수정이 생기거나 삭제 또는 추가가 발생할 경우에 어려움이 있을 것 같다고 생각했어요! |
||
| const { Tabs } = useTab<keyof typeof TabKey>(TabKey.CHART_ANALYZE); | ||
|
|
||
| const tabItems = [ | ||
| { | ||
| key: TabKey.CHART_ANALYZE, | ||
| label: "AI 차트해석", | ||
| activeIcon: <IconAIChartAnalyzeActive />, | ||
| inactiveIcon: <IconAIChartAnalyzeInactive />, | ||
| content: <AIChartAnalyze />, | ||
| }, | ||
| { | ||
| key: TabKey.CHART_RECOMMENDATION, | ||
| label: "AI 추천판단", | ||
| activeIcon: <IconAIChartRecommendActive />, | ||
| inactiveIcon: <IconAIChartRecommendInactive />, | ||
| content: <AIChartRecommendation />, | ||
| }, | ||
| { | ||
| key: TabKey.FUNCTION_HELP, | ||
| label: "기능 도움말", | ||
| activeIcon: <IconFunctionHelpActive />, | ||
| inactiveIcon: <IconFunctionHelpInactive />, | ||
| content: <FunctionHelp />, | ||
| }, | ||
| ]; | ||
|
|
||
| return ( | ||
| <FloatingPanelLayout> | ||
| <Tabs> | ||
| <Tabs.List> | ||
| {tabItems.map(({ key, label, activeIcon, inactiveIcon }) => ( | ||
| <Tabs.Trigger | ||
| key={key} | ||
| value={key} | ||
| activeIcon={activeIcon} | ||
| inactiveIcon={inactiveIcon} | ||
| > | ||
| {label} | ||
| </Tabs.Trigger> | ||
| ))} | ||
| </Tabs.List> | ||
| <Tabs.ContentWrapper> | ||
| {tabItems.map(({ key, content }) => ( | ||
| <Tabs.Content key={key} value={key}> | ||
| {content} | ||
| </Tabs.Content> | ||
| ))} | ||
| </Tabs.ContentWrapper> | ||
| </Tabs> | ||
| </FloatingPanelLayout> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| import { PropsWithChildren } from "react"; | ||
| import { css } from "@emotion/react"; | ||
|
|
||
| export default function FloatingPanelLayout({ children }: PropsWithChildren) { | ||
| return ( | ||
| <div css={containerStyle}> | ||
| <div css={panelStyle}> | ||
| {children} | ||
| <div css={arrowStyle} /> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| const containerStyle = css` | ||
| position: absolute; | ||
| bottom: 9rem; | ||
| right: 1rem; | ||
| `; | ||
|
|
||
| const panelStyle = css` | ||
| width: 390px; | ||
| height: calc(100dvh - 450px); | ||
| background-color: white; | ||
| padding: 1.5rem; | ||
| border-radius: 24px; | ||
| box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); | ||
| border: 1px solid #e5e7eb; | ||
| position: relative; | ||
| `; | ||
|
|
||
| const arrowStyle = css` | ||
| position: absolute; | ||
| right: 2rem; | ||
| bottom: -0.5rem; | ||
| width: 2rem; | ||
| height: 2rem; | ||
| background-color: white; | ||
| transform: rotate(45deg); | ||
| border-bottom-right-radius: 2px; | ||
| `; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| import { PropsWithChildren, forwardRef } from "react"; | ||
| import { css } from "@emotion/react"; | ||
|
|
||
| interface FloatingWidgetLayoutProps extends PropsWithChildren { | ||
| containerRef: React.RefObject<HTMLDivElement>; | ||
| } | ||
|
|
||
| const FloatingWidgetLayout = forwardRef< | ||
| HTMLDivElement, | ||
| FloatingWidgetLayoutProps | ||
| >(({ children, containerRef }) => { | ||
| return ( | ||
| <div css={wrapperStyle}> | ||
| <div ref={containerRef} css={containerStyle}> | ||
| {children} | ||
| </div> | ||
| </div> | ||
| ); | ||
| }); | ||
|
|
||
| const wrapperStyle = css` | ||
| position: fixed; | ||
| bottom: 5rem; | ||
| right: 8rem; | ||
| button { | ||
| background: transparent; | ||
| border: none; | ||
| padding: 0; | ||
| margin: 0; | ||
| outline: none; | ||
| } | ||
| `; | ||
|
|
||
| const containerStyle = css` | ||
| position: relative; | ||
| `; | ||
|
|
||
| FloatingWidgetLayout.displayName = "FloatingWidgetLayout"; | ||
|
|
||
| export default FloatingWidgetLayout; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,3 @@ | ||||||||||||||||||||||||||||||||||||||||
| export default function FunctionHelp() { | ||||||||||||||||||||||||||||||||||||||||
| return <div>FunctionHelp</div>; | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1
to
+3
Contributor
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. 🛠️ Refactor suggestion Component implementation is a placeholder This component is currently just a placeholder returning static text. As part of the floating panel's tabbed interface, it should be properly implemented with actual function help content. Consider implementing the actual help content with proper styling and information about the application's features: -export default function FunctionHelp() {
- return <div>FunctionHelp</div>;
+export default function FunctionHelp() {
+ return (
+ <div className="function-help-container">
+ <h3>기능 도움말</h3>
+ <ul>
+ <li>
+ <strong>차트 분석:</strong> 코인 데이터를 분석하여 추세를 파악할 수 있습니다.
+ </li>
+ <li>
+ <strong>AI 추천:</strong> AI 기반 코인 투자 추천을 확인할 수 있습니다.
+ </li>
+ {/* Add more help items as needed */}
+ </ul>
+ </div>
+ );
}📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| import { useRef } from "react"; | ||
| import useToggle from "@/hooks/useToggle"; | ||
| import useClickOutside from "@/hooks/useClickOutside"; | ||
| import FloatingWidgetLayout from "./FloatingWidgetLayout"; | ||
| import FloatingPanel from "./FloatingPanel"; | ||
| import FloatingButton from "./FloatingButton"; | ||
|
|
||
| export default function FloatingWidget() { | ||
| const floatingRef = useRef<HTMLDivElement>(null); | ||
| const { | ||
| value: isOpen, | ||
| toggleValue: toggleOpen, | ||
| setValue: setIsOpen, | ||
| } = useToggle(false); | ||
|
|
||
| useClickOutside(floatingRef, () => { | ||
| if (isOpen) { | ||
| setIsOpen(false); | ||
| } | ||
| }); | ||
|
|
||
| return ( | ||
| <FloatingWidgetLayout containerRef={floatingRef}> | ||
| {isOpen && <FloatingPanel />} | ||
| <FloatingButton isOpen={isOpen} onClick={toggleOpen} /> | ||
| </FloatingWidgetLayout> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| import { RefObject, useEffect } from "react"; | ||
|
|
||
| export default function useClickOutside( | ||
| ref: RefObject<HTMLElement>, | ||
| handler: () => void, | ||
| ) { | ||
| useEffect(() => { | ||
| const listener = (event: MouseEvent | TouchEvent) => { | ||
| if (!ref.current || ref.current.contains(event.target as Node)) { | ||
| return; | ||
| } | ||
| handler(); | ||
| }; | ||
|
|
||
| document.addEventListener("mousedown", listener); | ||
| document.addEventListener("touchstart", listener); | ||
|
|
||
| return () => { | ||
| document.removeEventListener("mousedown", listener); | ||
| document.removeEventListener("touchstart", listener); | ||
| }; | ||
| }, [ref, handler]); | ||
| } |
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.
Remove erroneous import statement in CSS template literal.
There's an import statement incorrectly placed inside a CSS template literal for the button's height. This will cause styling issues and potentially runtime errors.