Skip to content
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
20,968 changes: 20,968 additions & 0 deletions .pnp.cjs

Large diffs are not rendered by default.

2,076 changes: 2,076 additions & 0 deletions .pnp.loader.mjs

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions packages/global-components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './src/utils'
export * from './src/manager'
export * from './src/provider'
10 changes: 10 additions & 0 deletions packages/global-components/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "@daehwahap/global-components",
"version": "0.0.1",
"private": true,
"devDependencies": {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

요런식으로 packageManager 명시해줘도 좋을듯 !
image

"@types/react": "^18.2.61",
"react": "^18.2.0",
"typescript": "5.4.3"
}
}
33 changes: 33 additions & 0 deletions packages/global-components/src/manager/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { GlobalComponentContextType } from '../types'

type GlobalComponentDisplayName =
| 'BottomSheet'
| 'LoadingIndicator'
| 'Popup'
| 'ScreenBottomSheet'
| 'Toast'

class GlobalComponentManager {
private map = new Map<string, React.RefObject<GlobalComponentContextType<any>>>()

public registerRef(name: string, ref: React.RefObject<GlobalComponentContextType<any>>): void {
if (this.map.has(name)) return
this.map.set(name, ref)
}

public async hideAll(): Promise<boolean> {
await Promise.all(Array.from(this.map).map(([_, popup]) => popup.current?.hide()))
return true
}

public async hide(params: { name: GlobalComponentDisplayName }) {
const componentRef = this.map.get(params.name)

if (componentRef) {
await componentRef.current?.hide()
}
return true
}
}

export const globalComponentManager = new GlobalComponentManager()
68 changes: 68 additions & 0 deletions packages/global-components/src/provider/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import {
createContext,
useCallback,
useContext,
useImperativeHandle,
useMemo,
useRef,
useState,
} from 'react'
import {
Animation,
GlobalComponentContextProviderProps,
GlobalComponentContextType,
} from '../types'

export const GlobalComponentContext = createContext<GlobalComponentContextType | null>(null)

const useGlobalComponentContext = () => {
const context = useContext(GlobalComponentContext)

if (!context) {
throw new Error('GlobalComponent should be used with GlobalComponentContext')
}

return context
}

const GlobalComponentContextProvider = <T extends any>({
Component,
internalRef,
}: GlobalComponentContextProviderProps<T>) => {
const [props, setProps] = useState<T | null>(null)

const animations = useRef<Animation[]>([])

const execHideAnimations = useCallback(async () => {
await Promise.all(animations.current.map((hideAnimationFn) => hideAnimationFn()))
animations.current = []
}, [])

const hideComponent = useCallback(async () => {
await execHideAnimations()
setProps(null)
}, [])

const context = useMemo(
() => ({
show: async (p: T) => {
await hideComponent()
setProps(p)
},
hide: () => hideComponent(),
}),
[],
)

useImperativeHandle(internalRef, () => context, [])

if (!props) return <></>

return (
<GlobalComponentContext.Provider value={context}>
<Component {...props} />
</GlobalComponentContext.Provider>
)
}

export { useGlobalComponentContext, GlobalComponentContextProvider }
11 changes: 11 additions & 0 deletions packages/global-components/src/types/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export type Animation = () => Promise<void>

export interface GlobalComponentContextType<T = any> {
show: (props: T, hidePrevComponentImmediate?: boolean) => Promise<void>
hide: () => Promise<void>
}

export interface GlobalComponentContextProviderProps<T> {
Component: React.FC<T>
internalRef: React.RefObject<GlobalComponentContextType<T>>
}
39 changes: 39 additions & 0 deletions packages/global-components/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { createContext, createElement, createRef } from 'react'
import { GlobalComponentContextType } from '../types'
import { globalComponentManager } from '../manager'
import { GlobalComponentContextProvider } from '../provider'

export const createPortal = <T>(Component: React.FC<T>) => {
const context = createContext<GlobalComponentContextType<T>>({} as any)

const internalRef = createRef<GlobalComponentContextType<T>>()

const show = async (props: T, hidePrevComponentImmediate?: boolean) => {
if (!internalRef.current) {
return console.warn('global-components should be used with context')
}

await internalRef.current.show(props, hidePrevComponentImmediate)
}

const hide = async () => {
if (!internalRef.current) {
return console.warn('global-components should be used with context')
}

await internalRef.current.hide()
}

globalComponentManager.registerRef(Component.displayName || Component.name, internalRef)

return {
show,
hide,
Portal: ({ children }: { children?: React.ReactNode }) =>
createElement(
context.Provider,
{ value: { show, hide } },
createElement(GlobalComponentContextProvider<T>, { Component, internalRef }, children),
),
}
}
2 changes: 2 additions & 0 deletions web/daehwahap-webview/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
},
"dependencies": {
"@daehwahap/daehwahap-server": "workspace:^",
"@daehwahap/global-components": "workspace:^",
"@trpc/client": "^10.45.2",
"@trpc/server": "^10.45.2",
"framer-motion": "^11.1.1",
"next": "14.1.4",
"react": "^18",
"react-dom": "^18"
Expand Down
24 changes: 12 additions & 12 deletions web/daehwahap-webview/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";

const inter = Inter({ subsets: ["latin"] });
import type { Metadata } from 'next'
import './globals.css'
import HOCProvider from '../global-components/hocs/HOCProvider'

export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
title: 'Create Next App',
description: 'Generated by create next app',
}

export default function RootLayout({
export function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
children: React.ReactNode
}>) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
<HOCProvider>
<body>{children}</body>
</HOCProvider>
</html>
);
)
}
10 changes: 6 additions & 4 deletions web/daehwahap-webview/src/app/trpc/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { trpc } from '../../trpc'
import { TestButton } from '../../components/TestButton'

const TrpcPage = async () => {
const { greeting } = await trpc.createUser.mutate({ greeting: 'aa' })

return <div>{greeting}</div>
return (
<div style={{ backgroundColor: 'blue', height: '100vh' }}>
<TestButton />
</div>
)
}

export default TrpcPage
17 changes: 17 additions & 0 deletions web/daehwahap-webview/src/components/TestButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use client'

import Toast from '../global-components/components/Toast'

export const TestButton = () => {
return (
<button
style={{ backgroundColor: 'black', width: 100, height: 100 }}
onClick={() => {
console.log('toast')
Toast.show({ text: 'toast' })
}}
>
aaaaaaa
</button>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
'use client'

import { motion, AnimatePresence } from 'framer-motion'
import { useEffect } from 'react'
import { createPortal, useGlobalComponentContext } from '@daehwahap/global-components'

interface ToastProps {
text: string
position?: 'top' | 'middle' | 'bottom'
duration?: number
}

const Toast = ({ text, position = 'top', duration = 2000 }: ToastProps) => {
// 이거는 Toast에만 해당하는 context임
const { hide } = useGlobalComponentContext()

useEffect(() => {
setTimeout(() => {
hide()
}, duration)
}, [])

return (
<AnimatePresence>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 20 }}
transition={{ duration: 0.3 }}
style={{
position: 'fixed',
top: 60,
width: '100vw',
maxWidth: '720px',
display: 'flex',
justifyContent: 'center',
}}
>
<div
style={{
background: '#000000BF',
color: '#fff',
padding: '12px 20px',
borderRadius: '12px',
}}
>
{text}
</div>
</motion.div>
</AnimatePresence>
)
}

export default createPortal(Toast)
19 changes: 19 additions & 0 deletions web/daehwahap-webview/src/global-components/hocs/HOCProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
'use client'

import { ReactNode } from 'react'
import Toast from '../components/Toast'

type HOCProviderProps = { children: ReactNode }

const HOCProvider = ({ children }: HOCProviderProps) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

아래처럼 해도 되겠다

Suggested change
const HOCProvider = ({ children }: HOCProviderProps) => {
const HOCProvider = ({ children }: PropsWithChildren) => {

return (
<>
{children}
<div style={{ position: 'absolute' }}>
<Toast.Portal />
</div>
Comment on lines +12 to +14

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

이쪽에서 hydration error 발생하는데 createPortal에서 createElement 해주는 부분 때문인가
image

</>
)
}

export default HOCProvider
32 changes: 32 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,7 @@ __metadata:
dependencies:
"@daehwahap/daehwahap-server": "workspace:^"
"@daehwahap/eslint-config": "workspace:^"
"@daehwahap/global-components": "workspace:^"
"@trpc/client": "npm:^10.45.2"
"@trpc/server": "npm:^10.45.2"
"@types/eslint": "npm:^8"
Expand All @@ -578,6 +579,7 @@ __metadata:
"@types/react-dom": "npm:^18"
autoprefixer: "npm:^10.0.1"
eslint: "npm:8.57.0"
framer-motion: "npm:^11.1.1"
next: "npm:14.1.4"
postcss: "npm:^8"
react: "npm:^18"
Expand Down Expand Up @@ -608,6 +610,16 @@ __metadata:
languageName: unknown
linkType: soft

"@daehwahap/global-components@workspace:^, @daehwahap/global-components@workspace:packages/global-components":
version: 0.0.0-use.local
resolution: "@daehwahap/global-components@workspace:packages/global-components"
dependencies:
"@types/react": "npm:^18.2.61"
react: "npm:^18.2.0"
typescript: "npm:5.4.3"
languageName: unknown
linkType: soft

"@daehwahap/web@workspace:apps/web":
version: 0.0.0-use.local
resolution: "@daehwahap/web@workspace:apps/web"
Expand Down Expand Up @@ -5044,6 +5056,26 @@ __metadata:
languageName: node
linkType: hard

"framer-motion@npm:^11.1.1":
version: 11.1.1
resolution: "framer-motion@npm:11.1.1"
dependencies:
tslib: "npm:^2.4.0"
peerDependencies:
"@emotion/is-prop-valid": "*"
react: ^18.0.0
react-dom: ^18.0.0
peerDependenciesMeta:
"@emotion/is-prop-valid":
optional: true
react:
optional: true
react-dom:
optional: true
checksum: 10c0/0f80828f27ab18937a0dee55efacbd124f248f8eb869e581aeabbbbd755eb2cd42f70f5490edf1280abdef3d08c4c0d3f404db8fe6d32583d3842cb575f09cdb
languageName: node
linkType: hard

"fresh@npm:0.5.2":
version: 0.5.2
resolution: "fresh@npm:0.5.2"
Expand Down